#include "custom_scripts.h" #include "Engine/Vfs/initfs.h" #include #include #include #include #include #include #include namespace dingosdk::custom_scripts { namespace { namespace fs = std::filesystem; std::mutex settings_mutex, report_mutex; std::map reports; std::string lower(std::string value) { for (auto& ch : value) if (ch >= 'A' && ch <= 'Z') ch = static_cast(ch + 'a' - 'A'); return value; } std::string utf8(const fs::path& path) { const auto text = path.generic_u8string(); return {reinterpret_cast(text.data()), text.size()}; } std::string read_text(const fs::path& path, std::size_t maximum) { std::ifstream input(path, std::ios::binary | std::ios::ate); if (!input) throw std::runtime_error("Cannot open " + utf8(path.filename())); const auto size = input.tellg(); if (size < 0 || static_cast(size) > maximum) throw std::runtime_error("File exceeds size limit: " + utf8(path.filename())); std::string text(static_cast(size), '\0'); input.seekg(0); if (!text.empty() && !input.read(text.data(), static_cast(text.size()))) throw std::runtime_error("Cannot read " + utf8(path.filename())); if (text.starts_with("\xef\xbb\xbf")) text.erase(0, 3); return text; } Json preferences(const fs::path& root) { const auto path = root / L"ReSkate.custom.json"; Json value = fs::exists(path) ? Json::parse(read_text(path, 65536)) : Json::object(); if (!value.is_object()) throw std::runtime_error("ReSkate.custom.json must contain an object"); if (!value.contains("scripts")) value["scripts"] = Json::object(); if (!value["scripts"].is_object()) throw std::runtime_error("ReSkate.custom.json: scripts must be an object"); return value; } void write_preferences(const fs::path& root, const Json& value) { const auto text = value.dump(2) + '\n'; if (text.size() > 65536) throw std::runtime_error("Custom script preferences exceed 64 KiB"); const auto destination = root / L"ReSkate.custom.json"; static std::atomic sequence{}; const auto temporary = root / (L"ReSkate.custom-" + std::to_wstring(GetCurrentProcessId()) + L"-" + std::to_wstring(GetTickCount64()) + L"-" + std::to_wstring(sequence.fetch_add(1)) + L".tmp"); const auto file = CreateFileW(temporary.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); if (file == INVALID_HANDLE_VALUE) throw std::runtime_error("Cannot save custom script preferences"); DWORD written{}; const bool saved = WriteFile(file, text.data(), static_cast(text.size()), &written, nullptr) && written == text.size() && FlushFileBuffers(file); CloseHandle(file); if (!saved || !MoveFileExW(temporary.c_str(), destination.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { DeleteFileW(temporary.c_str()); throw std::runtime_error("Cannot publish custom script preferences"); } } Json& preference_row(Json& settings, std::string_view id) { auto& rows = settings["scripts"]; const auto key = lower(std::string(id)); if (!rows.contains(key)) rows[key] = Json::object(); if (!rows[key].is_object()) throw std::runtime_error("Invalid preferences for " + std::string(id)); return rows[key]; } bool valid_id(std::string_view id) { return lower(std::string(id)).ends_with(".lua") && initfs::loose_relative_path("Scripts/Custom/" + std::string(id)).has_value(); } } std::vector