/* 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