blob: bc4dd46b1bc4fdcd3d29d4e6d5ef3c38c401ffe2 [file]
#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;
}