Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(skyrim-platform): writePlugin/getPluginSourceCode: add overrideFolder arg & support INI setting #2054

Merged
merged 4 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading