From de527b3ff7eee294298fdd28251a626513bf2405 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 18 Mar 2018 16:27:48 +0000 Subject: [PATCH] Add load and start plugin scripts --- src/openrct2/PlatformEnvironment.cpp | 2 + src/openrct2/PlatformEnvironment.h | 1 + src/openrct2/scripting/Plugin.cpp | 110 +++++++++++++++++++++ src/openrct2/scripting/Plugin.h | 52 ++++++++++ src/openrct2/scripting/ScriptEngine.cpp | 35 +++++++ src/openrct2/scripting/ScriptEngine.h | 10 +- src/openrct2/thirdparty/dukglue/dukvalue.h | 30 ++++++ 7 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 src/openrct2/scripting/Plugin.cpp create mode 100644 src/openrct2/scripting/Plugin.h diff --git a/src/openrct2/PlatformEnvironment.cpp b/src/openrct2/PlatformEnvironment.cpp index 98a35f5f4e..8c5f781301 100644 --- a/src/openrct2/PlatformEnvironment.cpp +++ b/src/openrct2/PlatformEnvironment.cpp @@ -194,6 +194,7 @@ const char * PlatformEnvironment::DirectoryNamesRCT2[] = nullptr, // LOG_SERVER nullptr, // NETWORK_KEY "ObjData", // OBJECT + nullptr, // PLUGIN "Saved Games", // SAVE "Scenarios", // SCENARIO nullptr, // SCREENSHOT @@ -212,6 +213,7 @@ const char * PlatformEnvironment::DirectoryNamesOpenRCT2[] = "serverlogs", // LOG_SERVER "keys", // NETWORK_KEY "object", // OBJECT + "plugin", // PLUGIN "save", // SAVE "scenario", // SCENARIO "screenshot", // SCREENSHOT diff --git a/src/openrct2/PlatformEnvironment.h b/src/openrct2/PlatformEnvironment.h index 2df41e60ee..a17974e455 100644 --- a/src/openrct2/PlatformEnvironment.h +++ b/src/openrct2/PlatformEnvironment.h @@ -38,6 +38,7 @@ namespace OpenRCT2 LOG_SERVER, // Contains server logs. NETWORK_KEY, // Contains the user's public and private keys. OBJECT, // Contains objects. + PLUGIN, // Contains plugins (.js). SAVE, // Contains saved games (SV6). SCENARIO, // Contains scenarios (SC6). SCREENSHOT, // Contains screenshots. diff --git a/src/openrct2/scripting/Plugin.cpp b/src/openrct2/scripting/Plugin.cpp new file mode 100644 index 0000000000..0da56f3005 --- /dev/null +++ b/src/openrct2/scripting/Plugin.cpp @@ -0,0 +1,110 @@ +#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 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. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "Plugin.h" +#include +#include +#include +#include +#include + +using namespace OpenRCT2::Scripting; + +Plugin::Plugin(duk_context * context, const std::string &path) + : _context(context), + _path(path) +{ +} + +Plugin::Plugin(const Plugin&& src) + : _context(src._context), + _path(src._path), + _metadata(src._metadata) +{ +} + +void Plugin::Load() +{ + std::string projectedVariables = "console,park"; + std::string code; + { + std::ifstream fs(_path); + if (fs.is_open()) + { + fs.seekg(0, std::ios::end); + code.reserve(fs.tellg()); + fs.seekg(0, std::ios::beg); + code.assign( + std::istreambuf_iterator(fs), + std::istreambuf_iterator()); + } + } + // Wrap the script in a function and pass the global objects as variables + // so that if the script modifies them, they are not modified for other scripts. + code = "(function(" + projectedVariables + "){" + code + "})(" + projectedVariables + ");"; + auto flags = DUK_COMPILE_EVAL | DUK_COMPILE_SAFE | DUK_COMPILE_NOSOURCE | DUK_COMPILE_NOFILENAME; + auto result = duk_eval_raw(_context, code.c_str(), code.size(), flags); + if (result != DUK_ERR_NONE) + { + auto val = std::string(duk_safe_to_string(_context, -1)); + duk_pop(_context); + throw std::runtime_error("Failed to load plug-in script: " + val); + } + + _metadata = GetMetadata(DukValue::take_from_stack(_context)); +} + +void Plugin::Start() +{ + const auto& mainFunc = _metadata.Main; + mainFunc.push(); + auto result = duk_pcall(_context, 0); + if (result != DUK_ERR_NONE) + { + auto val = std::string(duk_safe_to_string(_context, -1)); + duk_pop(_context); + throw std::runtime_error("[" + _metadata.Name + "] " + val); + } + duk_pop(_context); +} + +PluginMetadata Plugin::GetMetadata(const DukValue& dukMetadata) +{ + PluginMetadata metadata; + if (dukMetadata.type() == DukValue::Type::OBJECT) + { + metadata.Name = dukMetadata["name"].as_string(); + metadata.Version = dukMetadata["version"].as_string(); + + auto dukAuthors = dukMetadata["authors"]; + dukAuthors.push(); + if (dukAuthors.is_array()) + { + auto elements = dukAuthors.as_array(); + std::transform( + elements.begin(), + elements.end(), + std::back_inserter(metadata.Authors), + [](const DukValue& v) { return v.as_string(); }); + } + else + { + metadata.Authors = { dukAuthors.as_string() }; + } + metadata.Main = dukMetadata["main"]; + } + return metadata; +} diff --git a/src/openrct2/scripting/Plugin.h b/src/openrct2/scripting/Plugin.h new file mode 100644 index 0000000000..207eff783a --- /dev/null +++ b/src/openrct2/scripting/Plugin.h @@ -0,0 +1,52 @@ +#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 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. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include +#include +#include +#include + +namespace OpenRCT2::Scripting +{ + struct PluginMetadata + { + std::string Name; + std::string Version; + std::vector Authors; + DukValue Main; + }; + + class Plugin + { + private: + duk_context * const _context; + std::string const _path; + PluginMetadata _metadata; + + public: + Plugin(duk_context * context, const std::string &path); + Plugin(const Plugin&) = delete; + Plugin(const Plugin&&); + + void Load(); + void Start(); + + private: + static PluginMetadata GetMetadata(const DukValue& dukMetadata); + }; +} diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index 898fe7d2c2..bed35d9ca2 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -8,7 +8,10 @@ *****************************************************************************/ #include "ScriptEngine.h" +#include "../core/FileScanner.h" +#include "../core/Path.hpp" #include "../interface/InteractiveConsole.h" +#include "../PlatformEnvironment.h" #include #include #include @@ -47,6 +50,38 @@ void ScriptEngine::Initialise() dukglue_register_global(ctx, std::make_shared(_console), "console"); dukglue_register_global(ctx, std::make_shared(), "park"); + + LoadPlugins(); + StartPlugins(); +} + +void ScriptEngine::LoadPlugins() +{ + auto base = _env.GetDirectoryPath(DIRBASE::USER, DIRID::PLUGIN); + auto pattern = Path::Combine(base, "*.js"); + auto scanner = std::unique_ptr(Path::ScanDirectory(pattern, true)); + while (scanner->Next()) + { + auto path = std::string(scanner->GetPath()); + try + { + Plugin p(_context, path); + p.Load(); + _plugins.push_back(std::move(p)); + } + catch (const std::exception &e) + { + _console.WriteLineError(e.what()); + } + } +} + +void ScriptEngine::StartPlugins() +{ + for (auto& plugin : _plugins) + { + plugin.Start(); + } } void ScriptEngine::Update() diff --git a/src/openrct2/scripting/ScriptEngine.h b/src/openrct2/scripting/ScriptEngine.h index e7673978a9..c978703b29 100644 --- a/src/openrct2/scripting/ScriptEngine.h +++ b/src/openrct2/scripting/ScriptEngine.h @@ -10,6 +10,7 @@ #pragma once #include "../common.h" +#include "Plugin.h" #include #include #include @@ -34,14 +35,19 @@ namespace OpenRCT2::Scripting bool _initialised{}; duk_context * _context{}; std::queue, std::string>> _evalQueue; + std::vector _plugins; public: ScriptEngine(InteractiveConsole& console, IPlatformEnvironment& env); - ScriptEngine(ScriptEngine&&) = delete; + ScriptEngine(ScriptEngine&) = delete; ~ScriptEngine(); - void Initialise(); void Update(); std::future Eval(const std::string &s); + + private: + void Initialise(); + void LoadPlugins(); + void StartPlugins(); }; } diff --git a/src/openrct2/thirdparty/dukglue/dukvalue.h b/src/openrct2/thirdparty/dukglue/dukvalue.h index e0a0782be5..982bad1496 100644 --- a/src/openrct2/thirdparty/dukglue/dukvalue.h +++ b/src/openrct2/thirdparty/dukglue/dukvalue.h @@ -595,4 +595,34 @@ public: duk_get_prop_string(mContext, -1, key.c_str()); return DukValue::take_from_stack(mContext); } + + bool is_array() const + { + push(); + bool result = duk_is_array(mContext, -1); + duk_pop(mContext); + return result; + } + + std::vector as_array() const + { + push(); + if (!duk_is_array(mContext, -1)) + { + duk_pop(mContext); + throw DukException() << "Expected array, got " << type_name(); + } + + auto arrayLength = duk_get_length(mContext, -1); + std::vector result; + result.reserve(arrayLength); + for (size_t i = 0; i < arrayLength; i++) + { + duk_get_prop_index(mContext, -1, i); + result.push_back(take_from_stack(mContext)); + } + + duk_pop(mContext); + return result; + } }; \ No newline at end of file