scummvm/engines/mtropolis/miniscript.cpp

2100 lines
68 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/config-manager.h"
#include "common/random.h"
#include "common/memstream.h"
#include "mtropolis/coroutines.h"
#include "mtropolis/miniscript.h"
namespace MTropolis {
class MiniscriptInstructionParserFeedback : public IMiniscriptInstructionParserFeedback {
public:
explicit MiniscriptInstructionParserFeedback(Common::Array<MiniscriptReferences::GlobalRef> *globalRefs);
uint registerGlobalGUIDIndex(uint32 guid) override;
private:
Common::Array<MiniscriptReferences::GlobalRef> *_globalRefs;
};
MiniscriptInstructionParserFeedback::MiniscriptInstructionParserFeedback(Common::Array<MiniscriptReferences::GlobalRef> *globalRefs) : _globalRefs(globalRefs) {
}
uint MiniscriptInstructionParserFeedback::registerGlobalGUIDIndex(uint32 guid) {
for (uint i = 0; i < _globalRefs->size(); i++) {
if ((*_globalRefs)[i].guid == guid)
return i;
}
uint newIndex = _globalRefs->size();
MiniscriptReferences::GlobalRef globalRef;
globalRef.guid = guid;
_globalRefs->push_back(globalRef);
return newIndex;
}
bool miniscriptEvaluateTruth(const DynamicValue &value) {
// NOTE: Comparing equal to "true" only passes for 1 exactly, but for conditions,
// any non-zero value is true.
switch (value.getType()) {
case DynamicValueTypes::kBoolean:
return value.getBool();
case DynamicValueTypes::kInteger:
return (value.getInt() != 0);
case DynamicValueTypes::kFloat:
return !(value.getFloat() == 0.0);
case DynamicValueTypes::kObject:
return !value.getObject().object.expired();
default:
return false;
}
}
IMiniscriptInstructionParserFeedback::~IMiniscriptInstructionParserFeedback() {
}
MiniscriptInstruction::~MiniscriptInstruction() {
}
MiniscriptReferences::LocalRef::LocalRef() : guid(0) {
}
MiniscriptReferences::GlobalRef::GlobalRef() : guid(0) {
}
MiniscriptReferences::MiniscriptReferences(const Common::Array<LocalRef> &localRefs, const Common::Array<GlobalRef> &globalRefs) : _localRefs(localRefs), _globalRefs(globalRefs) {
}
void MiniscriptReferences::linkInternalReferences(ObjectLinkingScope *scope) {
// Resolve using name lookups since there are some known cases where the GUID is broken
// e.g. "bArriveFromCutScene" in "Set bArriveFromCutScene on PE" in Obsidian
for (Common::Array<LocalRef>::iterator it = _localRefs.begin(), itEnd = _localRefs.end(); it != itEnd; ++it)
it->resolution = scope->resolve(it->guid, it->name, false);
for (Common::Array<GlobalRef>::iterator it = _globalRefs.begin(), itEnd = _globalRefs.end(); it != itEnd; ++it)
it->resolution = scope->resolve(it->guid, "", true);
}
void MiniscriptReferences::visitInternalReferences(IStructuralReferenceVisitor *visitor) {
for (LocalRef &ref : _localRefs) {
Common::SharedPtr<RuntimeObject> obj = ref.resolution.lock();
if (obj) {
if (obj->isModifier()) {
Common::WeakPtr<Modifier> mod = obj.staticCast<Modifier>();
visitor->visitWeakModifierRef(mod);
ref.resolution = mod;
} else if (obj->isStructural()) {
Common::WeakPtr<Structural> struc = obj.staticCast<Structural>();
visitor->visitWeakStructuralRef(struc);
ref.resolution = struc;
}
}
}
for (GlobalRef &ref : _globalRefs) {
Common::SharedPtr<RuntimeObject> obj = ref.resolution.lock();
if (obj) {
if (obj->isModifier()) {
Common::WeakPtr<Modifier> mod = obj.staticCast<Modifier>();
visitor->visitWeakModifierRef(mod);
ref.resolution = mod;
} else if (obj->isStructural()) {
Common::WeakPtr<Structural> struc = obj.staticCast<Structural>();
visitor->visitWeakStructuralRef(struc);
ref.resolution = struc;
}
}
}
}
Common::WeakPtr<RuntimeObject> MiniscriptReferences::getRefByIndex(uint index) const {
if (index >= _localRefs.size())
return Common::WeakPtr<RuntimeObject>();
return _localRefs[index].resolution;
}
Common::WeakPtr<RuntimeObject> MiniscriptReferences::getGlobalRefByIndex(uint index) const {
if (index >= _globalRefs.size())
return Common::WeakPtr<RuntimeObject>();
return _globalRefs[index].resolution;
}
MiniscriptProgram::MiniscriptProgram(const Common::SharedPtr<Common::Array<uint8> > &programData, const Common::Array<MiniscriptInstruction *> &instructions, const Common::Array<Attribute> &attributes)
: _programData(programData), _instructions(instructions), _attributes(attributes) {
}
MiniscriptProgram::~MiniscriptProgram() {
// Destruct all instructions
for (Common::Array<MiniscriptInstruction *>::const_iterator it = _instructions.begin(), itEnd = _instructions.end(); it != itEnd; ++it)
(*it)->~MiniscriptInstruction();
}
const Common::Array<MiniscriptInstruction *> &MiniscriptProgram::getInstructions() const {
return _instructions;
}
const Common::Array<MiniscriptProgram::Attribute> &MiniscriptProgram::getAttributes() const {
return _attributes;
}
template<class T>
struct MiniscriptInstructionLoader {
static bool loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback);
};
template<class T>
bool MiniscriptInstructionLoader<T>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
// Default loader for simple instructions with no private data
new (dest) T();
return true;
}
template<>
bool MiniscriptInstructionLoader<MiniscriptInstructions::Send>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
Data::Event dataEvent;
if (!dataEvent.load(instrDataReader))
return false;
Event evt;
if (!evt.load(dataEvent))
return false;
MessageFlags msgFlags;
msgFlags.immediate = ((instrFlags & 0x04) == 0);
msgFlags.cascade = ((instrFlags & 0x08) == 0);
msgFlags.relay = ((instrFlags & 0x10) == 0);
new (dest) MiniscriptInstructions::Send(evt, msgFlags);
return true;
}
template<>
bool MiniscriptInstructionLoader<MiniscriptInstructions::BuiltinFunc>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
uint32 functionID;
if (!instrDataReader.readU32(functionID))
return false;
if (functionID < 1 || functionID > 20)
return false; // Unknown function
new (dest) MiniscriptInstructions::BuiltinFunc(static_cast<MiniscriptInstructions::BuiltinFunc::BuiltinFunctionID>(functionID));
return true;
}
template<>
bool MiniscriptInstructionLoader<MiniscriptInstructions::GetChild>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
uint32 childAttribute;
if (!instrDataReader.readU32(childAttribute))
return false;
new (dest) MiniscriptInstructions::GetChild(childAttribute, (instrFlags & 1) != 0, (instrFlags & 32) != 0);
return true;
}
template<>
bool MiniscriptInstructionLoader<MiniscriptInstructions::PushGlobal>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
uint32 globalID;
if (!instrDataReader.readU32(globalID))
return false;
new (dest) MiniscriptInstructions::PushGlobal(globalID, (instrFlags & 1) != 0);
return true;
}
template<>
bool MiniscriptInstructionLoader<MiniscriptInstructions::Jump>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
uint32 jumpFlags, unknown, instrOffset;
if (!instrDataReader.readU32(jumpFlags) || !instrDataReader.readU32(unknown) || !instrDataReader.readU32(instrOffset))
return false;
bool isConditional = (jumpFlags == 2);
if (jumpFlags != 1 && jumpFlags != 2)
return false; // Don't recognize this flag combination
if (instrOffset == 0)
return false; // Not valid
new (dest) MiniscriptInstructions::Jump(instrOffset, isConditional);
return true;
}
template<>
bool MiniscriptInstructionLoader<MiniscriptInstructions::PushValue>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
uint16 dataType;
if (!instrDataReader.readU16(dataType))
return false;
if (dataType == 0)
new (dest) MiniscriptInstructions::PushValue(MiniscriptInstructions::PushValue::kDataTypeNull, nullptr, false);
else if (dataType == 0x15) {
Common::XPFloat f;
if (!instrDataReader.readPlatformFloat(f))
return false;
double d = f.toDouble();
new (dest) MiniscriptInstructions::PushValue(MiniscriptInstructions::PushValue::kDataTypeDouble, &d, false);
} else if (dataType == 0x1a) {
uint8 boolValue;
if (!instrDataReader.readU8(boolValue))
return false;
bool b = (boolValue != 0);
new (dest) MiniscriptInstructions::PushValue(MiniscriptInstructions::PushValue::kDataTypeBool, &b, false);
} else if (dataType == 0x1f9) {
uint32 refValue;
if (!instrDataReader.readU32(refValue))
return false;
new (dest) MiniscriptInstructions::PushValue(MiniscriptInstructions::PushValue::kDataTypeLocalRef, &refValue, (instrFlags & 1) != 0);
} else if (dataType == 0x1fa) {
uint32 refValue;
if (!instrDataReader.readU32(refValue))
return false;
uint32 indexedRef = feedback.registerGlobalGUIDIndex(refValue);
new (dest) MiniscriptInstructions::PushValue(MiniscriptInstructions::PushValue::kDataTypeGlobalRef, &indexedRef, (instrFlags & 1) != 0);
} else if (dataType == 0x1d) {
MiniscriptInstructions::PushValue::Label label;
if (!instrDataReader.readU32(label.superGroup) || !instrDataReader.readU32(label.id))
return false;
new (dest) MiniscriptInstructions::PushValue(MiniscriptInstructions::PushValue::kDataTypeLabel, &label, false);
} else
return false;
return true;
}
template<>
bool MiniscriptInstructionLoader<MiniscriptInstructions::PushString>::loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback) {
uint16 strLength;
if (!instrDataReader.readU16(strLength))
return false;
// Unlike most cases, in this case the string is null-terminated but the str length doesn't include the terminator
Common::String str;
if (!instrDataReader.readTerminatedStr(str, strLength + 1))
return false;
new (dest) MiniscriptInstructions::PushString(str);
return true;
}
struct SIMiniscriptInstructionFactory {
bool (*create)(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, MiniscriptInstruction *&outMiniscriptInstructionPtr, IMiniscriptInstructionParserFeedback &feedback);
void (*getSizeAndAlignment)(size_t &outSize, size_t &outAlignment);
};
template<class T>
class MiniscriptInstructionFactory {
public:
static bool create(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, MiniscriptInstruction *&outMiniscriptInstructionPtr, IMiniscriptInstructionParserFeedback &feedback);
static void getSizeAndAlignment(size_t &outSize, size_t &outAlignment);
static SIMiniscriptInstructionFactory *getInstance();
private:
static SIMiniscriptInstructionFactory _instance;
};
template<class T>
bool MiniscriptInstructionFactory<T>::create(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, MiniscriptInstruction *&outMiniscriptInstructionPtr, IMiniscriptInstructionParserFeedback &feedback) {
if (!MiniscriptInstructionLoader<T>::loadInstruction(dest, instrFlags, instrDataReader, feedback))
return false;
outMiniscriptInstructionPtr = static_cast<MiniscriptInstruction *>(static_cast<T *>(dest));
return true;
}
template<class T>
void MiniscriptInstructionFactory<T>::getSizeAndAlignment(size_t &outSize, size_t &outAlignment) {
outSize = sizeof(T);
outAlignment = alignof(T);
}
template<class T>
inline SIMiniscriptInstructionFactory *MiniscriptInstructionFactory<T>::getInstance() {
return &_instance;
}
template<class T>
SIMiniscriptInstructionFactory MiniscriptInstructionFactory<T>::_instance = {
MiniscriptInstructionFactory<T>::create,
MiniscriptInstructionFactory<T>::getSizeAndAlignment
};
MiniscriptParser::InstructionData::InstructionData()
: opcode(0), flags(0), pdPosition(0), instrFactory(nullptr) {
}
bool MiniscriptParser::parse(const Data::MiniscriptProgram &program, Common::SharedPtr<MiniscriptProgram> &outProgram, Common::SharedPtr<MiniscriptReferences> &outReferences) {
Common::Array<MiniscriptReferences::LocalRef> localRefs;
Common::Array<MiniscriptReferences::GlobalRef> globalRefs;
Common::Array<MiniscriptProgram::Attribute> attributes;
Common::SharedPtr<Common::Array<uint8> > programDataPtr;
Common::Array<MiniscriptInstruction *> miniscriptInstructions;
// If the program is empty then just return an empty program
if (program.bytecode.size() == 0 || program.numOfInstructions == 0) {
outProgram = Common::SharedPtr<MiniscriptProgram>(new MiniscriptProgram(programDataPtr, miniscriptInstructions, attributes));
outReferences = Common::SharedPtr<MiniscriptReferences>(new MiniscriptReferences(localRefs, globalRefs));
return true;
}
localRefs.resize(program.localRefs.size());
for (size_t i = 0; i < program.localRefs.size(); i++) {
localRefs[i].guid = program.localRefs[i].guid;
localRefs[i].name = program.localRefs[i].name;
}
attributes.resize(program.attributes.size());
for (size_t i = 0; i < program.attributes.size(); i++) {
attributes[i].name = program.attributes[i].name;
}
Common::MemoryReadStream stream(&program.bytecode[0], program.bytecode.size());
Data::DataReader reader(0, stream, program.dataFormat, kRuntimeVersion100, false);
Common::Array<InstructionData> rawInstructions;
rawInstructions.resize(program.numOfInstructions);
for (size_t i = 0; i < program.numOfInstructions; i++) {
InstructionData &rawInstruction = rawInstructions[i];
uint16 instrSize;
if (!reader.readU16(rawInstruction.opcode) || !reader.readU16(rawInstruction.flags) || !reader.readU16(instrSize))
return false;
if (instrSize < 6)
return false;
if (instrSize > 6) {
rawInstruction.contents.resize(instrSize - 6);
if (!reader.read(&rawInstruction.contents[0], instrSize - 6))
return false;
}
}
programDataPtr.reset(new Common::Array<uint8>());
Common::Array<uint8> &programData = *programDataPtr.get();
// Find out how much space we need and place instructions
size_t maxAlignment = 1;
size_t programDataSize = 0;
for (size_t i = 0; i < program.numOfInstructions; i++) {
InstructionData &rawInstruction = rawInstructions[i];
SIMiniscriptInstructionFactory *factory = resolveOpcode(rawInstruction.opcode);
rawInstruction.instrFactory = factory;
if (!factory)
return false;
size_t compiledSize = 0;
size_t compiledAlignment = 0;
factory->getSizeAndAlignment(compiledSize, compiledAlignment);
if (programDataSize % compiledAlignment != 0)
programDataSize += (compiledAlignment - (programDataSize % compiledAlignment));
rawInstruction.pdPosition = programDataSize;
programDataSize += compiledSize;
if (maxAlignment < compiledAlignment)
maxAlignment = compiledAlignment;
}
programData.resize(programDataSize + maxAlignment - 1);
uintptr programDataAddress = reinterpret_cast<uintptr>(&programData[0]);
size_t baseOffset = 0;
if (programDataAddress % maxAlignment != 0)
baseOffset = (maxAlignment - (programDataSize % maxAlignment));
miniscriptInstructions.resize(program.numOfInstructions);
MiniscriptInstructionParserFeedback parserFeedback(&globalRefs);
// Create instructions
for (size_t i = 0; i < program.numOfInstructions; i++) {
const InstructionData &rawInstruction = rawInstructions[i];
const void *dataLoc = nullptr;
if (rawInstruction.contents.size() != 0)
dataLoc = &rawInstruction.contents[0];
Common::MemoryReadStream instrContentsStream(static_cast<const byte *>(dataLoc), rawInstruction.contents.size());
Data::DataReader instrContentsReader(0, instrContentsStream, reader.getDataFormat(), reader.getRuntimeVersion(), reader.isVersionAutoDetect());
if (!rawInstruction.instrFactory->create(&programData[baseOffset + rawInstruction.pdPosition], rawInstruction.flags, instrContentsReader, miniscriptInstructions[i], parserFeedback)) {
// Destroy any already-created instructions
for (size_t di = 0; di < i; di++) {
miniscriptInstructions[i - 1 - di]->~MiniscriptInstruction();
}
return false;
}
}
// Done
outProgram = Common::SharedPtr<MiniscriptProgram>(new MiniscriptProgram(programDataPtr, miniscriptInstructions, attributes));
outReferences = Common::SharedPtr<MiniscriptReferences>(new MiniscriptReferences(localRefs, globalRefs));
return true;
}
SIMiniscriptInstructionFactory *MiniscriptParser::resolveOpcode(uint16 opcode) {
switch (opcode) {
case 0x834:
return MiniscriptInstructionFactory<MiniscriptInstructions::Set>::getInstance();
case 0x898:
return MiniscriptInstructionFactory<MiniscriptInstructions::Send>::getInstance();
case 0xc9:
return MiniscriptInstructionFactory<MiniscriptInstructions::Add>::getInstance();
case 0xca:
return MiniscriptInstructionFactory<MiniscriptInstructions::Sub>::getInstance();
case 0xcb:
return MiniscriptInstructionFactory<MiniscriptInstructions::Mul>::getInstance();
case 0xcc:
return MiniscriptInstructionFactory<MiniscriptInstructions::Div>::getInstance();
case 0xcd:
return MiniscriptInstructionFactory<MiniscriptInstructions::Pow>::getInstance();
case 0xce:
return MiniscriptInstructionFactory<MiniscriptInstructions::And>::getInstance();
case 0xcf:
return MiniscriptInstructionFactory<MiniscriptInstructions::Or>::getInstance();
case 0xd0:
return MiniscriptInstructionFactory<MiniscriptInstructions::Neg>::getInstance();
case 0xd1:
return MiniscriptInstructionFactory<MiniscriptInstructions::Not>::getInstance();
case 0xd2:
return MiniscriptInstructionFactory<MiniscriptInstructions::CmpEqual>::getInstance();
case 0xd3:
return MiniscriptInstructionFactory<MiniscriptInstructions::CmpNotEqual>::getInstance();
case 0xd4:
return MiniscriptInstructionFactory<MiniscriptInstructions::CmpLessOrEqual>::getInstance();
case 0xd5:
return MiniscriptInstructionFactory<MiniscriptInstructions::CmpLess>::getInstance();
case 0xd6:
return MiniscriptInstructionFactory<MiniscriptInstructions::CmpGreaterOrEqual>::getInstance();
case 0xd7:
return MiniscriptInstructionFactory<MiniscriptInstructions::CmpGreater>::getInstance();
case 0xd8:
return MiniscriptInstructionFactory<MiniscriptInstructions::BuiltinFunc>::getInstance();
case 0xd9:
return MiniscriptInstructionFactory<MiniscriptInstructions::DivInt>::getInstance();
case 0xda:
return MiniscriptInstructionFactory<MiniscriptInstructions::Modulo>::getInstance();
case 0xdb:
return MiniscriptInstructionFactory<MiniscriptInstructions::StrConcat>::getInstance();
case 0x12f:
return MiniscriptInstructionFactory<MiniscriptInstructions::PointCreate>::getInstance();
case 0x130:
return MiniscriptInstructionFactory<MiniscriptInstructions::RangeCreate>::getInstance();
case 0x131:
return MiniscriptInstructionFactory<MiniscriptInstructions::VectorCreate>::getInstance();
case 0x135:
return MiniscriptInstructionFactory<MiniscriptInstructions::GetChild>::getInstance();
case 0x136:
return MiniscriptInstructionFactory<MiniscriptInstructions::ListAppend>::getInstance();
case 0x137:
return MiniscriptInstructionFactory<MiniscriptInstructions::ListCreate>::getInstance();
case 0x191:
return MiniscriptInstructionFactory<MiniscriptInstructions::PushValue>::getInstance();
case 0x192:
return MiniscriptInstructionFactory<MiniscriptInstructions::PushGlobal>::getInstance();
case 0x193:
return MiniscriptInstructionFactory<MiniscriptInstructions::PushString>::getInstance();
case 0x7d3:
return MiniscriptInstructionFactory<MiniscriptInstructions::Jump>::getInstance();
default:
return nullptr;
}
}
namespace MiniscriptInstructions {
MiniscriptInstructionOutcome UnimplementedInstruction::execute(MiniscriptThread *thread) const {
thread->error("Unimplemented instruction");
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptInstructionOutcome Set::execute(MiniscriptThread *thread) const {
if (thread->getStackSize() != 2) {
// Sets are only allowed when they would empty the stack
thread->error("Invalid stack state for set instruction");
return kMiniscriptInstructionOutcomeFailed;
}
// Convert value
MiniscriptInstructionOutcome outcome = kMiniscriptInstructionOutcomeContinue;
const MiniscriptStackValue &srcValue = thread->getStackValueFromTop(0);
MiniscriptStackValue &target = thread->getStackValueFromTop(1);
if (target.value.getType() == DynamicValueTypes::kWriteProxy) {
const DynamicValueWriteProxy &proxy = target.value.getWriteProxy();
outcome = proxy.pod.ifc->write(thread, srcValue.value, proxy.pod.objectRef, proxy.pod.ptrOrOffset);
if (outcome == kMiniscriptInstructionOutcomeFailed) {
thread->error("Failed to assign value to proxy");
return outcome;
}
} else {
VariableModifier *var = nullptr;
if (target.value.getType() == DynamicValueTypes::kObject) {
Common::SharedPtr<RuntimeObject> obj = target.value.getObject().object.lock();
if (obj && obj->isModifier() && static_cast<const Modifier *>(obj.get())->isVariable())
var = static_cast<VariableModifier *>(obj.get());
}
if (var != nullptr) {
(void)var->varSetValue(thread, srcValue.value);
} else {
thread->error("Can't assign to rvalue");
return kMiniscriptInstructionOutcomeFailed;
}
}
thread->popValues(2);
return outcome;
}
Send::Send(const Event &evt, const MessageFlags &messageFlags) : _evt(evt), _messageFlags(messageFlags) {
}
MiniscriptInstructionOutcome Send::execute(MiniscriptThread *thread) const {
if (thread->getStackSize() != 2) {
// Send instructions are only allowed if they empty the stack
thread->error("Invalid stack state for send instruction");
return kMiniscriptInstructionOutcomeFailed;
}
DynamicValue &targetValue = thread->getStackValueFromTop(0).value;
DynamicValue &payloadValue = thread->getStackValueFromTop(1).value;
if (targetValue.getType() != DynamicValueTypes::kObject) {
// Failed sends are non-fatal (Obsidian requires this in the aircraft propulsion room to enter the propulsion puzzle)
warning("Invalid message destination (target isn't an object reference)");
thread->popValues(2);
return kMiniscriptInstructionOutcomeContinue;
}
Common::SharedPtr<RuntimeObject> obj = targetValue.getObject().object.lock();
if (!obj) {
// Obsidian also triggers NAV_Restart on Project Started, which triggers "<init globals> on NAV_Restart"
// which sends PRG_Toggle_Status_Display to sharedScene, even though at that point there is no shared scene.
warning("Invalid message destination (target object is invalid)");
thread->popValues(2);
return kMiniscriptInstructionOutcomeContinue;
}
Common::SharedPtr<MessageProperties> msgProps(new MessageProperties(_evt, payloadValue, thread->getModifier()->getSelfReference()));
Common::SharedPtr<MessageDispatch> dispatch;
if (obj->isModifier())
dispatch.reset(new MessageDispatch(msgProps, static_cast<Modifier *>(obj.get()), _messageFlags.cascade, _messageFlags.relay, true));
else if (obj->isStructural())
dispatch.reset(new MessageDispatch(msgProps, static_cast<Structural *>(obj.get()), _messageFlags.cascade, _messageFlags.relay, true));
else {
warning("Invalid message destination (target object is not a modifier or structural object)");
return kMiniscriptInstructionOutcomeContinue;
}
thread->popValues(2);
if (_messageFlags.immediate) {
thread->getRuntime()->sendMessageOnVThread(dispatch);
return kMiniscriptInstructionOutcomeYieldToVThread;
} else {
thread->getRuntime()->queueMessage(dispatch);
return kMiniscriptInstructionOutcomeContinue;
}
}
MiniscriptInstructionOutcome BinaryArithInstruction::execute(MiniscriptThread *thread) const {
if (thread->getStackSize() < 2) {
thread->error("Stack underflow");
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
outcome = thread->dereferenceRValue(1);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
DynamicValue &rs = thread->getStackValueFromTop(0).value;
DynamicValue &lsDest = thread->getStackValueFromTop(1).value;
if (lsDest.getType() == DynamicValueTypes::kPoint && rs.getType() == DynamicValueTypes::kPoint) {
Common::Point lsPoint = lsDest.getPoint();
Common::Point rsPoint = rs.getPoint();
double resultX = 0.0;
double resultY = 0.0;
outcome = arithExecute(thread, resultX, lsPoint.x, rsPoint.x);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
outcome = arithExecute(thread, resultY, lsPoint.y, rsPoint.y);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
lsDest.setPoint(Common::Point(static_cast<int16>(round(resultX)), static_cast<int16>(round(resultY))));
} else {
double leftVal = 0.0;
switch (lsDest.getType()) {
case DynamicValueTypes::kInteger:
leftVal = lsDest.getInt();
break;
case DynamicValueTypes::kFloat:
leftVal = lsDest.getFloat();
break;
case DynamicValueTypes::kBoolean:
leftVal = lsDest.getBool() ? 1.0 : 0.0;
break;
default:
thread->error("Invalid left-side type for binary arithmetic operator");
return kMiniscriptInstructionOutcomeFailed;
}
double rightVal = 0.0;
switch (rs.getType()) {
case DynamicValueTypes::kInteger:
rightVal = rs.getInt();
break;
case DynamicValueTypes::kFloat:
rightVal = rs.getFloat();
break;
case DynamicValueTypes::kBoolean:
rightVal = rs.getBool() ? 1.0 : 0.0;
break;
default:
thread->error("Invalid right-side type for binary arithmetic operator");
return kMiniscriptInstructionOutcomeFailed;
}
double result = 0.0;
outcome = arithExecute(thread, result, leftVal, rightVal);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
lsDest.setFloat(result);
}
thread->popValues(1);
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome Add::arithExecute(MiniscriptThread *thread, double &result, double left, double right) const {
result = left + right;
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome Sub::arithExecute(MiniscriptThread *thread, double &result, double left, double right) const {
result = left - right;
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome Mul::arithExecute(MiniscriptThread *thread, double &result, double left, double right) const {
result = left * right;
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome Div::arithExecute(MiniscriptThread *thread, double &result, double left, double right) const {
if (right == 0.0) {
thread->error("Arithmetic error: Division by zero");
return kMiniscriptInstructionOutcomeFailed;
}
result = left / right;
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome Pow::arithExecute(MiniscriptThread *thread, double &result, double left, double right) const {
if (left < 0.0 && right != floor(right)) {
thread->error("Arithmetic error: Left side is negative but right side is not an integer");
return kMiniscriptInstructionOutcomeFailed;
}
result = pow(left, right);
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome DivInt::arithExecute(MiniscriptThread *thread, double &result, double left, double right) const {
if (right == 0.0) {
thread->error("Arithmetic error: Integer division by zero");
return kMiniscriptInstructionOutcomeFailed;
}
result = floor(left / right);
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome Modulo::arithExecute(MiniscriptThread *thread, double &result, double left, double right) const {
if (right == 0.0) {
thread->error("Arithmetic error: Modulo division by zero");
return kMiniscriptInstructionOutcomeFailed;
}
// fmod keeps the sign from the left operand, but mTropolis modulo keeps the
// sign of the right operand.
double r = fmod(left, right);
if (signbit(left) != signbit(right)) {
if (r == 0.0)
r = copysign(0.0, right);
else
r += right;
}
result = r;
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome UnorderedCompareInstruction::execute(MiniscriptThread *thread) const {
if (thread->getStackSize() < 2) {
thread->error("Stack underflow");
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
outcome = thread->dereferenceRValue(1);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
DynamicValue &rs = thread->getStackValueFromTop(0).value;
DynamicValue &lsDest = thread->getStackValueFromTop(1).value;
bool isEqual = false;
bool isUndefined = false;
switch (lsDest.getType()) {
case DynamicValueTypes::kString:
if (rs.getType() == DynamicValueTypes::kString)
isEqual = caseInsensitiveEqual(lsDest.getString(), rs.getString());
break;
case DynamicValueTypes::kBoolean: {
switch (rs.getType()) {
case DynamicValueTypes::kInteger:
isEqual = (rs.getInt() == (lsDest.getBool() ? 1 : 0));
break;
case DynamicValueTypes::kFloat:
isEqual = (rs.getFloat() == (lsDest.getBool() ? 1.0 : 0.0));
break;
case DynamicValueTypes::kBoolean:
isEqual = (rs.getBool() == lsDest.getBool());
break;
default:
isEqual = (lsDest.getBool() == false);
break;
}
} break;
case DynamicValueTypes::kFloat: {
if (isnan(lsDest.getFloat()))
isUndefined = true;
else {
switch (rs.getType()) {
case DynamicValueTypes::kInteger:
isEqual = (rs.getInt() == lsDest.getFloat());
break;
case DynamicValueTypes::kFloat:
if (isnan(rs.getFloat()))
isUndefined = true;
else
isEqual = (rs.getFloat() == lsDest.getFloat());
break;
case DynamicValueTypes::kBoolean:
isEqual = ((rs.getBool() ? 1.0 : 0.0) == lsDest.getFloat());
break;
default:
isEqual = false;
break;
}
}
} break;
case DynamicValueTypes::kInteger: {
switch (rs.getType()) {
case DynamicValueTypes::kInteger:
isEqual = (rs.getInt() == lsDest.getInt());
break;
case DynamicValueTypes::kFloat:
if (isnan(rs.getFloat()))
isUndefined = true;
else
isEqual = (rs.getFloat() == lsDest.getInt());
break;
case DynamicValueTypes::kBoolean:
isEqual = ((rs.getBool() ? 1 : 0) == lsDest.getInt());
break;
default:
isEqual = false;
break;
}
} break;
case DynamicValueTypes::kLabel: {
// Really not sure how this works but there are buggy scripts in Obsidian which
// were probably written as "if loop = false then ..." except Miniscript resolved
// "loop" as a sound marker (!) because a sound marker with that name exists instead
// of resolving it as equivalent to element.loop as it usually would.
// Strict equality checks prevent the "GEN_Streaming_Update on ALC" script from
// working, which prevents the bqtstreaming and bstreaming flags from being cleared,
// causing, among other things, the player to get stuck after the Forest->Bureau
// transition because the stuck streaming flags are blocking the VO.
if (rs.getType() == DynamicValueTypes::kBoolean)
isEqual = !rs.getBool();
else
isEqual = (lsDest == rs);
} break;
default:
isEqual = (lsDest == rs);
break;
}
lsDest.setBool(isUndefined == false && this->resolve(isEqual));
thread->popValues(1);
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome And::execute(MiniscriptThread *thread) const {
if (thread->getStackSize() < 2) {
thread->error("Stack underflow");
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
outcome = thread->dereferenceRValue(1);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
DynamicValue &rs = thread->getStackValueFromTop(0).value;
DynamicValue &lsDest = thread->getStackValueFromTop(1).value;
lsDest.setBool(miniscriptEvaluateTruth(lsDest) && miniscriptEvaluateTruth(rs));
thread->popValues(1);
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome Or::execute(MiniscriptThread *thread) const {
if (thread->getStackSize() < 2) {
thread->error("Stack underflow");
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
outcome = thread->dereferenceRValue(1);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
DynamicValue &rs = thread->getStackValueFromTop(0).value;
DynamicValue &lsDest = thread->getStackValueFromTop(1).value;
lsDest.setBool(miniscriptEvaluateTruth(lsDest) || miniscriptEvaluateTruth(rs));
thread->popValues(1);
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome Neg::execute(MiniscriptThread *thread) const {
if (thread->getStackSize() < 1) {
thread->error("Stack underflow");
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
DynamicValue &value = thread->getStackValueFromTop(0).value;
switch (value.getType()) {
case DynamicValueTypes::kFloat:
value.setFloat(-value.getFloat());
break;
case DynamicValueTypes::kInteger: {
int32 i = value.getInt();
if (i == (0 - 1 -0x7fffffff))
value.setFloat(-static_cast<double>(i));
else
value.setInt(-i);
} break;
default:
thread->error("Couldn't negate a value of a non-numeric type");
return kMiniscriptInstructionOutcomeFailed;
}
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome Not::execute(MiniscriptThread *thread) const {
if (thread->getStackSize() < 1) {
thread->error("Stack underflow");
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
DynamicValue &value = thread->getStackValueFromTop(0).value;
value.setBool(!miniscriptEvaluateTruth(value));
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome OrderedCompareInstruction::execute(MiniscriptThread *thread) const {
if (thread->getStackSize() < 2) {
thread->error("Stack underflow");
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
outcome = thread->dereferenceRValue(1);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
DynamicValue &rs = thread->getStackValueFromTop(0).value;
DynamicValue &lsDest = thread->getStackValueFromTop(1).value;
double leftValue = 0.0;
double rightValue = 0.0;
if (lsDest.getType() == DynamicValueTypes::kFloat)
leftValue = lsDest.getFloat();
else if (lsDest.getType() == DynamicValueTypes::kInteger)
leftValue = lsDest.getInt();
else {
thread->error("Left-side value is invalid for comparison");
return kMiniscriptInstructionOutcomeFailed;
}
if (rs.getType() == DynamicValueTypes::kFloat)
rightValue = rs.getFloat();
else if (rs.getType() == DynamicValueTypes::kInteger)
rightValue = rs.getInt();
else {
thread->error("Right-side value is invalid for comparison");
return kMiniscriptInstructionOutcomeFailed;
}
lsDest.setBool(this->compareFloat(leftValue, rightValue));
thread->popValues(1);
return kMiniscriptInstructionOutcomeContinue;
}
BuiltinFunc::BuiltinFunc(BuiltinFunctionID bfid) : _funcID(bfid) {
}
MiniscriptInstructionOutcome BuiltinFunc::execute(MiniscriptThread *thread) const {
size_t stackArgsNeeded = 1;
bool returnsValue = true;
if (thread->getStackSize() < stackArgsNeeded) {
thread->error("Stack underflow");
return kMiniscriptInstructionOutcomeFailed;
}
for (size_t i = 0; i < stackArgsNeeded; i++) {
MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(i);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
}
DynamicValue staticDest;
DynamicValue *dest = nullptr;
if (returnsValue) {
if (stackArgsNeeded > 0)
dest = &thread->getStackValueFromTop(stackArgsNeeded - 1).value;
else
dest = &staticDest;
}
MiniscriptInstructionOutcome outcome = executeFunction(thread, dest);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
if (stackArgsNeeded > 0) {
size_t valuesToPop = stackArgsNeeded;
if (returnsValue)
valuesToPop--;
if (valuesToPop > 0) {
// coverity[dead_error_line]
thread->popValues(valuesToPop);
}
} else {
// coverity[dead_error_line]
if (returnsValue)
thread->pushValue(staticDest);
}
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome BuiltinFunc::executeFunction(MiniscriptThread *thread, DynamicValue *returnValue) const {
switch (_funcID) {
case kSin:
case kCos:
case kRandom:
case kSqrt:
case kTan:
case kAbs:
case kSign:
case kArctangent:
case kExp:
case kLn:
case kLog:
case kCosH:
case kSinH:
case kTanH:
case kTrunc:
case kRound:
return executeSimpleNumericInstruction(thread, returnValue);
case kRect2Polar:
return executeRectToPolar(thread, returnValue);
case kPolar2Rect:
return executePolarToRect(thread, returnValue);
case kNum2Str:
return executeNum2Str(thread, returnValue);
case kStr2Num:
return executeStr2Num(thread, returnValue);
default:
thread->error("Unimplemented built-in function");
return kMiniscriptInstructionOutcomeFailed;
}
}
MiniscriptInstructionOutcome BuiltinFunc::executeSimpleNumericInstruction(MiniscriptThread *thread, DynamicValue *returnValue) const {
double result = 0.0;
double input = 0.0;
const DynamicValue &inputDynamicValue = thread->getStackValueFromTop(0).value;
switch (inputDynamicValue.getType()) {
case DynamicValueTypes::kInteger:
input = inputDynamicValue.getInt();
break;
case DynamicValueTypes::kFloat:
input = inputDynamicValue.getFloat();
break;
default:
thread->error("Invalid numeric function input type");
return kMiniscriptInstructionOutcomeFailed;
}
switch (_funcID) {
case kSin:
result = sin(input * (M_PI / 180.0));
break;
case kCos:
result = cos(input * (M_PI / 180.0));
break;
case kRandom:
if (input < 1.5)
result = 0.0;
else {
uint rngMax = static_cast<uint>(floor(input + 0.5)) - 1;
result = thread->getRuntime()->getRandom()->getRandomNumber(rngMax);
}
break;
case kSqrt:
result = sqrt(input);
break;
case kTan:
result = tan(input * (M_PI / 180.0));
break;
case kAbs:
result = fabs(input);
break;
case kSign:
if (input < 0.0)
result = -1;
else if (input > 0.0)
result = 1;
else
result = 0;
break;
case kArctangent:
result = atan(input) * (180.0 / M_PI);
break;
case kExp:
result = exp(input);
break;
case kLn:
result = log(input);
break;
case kLog:
result = log10(input);
break;
case kCosH:
result = cosh(input * (M_PI / 180.0));
break;
case kSinH:
result = sinh(input * (M_PI / 180.0));
break;
case kTanH:
result = tanh(input * (M_PI / 180.0));
break;
case kTrunc:
result = trunc(input);
break;
case kRound:
result = round(input);
break;
default:
thread->error("Unimplemented numeric function");
return kMiniscriptInstructionOutcomeFailed;
}
returnValue->setFloat(result);
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome BuiltinFunc::executeRectToPolar(MiniscriptThread *thread, DynamicValue *returnValue) const {
const DynamicValue &inputDynamicValue = thread->getStackValueFromTop(0).value;
if (inputDynamicValue.getType() != DynamicValueTypes::kPoint) {
thread->error("Polar to rect input must be a vector");
return kMiniscriptInstructionOutcomeFailed;
}
Common::Point pt = inputDynamicValue.getPoint();
double angle = atan2(pt.y, pt.x);
double magnitude = sqrt(pt.x * pt.x + pt.y * pt.y);
returnValue->setVector(AngleMagVector::createRadians(angle, magnitude));
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome BuiltinFunc::executePolarToRect(MiniscriptThread *thread, DynamicValue *returnValue) const {
const DynamicValue &inputDynamicValue = thread->getStackValueFromTop(0).value;
if (inputDynamicValue.getType() != DynamicValueTypes::kVector) {
thread->error("Polar to rect input must be a vector");
return kMiniscriptInstructionOutcomeFailed;
}
const AngleMagVector &vec = inputDynamicValue.getVector();
double x = cos(vec.angleDegrees * (M_PI / 180.0)) * vec.magnitude;
double y = sin(vec.angleDegrees * (M_PI / 180.0)) * vec.magnitude;
returnValue->setPoint(Common::Point(static_cast<int16>(round(x)), static_cast<int16>(round(y))));
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome BuiltinFunc::executeNum2Str(MiniscriptThread *thread, DynamicValue *returnValue) const {
Common::String result;
const DynamicValue &inputDynamicValue = thread->getStackValueFromTop(0).value;
switch (inputDynamicValue.getType()) {
case DynamicValueTypes::kInteger:
result = Common::String::format("%i", static_cast<int>(inputDynamicValue.getInt()));
break;
case DynamicValueTypes::kFloat:
result = Common::String::format("%g", static_cast<double>(inputDynamicValue.getFloat()));
break;
default:
thread->error("Invalid input value to num2str");
return kMiniscriptInstructionOutcomeFailed;
}
returnValue->setString(result);
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome BuiltinFunc::executeStr2Num(MiniscriptThread *thread, DynamicValue *returnValue) const {
double result = 0.0;
const DynamicValue &inputDynamicValue = thread->getStackValueFromTop(0).value;
if (inputDynamicValue.getType() != DynamicValueTypes::kString) {
thread->error("Invalid input value to str2num");
return kMiniscriptInstructionOutcomeFailed;
}
const Common::String &str = inputDynamicValue.getString();
if (str.empty())
result = 0.0;
else if (!sscanf(str.c_str(), "%lf", &result)) {
// NOTE: sscanf will properly handle cases where a number is followed by a non-numeric value,
// which is consistent with mTropolis' behavior.
//
// If it fails, the result is 0.0 and no script error.
//
// This includes a case in Obsidian where it tries to parse in invalid room number "L100"
// upon entering the sky face for the first time in Bureau.
#ifdef MTROPOLIS_DEBUG_ENABLE
if (Debugger *debugger = thread->getRuntime()->debugGetDebugger())
debugger->notify(kDebugSeverityWarning, Common::String::format("Failed to parse '%s' as a number", str.c_str()));
#endif
result = 0.0;
}
returnValue->setFloat(result);
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome StrConcat::execute(MiniscriptThread *thread) const {
if (thread->getStackSize() < 2) {
thread->error("Stack underflow");
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
outcome = thread->dereferenceRValue(1);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
DynamicValue &rVal = thread->getStackValueFromTop(0).value;
DynamicValue &lValDest = thread->getStackValueFromTop(1).value;
if (rVal.getType() != DynamicValueTypes::kString) {
thread->error("String concat right side was not a string");
return kMiniscriptInstructionOutcomeFailed;
}
if (lValDest.getType() != DynamicValueTypes::kString) {
thread->error("String concat left side was not a string");
return kMiniscriptInstructionOutcomeFailed;
}
lValDest.setString(lValDest.getString() + rVal.getString());
thread->popValues(1);
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome PointCreate::execute(MiniscriptThread *thread) const {
if (thread->getStackSize() < 2) {
thread->error("Stack underflow");
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
outcome = thread->dereferenceRValue(1);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
DynamicValue &yVal = thread->getStackValueFromTop(0).value;
DynamicValue &xValDest = thread->getStackValueFromTop(1).value;
int16 coords[2];
DynamicValue *coordInputs[2] = {&xValDest, &yVal};
for (int i = 0; i < 2; i++) {
DynamicValue *v = coordInputs[i];
DynamicValue listContents;
if (v->getType() == DynamicValueTypes::kList) {
// Yes this is actually allowed
const Common::SharedPtr<DynamicList> &list = v->getList();
if (list->getSize() != 1 || !list->getAtIndex(0, listContents)) {
thread->error("Can't convert list to integer");
return kMiniscriptInstructionOutcomeFailed;
}
v = &listContents;
}
switch (v->getType()) {
case DynamicValueTypes::kFloat:
coords[i] = static_cast<int16>(floor(v->getFloat() + 0.5)) & 0xffff;
break;
case DynamicValueTypes::kInteger:
coords[i] = static_cast<int16>(v->getInt());
break;
case DynamicValueTypes::kBoolean:
coords[i] = (v->getBool()) ? 1 : 0;
break;
default:
thread->error("Invalid input for point creation");
return kMiniscriptInstructionOutcomeFailed;
}
}
xValDest.setPoint(Common::Point(coords[0], coords[1]));
thread->popValues(1);
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome RangeCreate::execute(MiniscriptThread *thread) const {
if (thread->getStackSize() < 2) {
thread->error("Stack underflow");
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
outcome = thread->dereferenceRValue(1);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
DynamicValue &yVal = thread->getStackValueFromTop(0).value;
DynamicValue &xValDest = thread->getStackValueFromTop(1).value;
int32 coords[2];
DynamicValue *coordInputs[2] = {&xValDest, &yVal};
for (int i = 0; i < 2; i++) {
DynamicValue *v = coordInputs[i];
DynamicValue listContents;
if (v->getType() == DynamicValueTypes::kList) {
// Yes this is actually allowed
const Common::SharedPtr<DynamicList> &list = v->getList();
if (list->getSize() != 1 || !list->getAtIndex(0, listContents)) {
thread->error("Can't convert list to integer");
return kMiniscriptInstructionOutcomeFailed;
}
v = &listContents;
}
switch (v->getType()) {
case DynamicValueTypes::kFloat:
coords[i] = static_cast<int32>(floor(v->getFloat() + 0.5));
break;
case DynamicValueTypes::kInteger:
coords[i] = v->getInt();
break;
case DynamicValueTypes::kBoolean:
coords[i] = (v->getBool()) ? 1 : 0;
break;
default:
thread->error("Invalid input for point creation");
return kMiniscriptInstructionOutcomeFailed;
}
}
xValDest.setIntRange(IntRange(coords[0], coords[1]));
thread->popValues(1);
return kMiniscriptInstructionOutcomeContinue;
}
GetChild::GetChild(uint32 attribute, bool isLValue, bool isIndexed)
: _attribute(attribute), _isLValue(isLValue), _isIndexed(isIndexed) {
}
MiniscriptInstructionOutcome GetChild::execute(MiniscriptThread *thread) const {
const Common::Array<MiniscriptProgram::Attribute> &attribs = thread->getProgram()->getAttributes();
if (_attribute >= attribs.size()) {
thread->error("Invalid attribute index");
return kMiniscriptInstructionOutcomeFailed;
}
const Common::String &attrib = attribs[_attribute].name;
MiniscriptInstructionOutcome outcome = kMiniscriptInstructionOutcomeFailed;
if (_isIndexed) {
if (thread->getStackSize() < 2) {
thread->error("Stack underflow");
return kMiniscriptInstructionOutcomeFailed;
}
// Convert index
outcome = thread->dereferenceRValue(0);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
const MiniscriptStackValue &indexSlot = thread->getStackValueFromTop(0);
MiniscriptStackValue &indexableValueSlot = thread->getStackValueFromTop(1);
if (_isLValue) {
if (indexableValueSlot.value.getType() == DynamicValueTypes::kObject) {
Common::SharedPtr<RuntimeObject> obj = indexableValueSlot.value.getObject().object.lock();
if (!obj) {
thread->error("Tried to write '" + attrib + "' to an invalid object reference");
return kMiniscriptInstructionOutcomeFailed;
}
DynamicValueWriteProxy proxy;
outcome = obj->writeRefAttributeIndexed(thread, proxy, attrib, indexSlot.value);
if (outcome == kMiniscriptInstructionOutcomeFailed) {
thread->error("Failed to get a writeable reference to indexed attribute '" + attrib + "'");
return outcome;
}
indexableValueSlot.value.setWriteProxy(proxy);
} else if (indexableValueSlot.value.getType() == DynamicValueTypes::kWriteProxy) {
DynamicValueWriteProxy proxy = indexableValueSlot.value.getWriteProxy();
outcome = proxy.pod.ifc->refAttribIndexed(thread, proxy, proxy.pod.objectRef, proxy.pod.ptrOrOffset, attrib, indexSlot.value);
if (outcome == kMiniscriptInstructionOutcomeFailed) {
thread->error("Can't write to indexed attribute '" + attrib + "'");
return outcome;
}
indexableValueSlot.value.setWriteProxy(proxy);
} else {
thread->error("Tried to l-value index something that was not writeable");
return kMiniscriptInstructionOutcomeFailed;
}
} else {
outcome = readRValueAttribIndexed(thread, indexableValueSlot.value, attrib, indexSlot.value);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
}
thread->popValues(1);
} else {
if (thread->getStackSize() < 1) {
thread->error("Stack underflow");
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptStackValue &indexableValueSlot = thread->getStackValueFromTop(0);
if (_isLValue) {
if (indexableValueSlot.value.getType() == DynamicValueTypes::kObject) {
Common::SharedPtr<RuntimeObject> obj = indexableValueSlot.value.getObject().object.lock();
if (!obj) {
thread->error("Tried to indirect '" + attrib + "' using an invalid object reference");
return kMiniscriptInstructionOutcomeFailed;
}
DynamicValueWriteProxy writeProxy;
outcome = obj->writeRefAttribute(thread, writeProxy, attrib);
if (outcome == kMiniscriptInstructionOutcomeFailed) {
thread->error("Failed to get a writeable reference to attribute '" + attrib + "'");
return outcome;
}
indexableValueSlot.value.setWriteProxy(writeProxy);
} else if (indexableValueSlot.value.getType() == DynamicValueTypes::kWriteProxy) {
DynamicValueWriteProxy proxy = indexableValueSlot.value.getWriteProxy();
outcome = proxy.pod.ifc->refAttrib(thread, proxy, proxy.pod.objectRef, proxy.pod.ptrOrOffset, attrib);
if (outcome == kMiniscriptInstructionOutcomeFailed) {
thread->error("Can't write to attribute '" + attrib + "'");
return outcome;
}
indexableValueSlot.value.setWriteProxy(proxy);
} else {
thread->error("Tried to l-value index something that was not writeable");
return kMiniscriptInstructionOutcomeFailed;
}
} else {
outcome = readRValueAttrib(thread, indexableValueSlot.value, attrib);
}
}
return outcome;
}
MiniscriptInstructionOutcome GetChild::readRValueAttrib(MiniscriptThread *thread, DynamicValue &valueSrcDest, const Common::String &attrib) const {
switch (valueSrcDest.getType()) {
case DynamicValueTypes::kPoint:
if (attrib == "x")
valueSrcDest.setInt(valueSrcDest.getPoint().x);
else if (attrib == "y")
valueSrcDest.setInt(valueSrcDest.getPoint().y);
else {
thread->error("Point has no attribute '" + attrib + "'");
return kMiniscriptInstructionOutcomeFailed;
}
break;
case DynamicValueTypes::kIntegerRange:
if (attrib == "start")
valueSrcDest.setInt(valueSrcDest.getIntRange().min);
else if (attrib == "end")
valueSrcDest.setInt(valueSrcDest.getIntRange().max);
else {
thread->error("Integer range has no attribute '" + attrib + "'");
return kMiniscriptInstructionOutcomeFailed;
}
break;
case DynamicValueTypes::kVector:
if (attrib == "angle")
valueSrcDest.setFloat(valueSrcDest.getVector().angleDegrees);
else if (attrib == "magnitude")
valueSrcDest.setFloat(valueSrcDest.getVector().magnitude);
else {
thread->error("Vector has no attribute '" + attrib + "'");
return kMiniscriptInstructionOutcomeFailed;
}
break;
case DynamicValueTypes::kObject: {
Common::SharedPtr<RuntimeObject> obj = valueSrcDest.getObject().object.lock();
if (!obj) {
thread->error("Unable to read object attribute '" + attrib + "' from invalid object");
return kMiniscriptInstructionOutcomeFailed;
} else if (!obj->readAttribute(thread, valueSrcDest, attrib)) {
thread->error("Unable to read object attribute '" + attrib + "'");
return kMiniscriptInstructionOutcomeFailed;
}
} break;
case DynamicValueTypes::kList: {
Common::SharedPtr<DynamicList> list = valueSrcDest.getList();
if (attrib == "count") {
valueSrcDest.setInt(list->getSize());
} else {
thread->error("Unable to read list attribute '" + attrib + "'");
return kMiniscriptInstructionOutcomeFailed;
}
} break;
default:
thread->error("Unable to read attribute '" + attrib + "' from rvalue");
return kMiniscriptInstructionOutcomeFailed;
}
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome GetChild::readRValueAttribIndexed(MiniscriptThread *thread, DynamicValue &valueSrcDest, const Common::String &attrib, const DynamicValue &index) const {
switch (valueSrcDest.getType()) {
case DynamicValueTypes::kList:
if (attrib == "value") {
// Hold list ref since it may get released by the read operation
Common::SharedPtr<DynamicList> list = valueSrcDest.getList();
size_t realIndex = 0;
if (!DynamicList::dynamicValueToIndex(realIndex, index)) {
thread->error("Unable to list value at specified index");
return kMiniscriptInstructionOutcomeFailed;
}
if (!list->getAtIndex(realIndex, valueSrcDest)) {
thread->error("List read index out of bounds");
return kMiniscriptInstructionOutcomeFailed;
}
} else {
thread->error("Unable to read list attribute '" + attrib + "'");
return kMiniscriptInstructionOutcomeFailed;
}
break;
case DynamicValueTypes::kObject: {
Common::SharedPtr<RuntimeObject> obj = valueSrcDest.getObject().object.lock();
if (!obj) {
thread->error("Unable to read object indexed attribute '" + attrib + "' from invalid object");
return kMiniscriptInstructionOutcomeFailed;
} else if (!obj->readAttributeIndexed(thread, valueSrcDest, attrib, index)) {
thread->error("Unable to read object indexed attribute '" + attrib + "'");
return kMiniscriptInstructionOutcomeFailed;
}
} break;
default:
thread->error("Unable to read indexed rvalue attribute '" + attrib + "'");
return kMiniscriptInstructionOutcomeFailed;
}
return kMiniscriptInstructionOutcomeContinue;
}
PushValue::PushValue(DataType dataType, const void *value, bool isLValue)
: _dataType(dataType)/*, _isLValue(isLValue) */ {
switch (dataType) {
case DataType::kDataTypeBool:
new (static_cast<bool *>(&_value.b)) bool(*static_cast<const bool *>(value));
break;
case DataType::kDataTypeDouble:
new (static_cast<double *>(&_value.f)) double(*static_cast<const double *>(value));
break;
case DataType::kDataTypeLocalRef:
case DataType::kDataTypeGlobalRef:
new (static_cast<uint32 *>(&_value.ref)) uint32(*static_cast<const uint32 *>(value));
break;
case DataType::kDataTypeLabel:
new (static_cast<Label *>(&_value.lbl)) Label(*static_cast<const Label *>(value));
break;
case DataType::kDataTypeNull:
break;
default:
warning("PushValue instruction has an unknown type of value, this will probably malfunction!");
break;
}
}
PushValue::ValueUnion::ValueUnion() {
}
PushValue::~PushValue() {
switch (_dataType) {
case DataType::kDataTypeLabel:
_value.lbl.~Label();
break;
default:
break;
}
}
MiniscriptInstructionOutcome ListCreate::execute(MiniscriptThread *thread) const {
if (thread->getStackSize() < 2) {
thread->error("Stack underflow");
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptStackValue &rs = thread->getStackValueFromTop(0);
MiniscriptStackValue &lsDest = thread->getStackValueFromTop(1);
Common::SharedPtr<DynamicList> list(new DynamicList());
if (!list->setAtIndex(0, lsDest.value)) {
thread->error("Failed to set value 1 of list");
return kMiniscriptInstructionOutcomeFailed;
}
if (!list->setAtIndex(1, rs.value)) {
thread->error("Failed to set value 2 of list");
return kMiniscriptInstructionOutcomeFailed;
}
lsDest.value.setList(list);
thread->popValues(1);
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome ListAppend::execute(MiniscriptThread *thread) const {
if (thread->getStackSize() < 2) {
thread->error("Stack underflow");
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptStackValue &rs = thread->getStackValueFromTop(0);
MiniscriptStackValue &lsDest = thread->getStackValueFromTop(1);
if (lsDest.value.getType() != DynamicValueTypes::kList) {
thread->error("Expected list on left side of list_append");
return kMiniscriptInstructionOutcomeFailed;
}
Common::SharedPtr<DynamicList> listRef = lsDest.value.getList();
if (listRef.refCount() != 2) {
listRef = listRef->clone();
lsDest.value.setList(listRef);
}
if (!listRef->setAtIndex(listRef->getSize(), rs.value)) {
thread->error("Failed to expand list");
return kMiniscriptInstructionOutcomeFailed;
}
thread->popValues(1);
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome PushValue::execute(MiniscriptThread *thread) const {
DynamicValue value;
switch (_dataType) {
case DataType::kDataTypeNull:
value.clear();
break;
case DataType::kDataTypeDouble:
value.setFloat(_value.f);
break;
case DataType::kDataTypeBool:
value.setBool(_value.b);
break;
case DataType::kDataTypeLocalRef:
value.setObject(ObjectReference(thread->getRefs()->getRefByIndex(_value.ref)));
break;
case DataType::kDataTypeGlobalRef:
value.setObject(ObjectReference(thread->getRefs()->getGlobalRefByIndex(_value.ref)));
break;
case DataType::kDataTypeLabel: {
MTropolis::Label label;
label.id = _value.lbl.id;
label.superGroupID = _value.lbl.superGroup;
value.setLabel(label);
} break;
default:
assert(false);
break;
}
thread->pushValue(value);
return kMiniscriptInstructionOutcomeContinue;
}
PushGlobal::PushGlobal(uint32 globalID, bool isLValue) : _globalID(globalID), _isLValue(isLValue) {
}
MiniscriptInstructionOutcome PushGlobal::execute(MiniscriptThread *thread) const {
thread->pushValue(DynamicValue());
DynamicValue &value = thread->getStackValueFromTop(0).value;
switch (_globalID) {
case kGlobalRefElement:
case kGlobalRefSection:
case kGlobalRefScene:
case kGlobalRefProject:
return executeFindFilteredParent(thread, value);
case kGlobalRefModifier:
value.setObject(thread->getModifier()->getSelfReference());
break;
case kGlobalRefIncomingData:
if (_isLValue) {
DynamicValueWriteProxy proxy;
thread->createWriteIncomingDataProxy(proxy);
value.setWriteProxy(proxy);
} else
value = thread->getMessageProperties()->getValue();
break;
case kGlobalRefSource:
value.setObject(ObjectReference(thread->getMessageProperties()->getSource()));
break;
case kGlobalRefMouse:
value.setPoint(thread->getRuntime()->getCachedMousePosition());
break;
case kGlobalRefTicks:
value.setInt(thread->getRuntime()->getPlayTime() * 60 / 1000);
break;
case kGlobalRefSharedScene:
value.setObject(ObjectReference(thread->getRuntime()->getActiveSharedScene()));
break;
case kGlobalRefActiveScene:
value.setObject(ObjectReference(thread->getRuntime()->getActiveMainScene()));
break;
default:
assert(false);
thread->error("Unknown global ref type");
return kMiniscriptInstructionOutcomeFailed;
}
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome PushGlobal::executeFindFilteredParent(MiniscriptThread *thread, DynamicValue &result) const {
Common::WeakPtr<RuntimeObject> ref = thread->getModifier()->getSelfReference();
for (;;) {
Common::SharedPtr<RuntimeObject> obj = ref.lock();
if (!obj)
break;
bool isMatch = false;
switch (_globalID) {
case kGlobalRefElement:
isMatch = obj->isStructural(); // We don't classify the project, sections, and subsections as elements, but mTropolis does
break;
case kGlobalRefSection:
isMatch = obj->isSection();
break;
case kGlobalRefScene:
// FIXME: Need better detection of scenes
isMatch = obj->isElement() && static_cast<Element *>(obj.get())->getParent()->isSubsection();
break;
case kGlobalRefProject:
isMatch = obj->isProject();
break;
default:
break;
};
if (isMatch)
break;
else if (obj->isStructural()) {
Structural *parent = static_cast<Structural *>(obj.get())->getParent();
if (parent)
ref = parent->getSelfReference();
else {
ref.reset();
break;
}
} else if (obj->isModifier()) {
ref = static_cast<Modifier *>(obj.get())->getParent();
} else {
ref.reset();
break;
}
}
result.setObject(ref);
return kMiniscriptInstructionOutcomeContinue;
}
PushString::PushString(const Common::String &str) : _str(str) {
}
MiniscriptInstructionOutcome PushString::execute(MiniscriptThread *thread) const {
DynamicValue str;
str.setString(_str);
thread->pushValue(str);
return kMiniscriptInstructionOutcomeContinue;
}
Jump::Jump(uint32 instrOffset, bool isConditional) : _instrOffset(instrOffset), _isConditional(isConditional) {
}
MiniscriptInstructionOutcome Jump::execute(MiniscriptThread *thread) const {
if (_isConditional) {
if (thread->getStackSize() < 1) {
thread->error("Stack underflow");
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptInstructionOutcome outcome = thread->dereferenceRValue(0);
if (outcome != kMiniscriptInstructionOutcomeContinue)
return outcome;
bool isTrue = miniscriptEvaluateTruth(thread->getStackValueFromTop(0).value);
thread->popValues(1);
if (!isTrue)
thread->jumpOffset(this->_instrOffset);
} else {
thread->jumpOffset(this->_instrOffset);
}
return kMiniscriptInstructionOutcomeContinue;
}
} // End of namespace MiniscriptInstructions
MiniscriptThread::MiniscriptThread(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msgProps, const Common::SharedPtr<MiniscriptProgram> &program, const Common::SharedPtr<MiniscriptReferences> &refs, Modifier *modifier)
: _runtime(runtime), _msgProps(msgProps), _program(program), _refs(refs), _modifier(modifier), _currentInstruction(0), _failed(false) {
}
void MiniscriptThread::error(const Common::String &message) {
#ifdef MTROPOLIS_DEBUG_ENABLE
if (_runtime->debugGetDebugger())
_runtime->debugGetDebugger()->notify(kDebugSeverityError, Common::String("Miniscript error: " + message));
#endif
warning("Miniscript error in (%x '%s'): %s", _modifier->getStaticGUID(), _modifier->getName().c_str(), message.c_str());
// This should be redundant
_failed = true;
}
const Common::SharedPtr<MiniscriptProgram> &MiniscriptThread::getProgram() const {
return _program;
}
const Common::SharedPtr<MiniscriptReferences> &MiniscriptThread::getRefs() const {
return _refs;
}
Modifier *MiniscriptThread::getModifier() const {
return _modifier;
}
const Common::SharedPtr<MessageProperties> &MiniscriptThread::getMessageProperties() const {
return _msgProps;
}
Runtime *MiniscriptThread::getRuntime() const {
return _runtime;
}
void MiniscriptThread::pushValue(const DynamicValue &value) {
_stack.push_back(MiniscriptStackValue());
MiniscriptStackValue &stackValue = _stack.back();
stackValue.value = value;
}
void MiniscriptThread::popValues(size_t count) {
while (count--)
_stack.pop_back();
}
size_t MiniscriptThread::getStackSize() const {
return _stack.size();
}
MiniscriptStackValue &MiniscriptThread::getStackValueFromTop(size_t offset) {
assert(offset < _stack.size());
return _stack[_stack.size() - 1 - offset];
}
MiniscriptInstructionOutcome MiniscriptThread::dereferenceRValue(size_t offset) {
assert(offset < _stack.size());
MiniscriptStackValue &stackValue = _stack[_stack.size() - 1 - offset];
switch (stackValue.value.getType()) {
case DynamicValueTypes::kObject: {
Common::SharedPtr<RuntimeObject> obj = stackValue.value.getObject().object.lock();
if (obj && obj->isModifier()) {
const Modifier *modifier = static_cast<const Modifier *>(obj.get());
if (modifier->isVariable()) {
static_cast<const VariableModifier *>(modifier)->varGetValue(stackValue.value);
}
}
} break;
case DynamicValueTypes::kWriteProxy:
this->error("Attempted to dereference an lvalue proxy");
return kMiniscriptInstructionOutcomeFailed;
case DynamicValueTypes::kList:
stackValue.value.setList(stackValue.value.getList()->clone());
break;
default:
break;
}
return kMiniscriptInstructionOutcomeContinue;
}
void MiniscriptThread::jumpOffset(size_t offset) {
if (offset == 0) {
this->error("Invalid jump offset");
_failed = true;
return;
}
_currentInstruction += offset - 1;
}
bool MiniscriptThread::evaluateTruthOfResult(bool &isTrue) {
if (_stack.size() != 1) {
this->error("Miniscript program didn't return a result");
return false;
}
MiniscriptInstructionOutcome outcome = dereferenceRValue(0);
if (outcome != kMiniscriptInstructionOutcomeContinue) {
this->error("Miniscript program result couldn't be dereferenced");
return false;
}
isTrue = miniscriptEvaluateTruth(_stack[0].value);
return true;
}
void MiniscriptThread::createWriteIncomingDataProxy(DynamicValueWriteProxy &proxy) {
proxy.pod.ifc = DynamicValueWriteInterfaceGlue<IncomingDataWriteInterface>::getInstance();
proxy.pod.objectRef = this;
proxy.pod.ptrOrOffset = 0;
}
void MiniscriptThread::retryInstruction() {
_currentInstruction--;
}
MiniscriptInstructionOutcome MiniscriptThread::IncomingDataWriteInterface::write(MiniscriptThread *thread, const DynamicValue &value, void *objectRef, uintptr ptrOrOffset) {
thread->_msgProps->setValue(value);
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome MiniscriptThread::IncomingDataWriteInterface::refAttrib(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib) {
// TODO: Generic refAttrib for dynamic values
return kMiniscriptInstructionOutcomeFailed;
}
MiniscriptInstructionOutcome MiniscriptThread::IncomingDataWriteInterface::refAttribIndexed(MiniscriptThread *thread, DynamicValueWriteProxy &proxy, void *objectRef, uintptr ptrOrOffset, const Common::String &attrib, const DynamicValue &index) {
// TODO: Generic refAttribIndexed for dynamic values
return kMiniscriptInstructionOutcomeFailed;
}
CORO_BEGIN_DEFINITION(MiniscriptThread::ResumeThreadCoroutine)
struct Locals {
MiniscriptThread *self = nullptr;
size_t numInstrs = 0;
size_t instrNum = 0;
};
CORO_BEGIN_FUNCTION
locals->self = params->thread.get();
locals->numInstrs = locals->self->_program->getInstructions().size();
CORO_IF (locals->numInstrs == 0)
CORO_RETURN;
CORO_END_IF
CORO_WHILE (locals->self->_currentInstruction < locals->numInstrs && !locals->self->_failed)
CORO_AWAIT_MINISCRIPT(locals->self->runNextInstruction());
CORO_END_WHILE
CORO_END_FUNCTION
CORO_END_DEFINITION
MiniscriptInstructionOutcome MiniscriptThread::runNextInstruction() {
const MiniscriptInstruction *instr = _program->getInstructions()[_currentInstruction++];
MiniscriptInstructionOutcome outcome = instr->execute(this);
if (outcome == kMiniscriptInstructionOutcomeFailed) {
// Treat this as non-fatal but bail out of the execution loop
_failed = true;
return kMiniscriptInstructionOutcomeContinue;
}
// Otherwise continue
return outcome;
}
MiniscriptInstructionOutcome MiniscriptThread::tryLoadVariable(MiniscriptStackValue &stackValue) {
if (stackValue.value.getType() == DynamicValueTypes::kObject) {
Common::SharedPtr<RuntimeObject> obj = stackValue.value.getObject().object.lock();
if (obj && obj->isModifier() && static_cast<Modifier *>(obj.get())->isVariable()) {
VariableModifier *varMod = static_cast<VariableModifier *>(obj.get());
varMod->varGetValue(stackValue.value);
}
}
return kMiniscriptInstructionOutcomeContinue;
}
MiniscriptInstructionOutcome miniscriptIgnoreFailure(MiniscriptInstructionOutcome outcome) {
if (outcome == kMiniscriptInstructionOutcomeFailed)
return kMiniscriptInstructionOutcomeContinue;
return outcome;
}
} // End of namespace MTropolis