| #ifdef WITH_RIVE_SCRIPTING | |
| #ifdef WITH_RIVE_SCRIPTING_LUAU | |
| // Only for the ScriptingVM to ScriptBackend conversion in backend(). | |
| #include "rive/lua/scripting_vm.hpp" | |
| #endif | |
| #include "rive/importers/text_asset_importer.hpp" | |
| #endif | |
| #include "rive/assets/script_asset.hpp" | |
| #include "rive/signed_content_header.hpp" | |
| #include "rive/component.hpp" | |
| #include "rive/file.hpp" | |
| #include "rive/importers/import_stack.hpp" | |
| #include "rive/importers/scripted_object_importer.hpp" | |
| #ifdef WITH_RIVE_SCRIPTING_WASM | |
| #include "rive/wasm/wasm_scripting_vm.hpp" | |
| #endif | |
| #include "rive/script_input_artboard.hpp" | |
| #include "rive/script_input_boolean.hpp" | |
| #include "rive/script_input_color.hpp" | |
| #include "rive/script_input_number.hpp" | |
| #include "rive/script_input_string.hpp" | |
| #include "rive/script_input_trigger.hpp" | |
| #include "rive/script_input_viewmodel_property.hpp" | |
| #include "rive/scripted/scripted_drawable.hpp" | |
| #include "rive/scripted/scripted_layout.hpp" | |
| #include "rive/scripted/scripted_object.hpp" | |
| #include "rive/data_bind/data_bind.hpp" | |
| using namespace rive; | |
| ScriptInput::~ScriptInput() | |
| { | |
| if (m_ownsDataBind && m_dataBind) | |
| { | |
| delete m_dataBind; | |
| } | |
| } | |
| ScriptInput* ScriptInput::from(Core* component) | |
| { | |
| switch (component->coreType()) | |
| { | |
| case ScriptInputArtboard::typeKey: | |
| return component->as<ScriptInputArtboard>(); | |
| case ScriptInputBoolean::typeKey: | |
| return component->as<ScriptInputBoolean>(); | |
| case ScriptInputColor::typeKey: | |
| return component->as<ScriptInputColor>(); | |
| case ScriptInputNumber::typeKey: | |
| return component->as<ScriptInputNumber>(); | |
| case ScriptInputString::typeKey: | |
| return component->as<ScriptInputString>(); | |
| case ScriptInputTrigger::typeKey: | |
| return component->as<ScriptInputTrigger>(); | |
| case ScriptInputViewModelProperty::typeKey: | |
| return component->as<ScriptInputViewModelProperty>(); | |
| } | |
| return nullptr; | |
| } | |
| bool ScriptInput::takesArtboardSlot(const Component* input) | |
| { | |
| return input->parentId() != 0u; | |
| } | |
| ScriptedObjectImporter* ScriptInput::ownerImporter( | |
| ImportStack& importStack, | |
| const Component* input) | |
| { | |
| // The importer stays open until the next scripted object replaces it, so | |
| // after an owner this runtime can't read it still holds the one before. | |
| // Only hand it to inputs whose parent matches what that owner gives its | |
| // own inputs. | |
| auto importer = importStack.latest<ScriptedObjectImporter>( | |
| ScriptedDrawable::typeKey); | |
| if (importer == nullptr || input->parentId() != importer->inputParentId()) | |
| { | |
| return nullptr; | |
| } | |
| return importer; | |
| } | |
| void ScriptInput::initScriptedValue() {} | |
| bool ScriptInput::validateForColdScriptInit() | |
| { | |
| return validateForScriptInit(); | |
| } | |
| bool ScriptInput::hydrateScriptInput() | |
| { | |
| initScriptedValue(); | |
| return true; | |
| } | |
| bool ScriptInput::validateHydrationPrerequisites() { return true; } | |
| #ifdef WITH_RIVE_SCRIPTING | |
| ScriptBackend* ScriptAsset::backend() | |
| { | |
| if (m_file == nullptr) | |
| { | |
| return nullptr; | |
| } | |
| #ifdef WITH_RIVE_SCRIPTING_WASM | |
| if (m_wasmBackend != nullptr) | |
| { | |
| return m_wasmBackend; | |
| } | |
| if (m_file->wasmScriptingVM() != nullptr) | |
| { | |
| return m_file->wasmScriptingVM(); | |
| } | |
| #endif | |
| #ifdef WITH_RIVE_SCRIPTING_LUAU | |
| return m_file->scriptingVM(); | |
| #else | |
| return nullptr; | |
| #endif | |
| } | |
| #endif | |
| bool ScriptAsset::initScriptedObject(ScriptedObject* object) | |
| { | |
| #ifdef WITH_RIVE_SCRIPTING | |
| if (backend() == nullptr) | |
| { | |
| return false; | |
| } | |
| return initScriptedObjectWith(object); | |
| #else | |
| return false; | |
| #endif | |
| } | |
| #if defined(WITH_RIVE_SCRIPTING) | |
| void ScriptAsset::registrationComplete(int ref) | |
| { | |
| if (isModule()) | |
| { | |
| m_scriptRegistered = true; | |
| } | |
| else | |
| { | |
| generatorFunctionRef(ref); | |
| // Force re-verification on next init so that method detection (e.g. | |
| // draw) reflects the newly compiled script. | |
| m_initted = false; | |
| } | |
| } | |
| #endif | |
| bool ScriptAsset::initScriptedObjectWith(ScriptedObject* object) | |
| { | |
| #if defined(WITH_RIVE_SCRIPTING) | |
| auto scriptBackend = backend(); | |
| if (scriptBackend == nullptr) | |
| { | |
| return false; | |
| } | |
| int ref = scriptBackend->resolveGeneratorRef(generatorFunctionRef()); | |
| if (ref == 0) | |
| { | |
| fprintf( | |
| stderr, | |
| "ScriptAsset doesn't have a generator function %s\n", | |
| name().c_str()); | |
| return false; | |
| } | |
| if (!m_initted) | |
| { | |
| // The editor detected the implemented methods (by executing the | |
| // generator in its workspace) and serialized the bitfield; the runtime | |
| // never detects. Files predating this property decode to the all-bits | |
| // default (2097151), so they behave as "implements everything" and rely | |
| // on graceful dispatch (each callback no-ops when the method isn't | |
| // actually a function on the returned table). | |
| OptionalScriptedMethods::implementedMethods( | |
| serializedImplementedMethods() & methodMask); | |
| #ifdef WITH_RIVE_TOOLS | |
| // Bit 15 was the removed drawCanvas callback: the editor detected it | |
| // on this script, so its canvas work never runs until moved into | |
| // draw. Legacy all-bits exports stay silent; the shipping runtime | |
| // stays quiet entirely, the editor console carries the migration. | |
| static const uint32_t kRetiredDrawCanvasBit = 1 << 15; | |
| static const uint32_t kAllMethodsDefault = (1 << 21) - 1; | |
| if (serializedImplementedMethods() != kAllMethodsDefault && | |
| (serializedImplementedMethods() & kRetiredDrawCanvasBit) != 0) | |
| { | |
| fprintf( | |
| stderr, | |
| "rive: script implements drawCanvas, which is no longer " | |
| "called; move its body into draw\n"); | |
| } | |
| #endif | |
| m_needsPointerCheck = serializedImplementedMethods() == | |
| legacyImplementedMethods; | |
| m_initted = true; | |
| } | |
| object->implementedMethods(implementedMethods()); | |
| if (!object->ensureScriptInitialized(scriptBackend, ref)) | |
| { | |
| return false; | |
| } | |
| if (m_needsPointerCheck) | |
| { | |
| checkLegacyPointerHandlers(scriptBackend, object->self()); | |
| // Recopy: the object took the bits before the check. | |
| object->implementedMethods(implementedMethods()); | |
| m_needsPointerCheck = false; | |
| } | |
| return true; | |
| #else | |
| return false; | |
| #endif | |
| } | |
| #if defined(WITH_RIVE_SCRIPTING) | |
| void ScriptAsset::checkLegacyPointerHandlers(ScriptBackend* vm, int self) | |
| { | |
| static const struct | |
| { | |
| const char* name; | |
| int bit; | |
| } handlers[] = { | |
| {"pointerDown", m_wantsPointerDownBit}, | |
| {"pointerMove", m_wantsPointerMoveBit}, | |
| {"pointerUp", m_wantsPointerUpBit}, | |
| {"pointerExit", m_wantsPointerExitBit}, | |
| }; | |
| int bits = implementedMethods(); | |
| for (const auto& handler : handlers) | |
| { | |
| if (!vm->hasMethod(self, handler.name)) | |
| { | |
| bits &= ~handler.bit; | |
| } | |
| } | |
| implementedMethods(bits); | |
| } | |
| #endif | |
| bool ScriptAsset::decode(SimpleArray<uint8_t>& data, Factory* factory) | |
| { | |
| #ifdef WITH_RIVE_SCRIPTING | |
| m_verified = false; | |
| // For in-band bytecode, isSigned should always be false (signature is | |
| // stored separately for aggregate verification). | |
| SignedContentHeader header(Span<const uint8_t>(data.data(), data.size())); | |
| if (!header.isValid()) | |
| { | |
| return false; | |
| } | |
| // Store just the bytecode (without header) for later verification and use. | |
| auto bytecode = header.content(); | |
| m_bytecode = SimpleArray<uint8_t>(bytecode.data(), bytecode.size()); | |
| #endif | |
| return true; | |
| } | |
| bool ScriptAsset::bytecode(Span<uint8_t> data) | |
| { | |
| #ifdef WITH_RIVE_SCRIPTING | |
| SignedContentHeader header(Span<const uint8_t>(data.data(), data.size())); | |
| if (!header.isValid()) | |
| { | |
| m_verified = false; | |
| return false; | |
| } | |
| auto bytecode = header.content(); | |
| if (!header.isSigned()) | |
| { | |
| // Unsigned bytecode - mark as unverified | |
| m_verified = false; | |
| m_bytecode = SimpleArray<uint8_t>(bytecode.data(), bytecode.size()); | |
| return true; | |
| } | |
| if (!verifiesContentSignature(header.signature(), bytecode)) | |
| { | |
| // Forged. | |
| m_verified = false; | |
| return false; | |
| } | |
| m_verified = true; | |
| m_bytecode = SimpleArray<uint8_t>(bytecode.data(), bytecode.size()); | |
| #endif | |
| return true; | |
| } |