Skip to content

Commit

Permalink
feat(skyrim-platform): writePlugin/getPluginSourceCode: add overrideF…
Browse files Browse the repository at this point in the history
…older arg & support INI setting (#2054)
  • Loading branch information
Pospelove authored Jul 1, 2024
1 parent ef9c7dc commit fc574e1
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 33 deletions.
2 changes: 1 addition & 1 deletion 1js/JsEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ class JsValue
{
// A bit ugly reinterpret_cast, but it's a hot path.
// We do not want to modify the ref counter for each argument.
// This is also unit tested, so we would know if it breaks.
// This is also unit tested, so we will know if it breaks.
return i < n ? reinterpret_cast<const JsValue&>(arr[i]) : *undefined;
}

Expand Down
1 change: 1 addition & 0 deletions docs/release/dev/sp-plugins-path-override-folder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
New optional argument `overrideFolder` for `writePlugin` and `getPluginSourceCode` methods: An optional argument `overrideFolder` is now available. This folder can be outside the list of plugin folders defined in `PluginFolders`. While this folder will be writable and readable, SkyrimPlatform will not monitor or load plugins from it. `overrideFolder` is relative to `Data/Platform`.
1 change: 1 addition & 0 deletions docs/release/dev/sp-plugins-path-use.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Updated `writePlugin` and `getPluginSourceCode` methods: These methods now support the `PluginFolders` INI setting.
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ export declare function writeLogs(pluginName: string, ...arguments: unknown[]):
export declare function setPrintConsolePrefixesEnabled(enabled: boolean): void
export declare function callNative(className: string, functionName: string, self?: PapyrusObject, ...args: PapyrusValue[]): PapyrusValue
export declare function getJsMemoryUsage(): number
export declare function getPluginSourceCode(pluginName: string): string
export declare function writePlugin(pluginName: string, newSources: string): string
export declare function getPluginSourceCode(pluginName: string, overrideFolder?: string): string // overrideFolder is relative to Data/Platform
export declare function writePlugin(pluginName: string, newSources: string, overrideFolder?: string): string // overrideFolder is relative to Data/Platform
export declare function getPlatformVersion(): string
export declare function disableCtrlPrtScnHotkey(): void
export declare function blockPapyrusEvents(block: boolean): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export declare function writeLogs(pluginName: string, ...arguments: unknown[]):
export declare function setPrintConsolePrefixesEnabled(enabled: boolean): void
export declare function callNative(className: string, functionName: string, self?: PapyrusObject, ...args: PapyrusValue[]): PapyrusValue
export declare function getJsMemoryUsage(): number
export declare function getPluginSourceCode(pluginName: string): string
export declare function writePlugin(pluginName: string, newSources: string): string
export declare function getPluginSourceCode(pluginName: string, overrideFolder?: string): string // overrideFolder is relative to Data/Platform
export declare function writePlugin(pluginName: string, newSources: string, overrideFolder?: string): string // overrideFolder is relative to Data/Platform
export declare function getPlatformVersion(): string
export declare function disableCtrlPrtScnHotkey(): void
export declare function blockPapyrusEvents(block: boolean): void
Expand Down
79 changes: 74 additions & 5 deletions skyrim-platform/src/platform_se/skyrim_platform/DevApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,28 @@
#include "InvalidArgumentException.h"
#include "NullPointerException.h"
#include "PapyrusTESModPlatform.h"
#include "Settings.h"
#include "Validators.h"

std::shared_ptr<JsEngine> DevApi::jsEngine = nullptr;
DevApi::NativeExportsMap DevApi::nativeExportsMap;

namespace {
bool CreateDirectoryRecursive(const std::string& dirName, std::error_code& err)
{
err.clear();
if (!std::filesystem::create_directories(dirName, err)) {
if (std::filesystem::exists(dirName)) {
// The folder already exists:
err.clear();
return true;
}
return false;
}
return true;
}
}

JsValue DevApi::Require(
const JsFunctionArguments& args,
const std::vector<std::filesystem::path>& pluginLoadDirectories)
Expand Down Expand Up @@ -64,20 +81,55 @@ JsValue DevApi::AddNativeExports(const JsFunctionArguments& args)
}

namespace {
std::filesystem::path GetPluginPath(const std::string& pluginName)
std::filesystem::path GetPluginPath(const std::string& pluginName,
std::optional<std::string> folderOverride)
{
if (!ValidateFilename(pluginName, /*allowDots*/ false)) {
throw InvalidArgumentException("pluginName", pluginName);
}
return std::filesystem::path("Data/Platform/Plugins") / (pluginName + ".js");

// Folder override is alowed to be not in list of plugin folders
// In this case it will be writable, but SkyrimPlatform will not monitor and
// load plugins from it.
if (folderOverride) {
if (!ValidateFilename(folderOverride->data(), /*allowDots*/ false)) {
throw InvalidArgumentException("folderOverride", *folderOverride);
}

return std::filesystem::path("Data/Platform") / *folderOverride /
(pluginName + ".js");
}

auto pluginFolders = Settings::GetPlatformSettings()->GetPluginFolders();

if (!pluginFolders) {
throw NullPointerException("pluginFolders");
}

if (pluginFolders->empty()) {
throw std::runtime_error("No plugin folders found");
}

auto folder = pluginFolders->front();

return folder / (pluginName + ".js");
}
}

JsValue DevApi::GetPluginSourceCode(const JsFunctionArguments& args)
{
// TODO: Support multifile plugins?
auto pluginName = args[1].ToString();
return Viet::ReadFileIntoString(GetPluginPath(pluginName));

std::optional<std::string> overrideFolder;
if (args.GetSize() >= 3) {
auto t = args[2].GetType();
if (t != JsValue::Type::Undefined && t != JsValue::Type::Null) {
overrideFolder = args[2].ToString();
}
}

return Viet::ReadFileIntoString(GetPluginPath(pluginName, overrideFolder));
}

JsValue DevApi::WritePlugin(const JsFunctionArguments& args)
Expand All @@ -86,13 +138,30 @@ JsValue DevApi::WritePlugin(const JsFunctionArguments& args)
auto pluginName = args[1].ToString();
auto newSources = args[2].ToString();

auto path = GetPluginPath(pluginName);
std::optional<std::string> overrideFolder;
if (args.GetSize() >= 4) {
auto t = args[3].GetType();
if (t != JsValue::Type::Undefined && t != JsValue::Type::Null) {
overrideFolder = args[3].ToString();
}
}

auto path = GetPluginPath(pluginName, overrideFolder);

std::error_code err;
CreateDirectoryRecursive(path.parent_path().string(), err);
if (err) {
throw std::runtime_error("Failed to create directory " +
path.parent_path().string() + ": " +
err.message());
}

std::ofstream f(path);
f << newSources;
f.close();
if (!f)
if (!f) {
throw std::runtime_error("Failed to write into " + path.string());
}
return JsValue::Undefined();
}

Expand Down
42 changes: 42 additions & 0 deletions skyrim-platform/src/platform_se/skyrim_platform/Settings.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
#pragma once
#include <filesystem>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>

namespace Settings {

Expand Down Expand Up @@ -171,6 +177,42 @@ class File
return ini.GetValue(section, key, defaultValue);
}

std::unique_ptr<std::vector<std::filesystem::path>> GetPluginFolders()
{
return GetPathsSemicolonSeparated(
"Main", "PluginFolders",
"Data/Platform/Plugins;Data/Platform/PluginsDev");
}

std::unique_ptr<std::vector<std::filesystem::path>>
GetPathsSemicolonSeparated(const char* section, const char* key,
const char* defaultValue)
{
std::string utf8pluginFoldersSemicolonSeparated =
GetString("Main", "PluginFolders",
"Data/Platform/Plugins;Data/Platform/PluginsDev");

std::istringstream ss(utf8pluginFoldersSemicolonSeparated);
std::string folder;

if (utf8pluginFoldersSemicolonSeparated.find_first_of("\"") !=
std::string::npos) {
throw std::runtime_error(
"Invalid path with quotes in PluginFolders setting. Please remove "
"quotes and restart the game.");
}

auto result = std::make_unique<std::vector<std::filesystem::path>>();

while (std::getline(ss, folder, ';')) {
if (!folder.empty()) {
result->emplace_back(folder);
}
}

return result;
}

bool SetString(const char* section, const char* key, const char* value,
const char* comment = nullptr)
{
Expand Down
27 changes: 4 additions & 23 deletions skyrim-platform/src/platform_se/skyrim_platform/SkyrimPlatform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,31 +130,12 @@ class CommonExecutionListener : public TickListener
const std::vector<std::filesystem::path>& GetFileDirs() const
{
if (!pluginFolders) {
auto settings = Settings::GetPlatformSettings();
std::string utf8pluginFoldersSemicolonSeparated =
settings->GetString("Main", "PluginFolders",
"Data/Platform/Plugins;Data/Platform/PluginsDev");

std::istringstream ss(utf8pluginFoldersSemicolonSeparated);
std::string folder;

if (utf8pluginFoldersSemicolonSeparated.find_first_of("\"") !=
std::string::npos) {
try {
pluginFolders = Settings::GetPlatformSettings()->GetPluginFolders();
} catch (std::exception& e) {
pluginFolders = std::make_unique<std::vector<std::filesystem::path>>();
throw std::runtime_error(
"Invalid path with quotes in PluginFolders setting. Please remove "
"quotes and restart the game.");
throw;
}

auto result = std::make_unique<std::vector<std::filesystem::path>>();

while (std::getline(ss, folder, ';')) {
if (!folder.empty()) {
result->emplace_back(folder);
}
}

pluginFolders = std::move(result);
}

return *pluginFolders;
Expand Down

0 comments on commit fc574e1

Please sign in to comment.