mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-27 18:37:09 +01:00
Mods: read bounded two-sided variant addition declarations
This commit is contained in:
1 parent
e9233f25f1
commit
42db7a7d38
3 files changed
+62
No files matched your search
@@ -93,6 +93,23 @@ int main() {
|
||||
"A disabled pack still decided what loads");
|
||||
check(mods::variant_of("Pack#wide") == "wide" && mods::variant_owner("Pack#wide") == "Pack" && mods::variant_owner("Pack").empty(),
|
||||
"A variant's entry does not name its variant and mod");
|
||||
const auto cooperation = [&](const std::string& policy) {
|
||||
write(mods_root / "Pack" / "reskate-plugin.json",
|
||||
"{\"api\":1,\"variants\":{\"folder\":\"decks\",\"displaces\":[\"deck_bundle\"]},\"variant_additions\":" + policy + "}");
|
||||
std::vector<std::string> notes;
|
||||
return mods::describe_mod(mods_root / "Pack", "Pack", notes);
|
||||
};
|
||||
auto permitted = cooperation(R"({"accept":["WIN32/TEST/DECK"],"contribute":["win32/test/other"]})");
|
||||
check(permitted.variant_additions.accept == std::vector<std::string>{"win32/test/deck"} &&
|
||||
permitted.variant_additions.contribute == std::vector<std::string>{"win32/test/other"}, "Both exact bundle permissions are parsed");
|
||||
for (const auto* invalid : {R"({"accept":true})", R"({"accept":["win32/*"]})", R"({"accept":["win32/../deck"]})",
|
||||
R"({"accept":["win32/test","WIN32/TEST"]})", R"({"accept":["win32/test"],"contribute":[3]})"}) {
|
||||
permitted = cooperation(invalid);
|
||||
check(permitted.variants && permitted.variants->displaces == std::vector<std::string>{"deck_bundle"},
|
||||
"Malformed cooperation retains the author's displacement rule");
|
||||
check(permitted.variant_additions.accept.empty() && permitted.variant_additions.contribute.empty(),
|
||||
"Malformed cooperation grants neither side, even after a valid first field");
|
||||
}
|
||||
fs::remove_all(root);
|
||||
std::cout << "Mod variants: order, choice, fallback, set aside, disabled and malformed passed.\n";
|
||||
}
|
||||
@@ -232,6 +232,43 @@ void read_variants(Mod& mod, std::vector<std::string>& notes) {
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid cooperation never removes an existing displacement rule. Read this
|
||||
// independently of variants, and publish neither side of a partial declaration.
|
||||
void read_variant_additions(Mod& mod, std::vector<std::string>& notes) {
|
||||
const auto path = mod.directory / plugin_manifest_file;
|
||||
std::error_code error;
|
||||
if (!fs::is_regular_file(path, error) || error) return;
|
||||
try {
|
||||
const auto root = parse(path, maximum_plugin_bytes);
|
||||
if (!root.is_object() || !root.contains("variant_additions")) return;
|
||||
const auto& policy = root.at("variant_additions");
|
||||
if (!policy.is_object()) throw std::runtime_error("variant_additions is an object");
|
||||
const auto list = [&](const char* field) {
|
||||
std::vector<std::string> result;
|
||||
if (!policy.contains(field)) return result;
|
||||
const auto& values = policy.at(field);
|
||||
if (!values.is_array() || values.size() > 16) throw std::runtime_error("at most 16 exact bundle names per side");
|
||||
for (const auto& value : values) {
|
||||
if (!value.is_string() || value.string().size() > 260) throw std::runtime_error("bundle name must be a string of at most 260 characters");
|
||||
auto name = lower(value.string());
|
||||
if (!name.starts_with("win32/") || name.find("..") != std::string::npos ||
|
||||
name.find("//") != std::string::npos || name.back() == '/' ||
|
||||
std::ranges::any_of(name, [](unsigned char c) { return c <= 0x20 || c >= 0x7f || c == '\\' || c == '*' || c == '?'; }))
|
||||
throw std::runtime_error("an exact win32 bundle name is required");
|
||||
if (std::ranges::find(result, name) != result.end()) throw std::runtime_error("repeated bundle permission");
|
||||
result.push_back(std::move(name));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
VariantAdditions parsed;
|
||||
parsed.accept = list("accept");
|
||||
parsed.contribute = list("contribute");
|
||||
mod.variant_additions = std::move(parsed);
|
||||
} catch (const std::exception& failure) {
|
||||
notes.push_back("Mod " + mod.name + ": private variant additions refused (" + failure.what() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
// Whether reskate-plugin.json names code to run. A file with only "resident" or "variants" serves
|
||||
// the mod's game data and is no plugin. The plugin host reports what else is wrong with it.
|
||||
void read_plugin_entry(Mod& mod) {
|
||||
@@ -498,6 +535,7 @@ Mod describe_mod(const std::filesystem::path& directory, std::string name, std::
|
||||
read_levels(mod);
|
||||
read_resident(mod, notes);
|
||||
read_variants(mod, notes);
|
||||
read_variant_additions(mod, notes);
|
||||
read_plugin_entry(mod);
|
||||
read_studio_marker(mod);
|
||||
return mod;
|
||||
|
||||
@@ -71,6 +71,12 @@ struct Variants {
|
||||
std::vector<std::string> displaces;
|
||||
};
|
||||
|
||||
// Separate from displacement: both authors must opt into a bounded native
|
||||
// compatibility check. Exact bundle names are used, never prefixes or globs.
|
||||
struct VariantAdditions {
|
||||
std::vector<std::string> accept, contribute;
|
||||
};
|
||||
|
||||
struct Mod {
|
||||
std::string name; // folder name directly under Mods/
|
||||
std::filesystem::path directory;
|
||||
@@ -96,6 +102,7 @@ struct Mod {
|
||||
std::vector<ResidentAssets> resident;
|
||||
// From reskate-plugin.json: the variants it holds (Variants), if it declares any.
|
||||
std::optional<Variants> variants;
|
||||
VariantAdditions variant_additions;
|
||||
// From .reskate-studio-patch: whether the mod has one, and the Skate.exe SHA-256 it records.
|
||||
bool studio_marker{};
|
||||
std::string skate_sha256;
|
||||
|
||||
Reference in new issue
Block a user