/* 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 .
*
*/
#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 *globalRefs);
uint registerGlobalGUIDIndex(uint32 guid) override;
private:
Common::Array *_globalRefs;
};
MiniscriptInstructionParserFeedback::MiniscriptInstructionParserFeedback(Common::Array *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 &localRefs, const Common::Array &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::iterator it = _localRefs.begin(), itEnd = _localRefs.end(); it != itEnd; ++it)
it->resolution = scope->resolve(it->guid, it->name, false);
for (Common::Array::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 obj = ref.resolution.lock();
if (obj) {
if (obj->isModifier()) {
Common::WeakPtr mod = obj.staticCast();
visitor->visitWeakModifierRef(mod);
ref.resolution = mod;
} else if (obj->isStructural()) {
Common::WeakPtr struc = obj.staticCast();
visitor->visitWeakStructuralRef(struc);
ref.resolution = struc;
}
}
}
for (GlobalRef &ref : _globalRefs) {
Common::SharedPtr obj = ref.resolution.lock();
if (obj) {
if (obj->isModifier()) {
Common::WeakPtr mod = obj.staticCast();
visitor->visitWeakModifierRef(mod);
ref.resolution = mod;
} else if (obj->isStructural()) {
Common::WeakPtr struc = obj.staticCast();
visitor->visitWeakStructuralRef(struc);
ref.resolution = struc;
}
}
}
}
Common::WeakPtr MiniscriptReferences::getRefByIndex(uint index) const {
if (index >= _localRefs.size())
return Common::WeakPtr();
return _localRefs[index].resolution;
}
Common::WeakPtr MiniscriptReferences::getGlobalRefByIndex(uint index) const {
if (index >= _globalRefs.size())
return Common::WeakPtr();
return _globalRefs[index].resolution;
}
MiniscriptProgram::MiniscriptProgram(const Common::SharedPtr > &programData, const Common::Array &instructions, const Common::Array &attributes)
: _programData(programData), _instructions(instructions), _attributes(attributes) {
}
MiniscriptProgram::~MiniscriptProgram() {
// Destruct all instructions
for (Common::Array::const_iterator it = _instructions.begin(), itEnd = _instructions.end(); it != itEnd; ++it)
(*it)->~MiniscriptInstruction();
}
const Common::Array &MiniscriptProgram::getInstructions() const {
return _instructions;
}
const Common::Array &MiniscriptProgram::getAttributes() const {
return _attributes;
}
template
struct MiniscriptInstructionLoader {
static bool loadInstruction(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, IMiniscriptInstructionParserFeedback &feedback);
};
template
bool MiniscriptInstructionLoader::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::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::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(functionID));
return true;
}
template<>
bool MiniscriptInstructionLoader::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::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::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::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::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 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
bool MiniscriptInstructionFactory::create(void *dest, uint32 instrFlags, Data::DataReader &instrDataReader, MiniscriptInstruction *&outMiniscriptInstructionPtr, IMiniscriptInstructionParserFeedback &feedback) {
if (!MiniscriptInstructionLoader::loadInstruction(dest, instrFlags, instrDataReader, feedback))
return false;
outMiniscriptInstructionPtr = static_cast(static_cast(dest));
return true;
}
template
void MiniscriptInstructionFactory::getSizeAndAlignment(size_t &outSize, size_t &outAlignment) {
outSize = sizeof(T);
outAlignment = alignof(T);
}
template
inline SIMiniscriptInstructionFactory *MiniscriptInstructionFactory::getInstance() {
return &_instance;
}
template
SIMiniscriptInstructionFactory MiniscriptInstructionFactory::_instance = {
MiniscriptInstructionFactory::create,
MiniscriptInstructionFactory::getSizeAndAlignment
};
MiniscriptParser::InstructionData::InstructionData()
: opcode(0), flags(0), pdPosition(0), instrFactory(nullptr) {
}
bool MiniscriptParser::parse(const Data::MiniscriptProgram &program, Common::SharedPtr &outProgram, Common::SharedPtr &outReferences) {
Common::Array localRefs;
Common::Array globalRefs;
Common::Array attributes;
Common::SharedPtr > programDataPtr;
Common::Array miniscriptInstructions;
// If the program is empty then just return an empty program
if (program.bytecode.size() == 0 || program.numOfInstructions == 0) {
outProgram = Common::SharedPtr(new MiniscriptProgram(programDataPtr, miniscriptInstructions, attributes));
outReferences = Common::SharedPtr(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 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());
Common::Array &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(&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(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(new MiniscriptProgram(programDataPtr, miniscriptInstructions, attributes));
outReferences = Common::SharedPtr(new MiniscriptReferences(localRefs, globalRefs));
return true;
}
SIMiniscriptInstructionFactory *MiniscriptParser::resolveOpcode(uint16 opcode) {
switch (opcode) {
case 0x834:
return MiniscriptInstructionFactory::getInstance();
case 0x898:
return MiniscriptInstructionFactory::getInstance();
case 0xc9:
return MiniscriptInstructionFactory::getInstance();
case 0xca:
return MiniscriptInstructionFactory::getInstance();
case 0xcb:
return MiniscriptInstructionFactory::getInstance();
case 0xcc:
return MiniscriptInstructionFactory::getInstance();
case 0xcd:
return MiniscriptInstructionFactory::getInstance();
case 0xce:
return MiniscriptInstructionFactory::getInstance();
case 0xcf:
return MiniscriptInstructionFactory::getInstance();
case 0xd0:
return MiniscriptInstructionFactory::getInstance();
case 0xd1:
return MiniscriptInstructionFactory::getInstance();
case 0xd2:
return MiniscriptInstructionFactory::getInstance();
case 0xd3:
return MiniscriptInstructionFactory::getInstance();
case 0xd4:
return MiniscriptInstructionFactory::getInstance();
case 0xd5:
return MiniscriptInstructionFactory::getInstance();
case 0xd6:
return MiniscriptInstructionFactory::getInstance();
case 0xd7:
return MiniscriptInstructionFactory::getInstance();
case 0xd8:
return MiniscriptInstructionFactory::getInstance();
case 0xd9:
return MiniscriptInstructionFactory::getInstance();
case 0xda:
return MiniscriptInstructionFactory::getInstance();
case 0xdb:
return MiniscriptInstructionFactory::getInstance();
case 0x12f:
return MiniscriptInstructionFactory::getInstance();
case 0x130:
return MiniscriptInstructionFactory::getInstance();
case 0x131:
return MiniscriptInstructionFactory::getInstance();
case 0x135:
return MiniscriptInstructionFactory::getInstance();
case 0x136:
return MiniscriptInstructionFactory::getInstance();
case 0x137:
return MiniscriptInstructionFactory::getInstance();
case 0x191:
return MiniscriptInstructionFactory::getInstance();
case 0x192:
return MiniscriptInstructionFactory::getInstance();
case 0x193:
return MiniscriptInstructionFactory::getInstance();
case 0x7d3:
return MiniscriptInstructionFactory::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 obj = target.value.getObject().object.lock();
if (obj && obj->isModifier() && static_cast(obj.get())->isVariable())
var = static_cast(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 obj = targetValue.getObject().object.lock();
if (!obj) {
// Obsidian also triggers NAV_Restart on Project Started, which triggers " 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 msgProps(new MessageProperties(_evt, payloadValue, thread->getModifier()->getSelfReference()));
Common::SharedPtr dispatch;
if (obj->isModifier())
dispatch.reset(new MessageDispatch(msgProps, static_cast(obj.get()), _messageFlags.cascade, _messageFlags.relay, true));
else if (obj->isStructural())
dispatch.reset(new MessageDispatch(msgProps, static_cast(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(round(resultX)), static_cast(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(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(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(round(x)), static_cast(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(inputDynamicValue.getInt()));
break;
case DynamicValueTypes::kFloat:
result = Common::String::format("%g", static_cast(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 &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(floor(v->getFloat() + 0.5)) & 0xffff;
break;
case DynamicValueTypes::kInteger:
coords[i] = static_cast(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 &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(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 &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 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 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 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 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 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 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(&_value.b)) bool(*static_cast(value));
break;
case DataType::kDataTypeDouble:
new (static_cast(&_value.f)) double(*static_cast(value));
break;
case DataType::kDataTypeLocalRef:
case DataType::kDataTypeGlobalRef:
new (static_cast(&_value.ref)) uint32(*static_cast(value));
break;
case DataType::kDataTypeLabel:
new (static_cast