743 lines
24 KiB
C++
743 lines
24 KiB
C++
/**
|
|
@file simplescript.cpp
|
|
|
|
@brief Implements the simplescript class.
|
|
*/
|
|
|
|
#include "simplescript.h"
|
|
#include "console.h"
|
|
#include "variable.h"
|
|
#include "debugger.h"
|
|
#include "filehelper.h"
|
|
#include <thread>
|
|
|
|
enum CMDRESULT
|
|
{
|
|
STATUS_ERROR = false,
|
|
STATUS_CONTINUE = true,
|
|
STATUS_EXIT = 2,
|
|
STATUS_PAUSE = 3
|
|
};
|
|
|
|
static std::vector<LINEMAPENTRY> linemap;
|
|
static std::vector<SCRIPTBP> scriptbplist;
|
|
static std::vector<int> scriptstack;
|
|
static int scriptIp = 0;
|
|
static int scriptIpOld = 0;
|
|
static bool volatile bAbort = false;
|
|
static bool volatile bIsRunning = false;
|
|
static bool scriptLogEnabled = false;
|
|
static CMDRESULT scriptLastError = STATUS_ERROR;
|
|
|
|
static SCRIPTBRANCHTYPE scriptgetbranchtype(const char* text)
|
|
{
|
|
char newtext[MAX_SCRIPT_LINE_SIZE] = "";
|
|
strcpy_s(newtext, StringUtils::Trim(text).c_str());
|
|
if(!strstr(newtext, " "))
|
|
strcat_s(newtext, " ");
|
|
if(!strncmp(newtext, "jmp ", 4) || !strncmp(newtext, "goto ", 5))
|
|
return scriptjmp;
|
|
else if(!strncmp(newtext, "jbe ", 4) || !strncmp(newtext, "ifbe ", 5) || !strncmp(newtext, "ifbeq ", 6) || !strncmp(newtext, "jle ", 4) || !strncmp(newtext, "ifle ", 5) || !strncmp(newtext, "ifleq ", 6))
|
|
return scriptjbejle;
|
|
else if(!strncmp(newtext, "jae ", 4) || !strncmp(newtext, "ifae ", 5) || !strncmp(newtext, "ifaeq ", 6) || !strncmp(newtext, "jge ", 4) || !strncmp(newtext, "ifge ", 5) || !strncmp(newtext, "ifgeq ", 6))
|
|
return scriptjaejge;
|
|
else if(!strncmp(newtext, "jne ", 4) || !strncmp(newtext, "ifne ", 5) || !strncmp(newtext, "ifneq ", 6) || !strncmp(newtext, "jnz ", 4) || !strncmp(newtext, "ifnz ", 5))
|
|
return scriptjnejnz;
|
|
else if(!strncmp(newtext, "je ", 3) || !strncmp(newtext, "ife ", 4) || !strncmp(newtext, "ifeq ", 5) || !strncmp(newtext, "jz ", 3) || !strncmp(newtext, "ifz ", 4))
|
|
return scriptjejz;
|
|
else if(!strncmp(newtext, "jb ", 3) || !strncmp(newtext, "ifb ", 4) || !strncmp(newtext, "jl ", 3) || !strncmp(newtext, "ifl ", 4))
|
|
return scriptjbjl;
|
|
else if(!strncmp(newtext, "ja ", 3) || !strncmp(newtext, "ifa ", 4) || !strncmp(newtext, "jg ", 3) || !strncmp(newtext, "ifg ", 4))
|
|
return scriptjajg;
|
|
else if(!strncmp(newtext, "call ", 5))
|
|
return scriptcall;
|
|
return scriptnobranch;
|
|
}
|
|
|
|
static int scriptlabelfind(const char* labelname)
|
|
{
|
|
int linecount = (int)linemap.size();
|
|
for(int i = 0; i < linecount; i++)
|
|
if(linemap.at(i).type == linelabel && !strcmp(linemap.at(i).u.label, labelname))
|
|
return i + 1;
|
|
return 0;
|
|
}
|
|
|
|
static inline bool isEmptyLine(SCRIPTLINETYPE type)
|
|
{
|
|
return type == lineempty || type == linecomment || type == linelabel;
|
|
}
|
|
|
|
static int scriptinternalstep(int fromIp) //internal step routine
|
|
{
|
|
int maxIp = (int)linemap.size(); //maximum ip
|
|
if(fromIp >= maxIp) //script end
|
|
return fromIp;
|
|
while(isEmptyLine(linemap.at(fromIp).type) && fromIp < maxIp) //skip empty lines
|
|
fromIp++;
|
|
fromIp++;
|
|
return fromIp;
|
|
}
|
|
|
|
static bool scriptisinternalcommand(const char* text, const char* cmd)
|
|
{
|
|
int len = (int)strlen(text);
|
|
int cmdlen = (int)strlen(cmd);
|
|
if(cmdlen > len)
|
|
return false;
|
|
else if(cmdlen == len)
|
|
return scmp(text, cmd);
|
|
else if(text[cmdlen] == ' ')
|
|
return (!_strnicmp(text, cmd, cmdlen));
|
|
return false;
|
|
}
|
|
|
|
static bool scriptcreatelinemap(const char* filename)
|
|
{
|
|
String filedata;
|
|
if(!FileHelper::ReadAllText(filename, filedata))
|
|
{
|
|
String TranslatedString = GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "FileHelper::ReadAllText failed..."));
|
|
GuiScriptError(0, TranslatedString.c_str());
|
|
return false;
|
|
}
|
|
auto len = filedata.length();
|
|
char temp[256] = "";
|
|
LINEMAPENTRY entry;
|
|
memset(&entry, 0, sizeof(entry));
|
|
linemap.clear();
|
|
for(size_t i = 0, j = 0; i < len; i++) //make raw line map
|
|
{
|
|
if(filedata[i] == '\r' && filedata[i + 1] == '\n') //windows file
|
|
{
|
|
memset(&entry, 0, sizeof(entry));
|
|
int add = 0;
|
|
while(isspace(temp[add]))
|
|
add++;
|
|
strcpy_s(entry.raw, temp + add);
|
|
*temp = 0;
|
|
j = 0;
|
|
i++;
|
|
linemap.push_back(entry);
|
|
}
|
|
else if(filedata[i] == '\n') //other file
|
|
{
|
|
memset(&entry, 0, sizeof(entry));
|
|
int add = 0;
|
|
while(isspace(temp[add]))
|
|
add++;
|
|
strcpy_s(entry.raw, temp + add);
|
|
*temp = 0;
|
|
j = 0;
|
|
linemap.push_back(entry);
|
|
}
|
|
else if(j >= 254)
|
|
{
|
|
memset(&entry, 0, sizeof(entry));
|
|
int add = 0;
|
|
while(isspace(temp[add]))
|
|
add++;
|
|
strcpy_s(entry.raw, temp + add);
|
|
*temp = 0;
|
|
j = 0;
|
|
linemap.push_back(entry);
|
|
}
|
|
else
|
|
j += sprintf_s(temp + j, sizeof(temp) - j, "%c", filedata[i]);
|
|
}
|
|
if(*temp)
|
|
{
|
|
memset(&entry, 0, sizeof(entry));
|
|
strcpy_s(entry.raw, temp);
|
|
linemap.push_back(entry);
|
|
}
|
|
int linemapsize = (int)linemap.size();
|
|
while(linemapsize && !*linemap.at(linemapsize - 1).raw) //remove empty lines from the end
|
|
{
|
|
linemapsize--;
|
|
linemap.pop_back();
|
|
}
|
|
for(int i = 0; i < linemapsize; i++)
|
|
{
|
|
LINEMAPENTRY cur = linemap.at(i);
|
|
|
|
//temp. remove comments from the raw line
|
|
char line_comment[256] = "";
|
|
char* comment = nullptr;
|
|
{
|
|
auto len = strlen(cur.raw);
|
|
auto inquote = false;
|
|
auto inescape = false;
|
|
for(size_t i = 0; i < len; i++)
|
|
{
|
|
auto ch = cur.raw[i];
|
|
switch(ch) //simple state machine to determine if the "//" is in quotes
|
|
{
|
|
case '\"':
|
|
if(!inescape)
|
|
inquote = !inquote;
|
|
inescape = false;
|
|
break;
|
|
case '\\':
|
|
inescape = !inescape;
|
|
break;
|
|
default:
|
|
inescape = false;
|
|
}
|
|
if(!inquote && ch == '/' && i + 1 < len && cur.raw[i + 1] == '/')
|
|
{
|
|
comment = cur.raw + i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(comment && comment != cur.raw) //only when the line doesnt start with a comment
|
|
{
|
|
if(*(comment - 1) == ' ') //space before comment
|
|
{
|
|
strcpy_s(line_comment, comment);
|
|
*(comment - 1) = '\0';
|
|
}
|
|
else //no space before comment
|
|
{
|
|
strcpy_s(line_comment, comment);
|
|
*comment = 0;
|
|
}
|
|
}
|
|
|
|
int rawlen = (int)strlen(cur.raw);
|
|
if(!rawlen) //empty
|
|
{
|
|
cur.type = lineempty;
|
|
}
|
|
else if(!strncmp(cur.raw, "//", 2) || *cur.raw == ';') //comment
|
|
{
|
|
cur.type = linecomment;
|
|
strcpy_s(cur.u.comment, cur.raw);
|
|
}
|
|
else if(cur.raw[rawlen - 1] == ':') //label
|
|
{
|
|
cur.type = linelabel;
|
|
sprintf_s(cur.u.label, "l %.*s", rawlen - 1, cur.raw); //create a fake command for formatting
|
|
strcpy_s(cur.u.label, StringUtils::Trim(cur.u.label).c_str());
|
|
strcpy_s(temp, cur.u.label + 2);
|
|
strcpy_s(cur.u.label, temp); //remove fake command
|
|
if(!*cur.u.label || !strcmp(cur.u.label, "\"\"")) //no label text
|
|
{
|
|
char message[256] = "";
|
|
sprintf_s(message, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Empty label detected on line %d!")), i + 1);
|
|
GuiScriptError(0, message);
|
|
linemap.clear();
|
|
return false;
|
|
}
|
|
int foundlabel = scriptlabelfind(cur.u.label);
|
|
if(foundlabel) //label defined twice
|
|
{
|
|
char message[256] = "";
|
|
sprintf_s(message, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Duplicate label \"%s\" detected on lines %d and %d!")), cur.u.label, foundlabel, i + 1);
|
|
GuiScriptError(0, message);
|
|
linemap.clear();
|
|
return false;
|
|
}
|
|
}
|
|
else if(scriptgetbranchtype(cur.raw) != scriptnobranch) //branch
|
|
{
|
|
cur.type = linebranch;
|
|
cur.u.branch.type = scriptgetbranchtype(cur.raw);
|
|
char newraw[MAX_SCRIPT_LINE_SIZE] = "";
|
|
strcpy_s(newraw, StringUtils::Trim(cur.raw).c_str());
|
|
int rlen = (int)strlen(newraw);
|
|
for(int j = 0; j < rlen; j++)
|
|
if(newraw[j] == ' ')
|
|
{
|
|
strcpy_s(cur.u.branch.branchlabel, newraw + j + 1);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cur.type = linecommand;
|
|
strcpy_s(cur.u.command, cur.raw);
|
|
}
|
|
|
|
//append the comment to the raw line again
|
|
if(*line_comment)
|
|
sprintf_s(cur.raw + rawlen, sizeof(cur.raw) - rawlen, "\1%s", line_comment);
|
|
linemap.at(i) = cur;
|
|
}
|
|
linemapsize = (int)linemap.size();
|
|
for(int i = 0; i < linemapsize; i++)
|
|
{
|
|
auto & currentLine = linemap.at(i);
|
|
if(currentLine.type == linebranch) //invalid branch label
|
|
{
|
|
int labelline = scriptlabelfind(currentLine.u.branch.branchlabel);
|
|
if(!labelline) //invalid branch label
|
|
{
|
|
char message[256] = "";
|
|
sprintf_s(message, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Invalid branch label \"%s\" detected on line %d!")), currentLine.u.branch.branchlabel, i + 1);
|
|
GuiScriptError(0, message);
|
|
linemap.clear();
|
|
return false;
|
|
}
|
|
else //set the branch destination line
|
|
currentLine.u.branch.dest = scriptinternalstep(labelline);
|
|
}
|
|
}
|
|
if(!linemap.empty())
|
|
{
|
|
memset(&entry, 0, sizeof(entry));
|
|
entry.type = linecommand;
|
|
strcpy_s(entry.raw, "ret");
|
|
strcpy_s(entry.u.command, "ret");
|
|
|
|
const auto & lastline = linemap.back();
|
|
switch(lastline.type)
|
|
{
|
|
case linecommand:
|
|
if(scriptisinternalcommand(lastline.u.command, "ret")
|
|
|| scriptisinternalcommand(lastline.u.command, "invalid")
|
|
|| scriptisinternalcommand(lastline.u.command, "error"))
|
|
{
|
|
// there is already a terminating command at the end of the script
|
|
}
|
|
else
|
|
{
|
|
linemap.push_back(entry);
|
|
}
|
|
break;
|
|
case linebranch:
|
|
// a branch at the end of the script can only go back
|
|
break;
|
|
case linelabel:
|
|
case linecomment:
|
|
case lineempty:
|
|
linemap.push_back(entry);
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool scriptinternalbpget(int line) //internal bpget routine
|
|
{
|
|
int bpcount = (int)scriptbplist.size();
|
|
for(int i = 0; i < bpcount; i++)
|
|
if(scriptbplist.at(i).line == line)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static bool scriptinternalbptoggle(int line) //internal breakpoint
|
|
{
|
|
if(!line || line > (int)linemap.size()) //invalid line
|
|
return false;
|
|
line = scriptinternalstep(line - 1); //no breakpoints on non-executable locations
|
|
if(scriptinternalbpget(line)) //remove breakpoint
|
|
{
|
|
int bpcount = (int)scriptbplist.size();
|
|
for(int i = 0; i < bpcount; i++)
|
|
if(scriptbplist.at(i).line == line)
|
|
{
|
|
scriptbplist.erase(scriptbplist.begin() + i);
|
|
break;
|
|
}
|
|
}
|
|
else //add breakpoint
|
|
{
|
|
SCRIPTBP newbp;
|
|
newbp.silent = true;
|
|
newbp.line = line;
|
|
scriptbplist.push_back(newbp);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool scriptinternalbranch(SCRIPTBRANCHTYPE type) //determine if we should jump
|
|
{
|
|
duint ezflag = 0;
|
|
duint bsflag = 0;
|
|
varget("$_EZ_FLAG", &ezflag, 0, 0);
|
|
varget("$_BS_FLAG", &bsflag, 0, 0);
|
|
bool bJump = false;
|
|
switch(type)
|
|
{
|
|
case scriptcall:
|
|
case scriptjmp:
|
|
bJump = true;
|
|
break;
|
|
case scriptjnejnz: //$_EZ_FLAG=0
|
|
if(!ezflag)
|
|
bJump = true;
|
|
break;
|
|
case scriptjejz: //$_EZ_FLAG=1
|
|
if(ezflag)
|
|
bJump = true;
|
|
break;
|
|
case scriptjbjl: //$_BS_FLAG=0 and $_EZ_FLAG=0 //below, not equal
|
|
if(!bsflag && !ezflag)
|
|
bJump = true;
|
|
break;
|
|
case scriptjajg: //$_BS_FLAG=1 and $_EZ_FLAG=0 //above, not equal
|
|
if(bsflag && !ezflag)
|
|
bJump = true;
|
|
break;
|
|
case scriptjbejle: //$_BS_FLAG=0 or $_EZ_FLAG=1
|
|
if(!bsflag || ezflag)
|
|
bJump = true;
|
|
break;
|
|
case scriptjaejge: //$_BS_FLAG=1 or $_EZ_FLAG=1
|
|
if(bsflag || ezflag)
|
|
bJump = true;
|
|
break;
|
|
default:
|
|
bJump = false;
|
|
break;
|
|
}
|
|
return bJump;
|
|
}
|
|
|
|
static CMDRESULT scriptinternalcmdexec(const char* cmd, bool silentRet)
|
|
{
|
|
if(scriptisinternalcommand(cmd, "ret")) //script finished
|
|
{
|
|
if(!scriptstack.size()) //nothing on the stack
|
|
{
|
|
if(!silentRet)
|
|
{
|
|
String TranslatedString = GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Script finished!"));
|
|
GuiScriptMessage(TranslatedString.c_str());
|
|
}
|
|
return STATUS_EXIT;
|
|
}
|
|
scriptIp = scriptstack.back();
|
|
scriptstack.pop_back(); //remove last stack entry
|
|
return STATUS_CONTINUE;
|
|
}
|
|
else if(scriptisinternalcommand(cmd, "error")) //show an error and end the script
|
|
{
|
|
GuiScriptError(0, StringUtils::Trim(cmd + strlen("error"), " \"'").c_str());
|
|
return STATUS_EXIT;
|
|
}
|
|
else if(scriptisinternalcommand(cmd, "invalid")) //invalid command for testing
|
|
return STATUS_ERROR;
|
|
else if(scriptisinternalcommand(cmd, "pause")) //pause the script
|
|
return STATUS_PAUSE;
|
|
else if(scriptisinternalcommand(cmd, "nop")) //do nothing
|
|
return STATUS_CONTINUE;
|
|
else if(scriptisinternalcommand(cmd, "log"))
|
|
scriptLogEnabled = true;
|
|
//super disgusting hack(s) to support branches in the GUI
|
|
{
|
|
auto branchtype = scriptgetbranchtype(cmd);
|
|
if(branchtype != scriptnobranch)
|
|
{
|
|
String cmdStr = cmd;
|
|
auto branchlabel = StringUtils::Trim(cmdStr.substr(cmdStr.find(' ')));
|
|
auto labelIp = scriptlabelfind(branchlabel.c_str());
|
|
if(!labelIp)
|
|
{
|
|
char message[256] = "";
|
|
sprintf_s(message, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Invalid branch label \"%s\" detected on line %d!")), branchlabel.c_str(), 0);
|
|
GuiScriptError(0, message);
|
|
return STATUS_ERROR;
|
|
}
|
|
if(scriptinternalbranch(branchtype))
|
|
{
|
|
if(branchtype == scriptcall) //calls have a special meaning
|
|
scriptstack.push_back(scriptIp);
|
|
scriptIp = scriptinternalstep(labelIp); //go to the first command after the label
|
|
GuiScriptSetIp(scriptIp);
|
|
}
|
|
return STATUS_CONTINUE;
|
|
}
|
|
}
|
|
auto res = cmddirectexec(cmd);
|
|
while(DbgIsDebugging() && dbgisrunning() && !bAbort) //while not locked (NOTE: possible deadlock)
|
|
{
|
|
Sleep(1);
|
|
GuiProcessEvents(); //workaround for scripts being executed on the GUI thread
|
|
}
|
|
scriptLogEnabled = false;
|
|
return res ? STATUS_CONTINUE : STATUS_ERROR;
|
|
}
|
|
|
|
static bool scriptinternalcmd(bool silentRet)
|
|
{
|
|
bool bContinue = true;
|
|
if(size_t(scriptIp - 1) >= linemap.size())
|
|
return false;
|
|
const LINEMAPENTRY & cur = linemap.at(scriptIp - 1);
|
|
scriptIpOld = scriptIp;
|
|
scriptIp = scriptinternalstep(scriptIp);
|
|
if(cur.type == linecommand)
|
|
{
|
|
scriptLastError = scriptinternalcmdexec(cur.u.command, silentRet);
|
|
switch(scriptLastError)
|
|
{
|
|
case STATUS_CONTINUE:
|
|
if(scriptIp == scriptIpOld)
|
|
{
|
|
bContinue = false;
|
|
scriptIp = scriptinternalstep(0);
|
|
}
|
|
break;
|
|
case STATUS_ERROR:
|
|
bContinue = false;
|
|
scriptIp = scriptIpOld;
|
|
GuiScriptError(scriptIp, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Error executing command!")));
|
|
break;
|
|
case STATUS_EXIT:
|
|
bContinue = false;
|
|
scriptIp = scriptinternalstep(0);
|
|
GuiScriptSetIp(scriptIp);
|
|
break;
|
|
case STATUS_PAUSE:
|
|
bContinue = false; //stop running the script
|
|
GuiScriptSetIp(scriptIp);
|
|
break;
|
|
}
|
|
}
|
|
else if(cur.type == linebranch)
|
|
{
|
|
if(scriptinternalbranch(cur.u.branch.type))
|
|
{
|
|
if(cur.u.branch.type == scriptcall) //calls have a special meaning
|
|
scriptstack.push_back(scriptIp);
|
|
scriptIp = scriptlabelfind(cur.u.branch.branchlabel);
|
|
scriptIp = scriptinternalstep(scriptIp); //go to the first command after the label
|
|
}
|
|
}
|
|
return bContinue;
|
|
}
|
|
|
|
static DWORD WINAPI scriptLoadSyncThread(LPVOID filename)
|
|
{
|
|
scriptLoadSync(reinterpret_cast<const char*>(filename));
|
|
return 0;
|
|
}
|
|
|
|
bool scriptRunSync(int destline, bool silentRet)
|
|
{
|
|
if(!destline || destline > (int)linemap.size()) //invalid line
|
|
destline = 0;
|
|
if(destline)
|
|
{
|
|
destline = scriptinternalstep(destline - 1); //no breakpoints on non-executable locations
|
|
if(!scriptinternalbpget(destline)) //no breakpoint set
|
|
scriptinternalbptoggle(destline);
|
|
}
|
|
bAbort = false;
|
|
if(scriptIp)
|
|
scriptIp--;
|
|
scriptIp = scriptinternalstep(scriptIp);
|
|
bool bContinue = true;
|
|
bool bIgnoreTimeout = settingboolget("Engine", "NoScriptTimeout");
|
|
unsigned long long kernelTime, userTime;
|
|
FILETIME creationTime, exitTime; // unused
|
|
while(bContinue && !bAbort) //run loop
|
|
{
|
|
bContinue = scriptinternalcmd(silentRet);
|
|
if(scriptinternalbpget(scriptIp)) //breakpoint=stop run loop
|
|
bContinue = false;
|
|
if(bContinue && !bIgnoreTimeout && GetThreadTimes(GetCurrentThread(), &creationTime, &exitTime, reinterpret_cast<LPFILETIME>(&kernelTime), reinterpret_cast<LPFILETIME>(&userTime)) != 0)
|
|
{
|
|
if(userTime + kernelTime >= 10 * 10000000) // time out in 10 seconds of CPU time
|
|
{
|
|
if(GuiScriptMsgyn(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "The script is too busy. Would you like to terminate it now?"))) != 0)
|
|
{
|
|
dputs(QT_TRANSLATE_NOOP("DBG", "Script is terminated by user."));
|
|
break;
|
|
}
|
|
else
|
|
bIgnoreTimeout = true;
|
|
}
|
|
}
|
|
}
|
|
bIsRunning = false; //not running anymore
|
|
GuiScriptSetIp(scriptIp);
|
|
// the script fully executed (which means scriptIp is reset to the first line), without any errors
|
|
return scriptIp == scriptinternalstep(0) && (scriptLastError == STATUS_EXIT || scriptLastError == STATUS_CONTINUE);
|
|
}
|
|
|
|
bool scriptLoadSync(const char* filename)
|
|
{
|
|
GuiScriptClear();
|
|
GuiScriptEnableHighlighting(true); //enable default script syntax highlighting
|
|
scriptIp = 0;
|
|
scriptIpOld = 0;
|
|
scriptbplist.clear();
|
|
scriptstack.clear();
|
|
bAbort = false;
|
|
if(!scriptcreatelinemap(reinterpret_cast<const char*>(filename)))
|
|
return false; // Script load failed
|
|
int lines = (int)linemap.size();
|
|
const char** script = reinterpret_cast<const char**>(BridgeAlloc(lines * sizeof(const char*)));
|
|
for(int i = 0; i < lines; i++) //add script lines
|
|
script[i] = linemap.at(i).raw;
|
|
GuiScriptAdd(lines, script);
|
|
scriptIp = scriptinternalstep(0);
|
|
GuiScriptSetIp(scriptIp);
|
|
return true;
|
|
}
|
|
|
|
void scriptload(const char* filename)
|
|
{
|
|
static char filename_[MAX_PATH] = "";
|
|
strcpy_s(filename_, filename);
|
|
auto hThread = CreateThread(nullptr, 0, scriptLoadSyncThread, filename_, 0, nullptr);
|
|
while(WaitForSingleObject(hThread, 100) == WAIT_TIMEOUT)
|
|
GuiProcessEvents();
|
|
CloseHandle(hThread);
|
|
}
|
|
|
|
void scriptunload()
|
|
{
|
|
GuiScriptClear();
|
|
linemap.clear();
|
|
scriptbplist.clear();
|
|
scriptstack.clear();
|
|
scriptIp = 0;
|
|
scriptIpOld = 0;
|
|
bAbort = false;
|
|
}
|
|
|
|
void scriptrun(int destline)
|
|
{
|
|
if(DbgIsDebugging() && dbgisrunning())
|
|
{
|
|
GuiScriptError(0, GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Debugger must be paused to run a script!")));
|
|
return;
|
|
}
|
|
if(bIsRunning) //already running
|
|
return;
|
|
bIsRunning = true;
|
|
std::thread t([destline]
|
|
{
|
|
scriptRunSync(destline, false);
|
|
});
|
|
t.detach();
|
|
}
|
|
|
|
void scriptstep()
|
|
{
|
|
std::thread t([]
|
|
{
|
|
if(!bIsRunning) //only step when not running
|
|
{
|
|
scriptinternalcmd(false);
|
|
GuiScriptSetIp(scriptIp);
|
|
}
|
|
});
|
|
t.detach();
|
|
}
|
|
|
|
bool scriptbptoggle(int line)
|
|
{
|
|
if(!line || line > (int)linemap.size()) //invalid line
|
|
return false;
|
|
line = scriptinternalstep(line - 1); //no breakpoints on non-executable locations
|
|
if(scriptbpget(line)) //remove breakpoint
|
|
{
|
|
int bpcount = (int)scriptbplist.size();
|
|
for(int i = 0; i < bpcount; i++)
|
|
if(scriptbplist.at(i).line == line && !scriptbplist.at(i).silent)
|
|
{
|
|
scriptbplist.erase(scriptbplist.begin() + i);
|
|
break;
|
|
}
|
|
}
|
|
else //add breakpoint
|
|
{
|
|
SCRIPTBP newbp;
|
|
newbp.silent = false;
|
|
newbp.line = line;
|
|
scriptbplist.push_back(newbp);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool scriptbpget(int line)
|
|
{
|
|
int bpcount = (int)scriptbplist.size();
|
|
for(int i = 0; i < bpcount; i++)
|
|
if(scriptbplist.at(i).line == line && !scriptbplist.at(i).silent)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool scriptcmdexec(const char* command)
|
|
{
|
|
scriptIpOld = scriptIp;
|
|
scriptLastError = scriptinternalcmdexec(command, false);
|
|
switch(scriptLastError)
|
|
{
|
|
case STATUS_ERROR:
|
|
return false;
|
|
case STATUS_EXIT:
|
|
scriptIp = scriptinternalstep(0);
|
|
GuiScriptSetIp(scriptIp);
|
|
break;
|
|
case STATUS_PAUSE:
|
|
case STATUS_CONTINUE:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void scriptabort()
|
|
{
|
|
if(bIsRunning)
|
|
{
|
|
bAbort = true;
|
|
while(bIsRunning)
|
|
Sleep(1);
|
|
}
|
|
else //reset the script
|
|
scriptsetip(0);
|
|
}
|
|
|
|
SCRIPTLINETYPE scriptgetlinetype(int line)
|
|
{
|
|
if(line > (int)linemap.size())
|
|
return lineempty;
|
|
return linemap.at(line - 1).type;
|
|
}
|
|
|
|
void scriptsetip(int line)
|
|
{
|
|
if(line)
|
|
line--;
|
|
scriptIp = scriptinternalstep(line);
|
|
GuiScriptSetIp(scriptIp);
|
|
}
|
|
|
|
void scriptreset()
|
|
{
|
|
while(bIsRunning)
|
|
{
|
|
bAbort = true;
|
|
Sleep(1);
|
|
}
|
|
Sleep(10);
|
|
scriptsetip(0);
|
|
}
|
|
|
|
bool scriptgetbranchinfo(int line, SCRIPTBRANCH* info)
|
|
{
|
|
if(!info || !line || line > (int)linemap.size()) //invalid line
|
|
return false;
|
|
if(linemap.at(line - 1).type != linebranch) //no branch
|
|
return false;
|
|
memcpy(info, &linemap.at(line - 1).u.branch, sizeof(SCRIPTBRANCH));
|
|
return true;
|
|
}
|
|
|
|
void scriptlog(const char* msg)
|
|
{
|
|
if(!scriptLogEnabled)
|
|
return;
|
|
GuiScriptSetInfoLine(scriptIpOld, msg);
|
|
}
|