From e5f450eb239b828e4c0ae223e1ff0d024c8266e4 Mon Sep 17 00:00:00 2001 From: JFronny Date: Mon, 14 Aug 2023 17:04:59 +0200 Subject: [PATCH] feat: redesign Dynamic --- build.gradle.kts | 2 +- docs/additional/Migrations.md | 9 ++- run/resourcepacks/testpack/respackopts.json5 | 2 +- .../respackopts/RespackoptsClient.java | 2 +- .../jfronny/respackopts/Respackopts.java | 22 ++++--- .../filters/util/FileExpansionProvider.java | 2 +- .../gson/entry/EnumEntrySerializer.java | 11 ++-- .../mixin/ResourcePackManagerMixin.java | 6 ++ .../model/tree/ConfigBooleanEntry.java | 7 +- .../respackopts/model/tree/ConfigBranch.java | 43 +++++++++--- .../respackopts/model/tree/ConfigEntry.java | 10 ++- .../model/tree/ConfigEnumEntry.java | 65 ++++++++++++++----- .../model/tree/ConfigNumericEntry.java | 7 +- .../jfronny/respackopts/util/MetaCache.java | 5 +- 14 files changed, 129 insertions(+), 64 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5f02cb5..c6e9413 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ repositories { } val fabricVersion = "0.87.0+1.20.1" -val muscriptVersion = "1.3-SNAPSHOT" +val muscriptVersion = "1.4-SNAPSHOT" jfMod { minecraftVersion = "1.20.1" yarn("build.10") diff --git a/docs/additional/Migrations.md b/docs/additional/Migrations.md index 8d6c71a..20a14b7 100644 --- a/docs/additional/Migrations.md +++ b/docs/additional/Migrations.md @@ -67,9 +67,14 @@ Corresponds to version 3.0.0-3.1.0 - Removal of fabric conditions API interop in favor of enabling cleaner syntax ## v9 -Corresponds to version 4.0.0 +Corresponds to version 4.0.0-4.3.1 - New config screen backend powered by LibJF (needed to support serverside) - New translation key syntax - Removal of manual configuration for sliders vs input boxes -- Support for respackopts configs in the pack root (`/respackopts.json5` instead of `/assets/respackopts/conf.json`) \ No newline at end of file +- Support for respackopts configs in the pack root (`/respackopts.json5` instead of `/assets/respackopts/conf.json`) + +## v10 +Corresponds to version 4.4.0 + +- Stricter enforcement of legal entry names: instead of sanitization, unsupported names are logged and ignored \ No newline at end of file diff --git a/run/resourcepacks/testpack/respackopts.json5 b/run/resourcepacks/testpack/respackopts.json5 index 8a51e6b..3110f08 100644 --- a/run/resourcepacks/testpack/respackopts.json5 +++ b/run/resourcepacks/testpack/respackopts.json5 @@ -1,6 +1,6 @@ { id: "lumi", - version: 8, + version: 10, capabilities: ["FileFilter", "DirFilter", "DirFilterAdditive"], conf: { tonemap: [ diff --git a/src/client/java/io/gitlab/jfronny/respackopts/RespackoptsClient.java b/src/client/java/io/gitlab/jfronny/respackopts/RespackoptsClient.java index 1808130..2d77d80 100644 --- a/src/client/java/io/gitlab/jfronny/respackopts/RespackoptsClient.java +++ b/src/client/java/io/gitlab/jfronny/respackopts/RespackoptsClient.java @@ -43,7 +43,7 @@ public class RespackoptsClient implements ClientModInitializer, SaveHook { StringBuilder sb = new StringBuilder(); sb.append("#ifndef respackopts_loaded"); sb.append("\n#define respackopts_loaded"); - MetaCache.forEach((key, state) -> state.configBranch().buildShader(sb, Respackopts.sanitizeString(state.packId()))); + MetaCache.forEach((key, state) -> state.configBranch().buildShader(sb, state.packId())); sb.append("\n#endif"); RespackoptsClient.shaderImportSource = sb.toString(); if (FREX_LOADED) { diff --git a/src/main/java/io/gitlab/jfronny/respackopts/Respackopts.java b/src/main/java/io/gitlab/jfronny/respackopts/Respackopts.java index fbc6ffb..0e9d5da 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/Respackopts.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/Respackopts.java @@ -26,9 +26,10 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.concurrent.CompletableFuture; +import java.util.regex.Pattern; public class Respackopts implements ModInitializer, SaveHook { - public static final Integer META_VERSION = 9; + public static final Integer META_VERSION = 10; public static final String FILE_EXTENSION = ".rpo"; public static final Gson GSON = new GsonBuilder() .registerTypeAdapter(ConfigEnumEntry.class, new EnumEntrySerializer()) @@ -93,15 +94,20 @@ public class Respackopts implements ModInitializer, SaveHook { return CompletableFuture.completedFuture(null); } + // ^ = start of string + // $ = end of string + // * = zero or more times + // [\\s_] = whitespace or underscores + // | = or + // [^a-zA-Z_] = not character or underscore + private static final Pattern UNSUPPORTED = Pattern.compile("[^a-zA-Z_]|^[\\s_]*|[\\s_]*$"); + public static String sanitizeString(String s) { // This trims whitespace/underscores and removes non-alphabetical or underscore characters + return UNSUPPORTED.matcher(s).replaceAll(""); + } - // ^ = start of string - // $ = end of string - // * = zero or more times - // [\\s_] = whitespace or underscores - // | = or - // [^a-zA-Z_] = not character or underscore - return s.replaceAll("[^a-zA-Z_]|^[\\s_]*|[\\s_]*$", ""); + public static boolean isLegal(String s) { + return !UNSUPPORTED.matcher(s).find(); } } diff --git a/src/main/java/io/gitlab/jfronny/respackopts/filters/util/FileExpansionProvider.java b/src/main/java/io/gitlab/jfronny/respackopts/filters/util/FileExpansionProvider.java index b2c41c4..46c6107 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/filters/util/FileExpansionProvider.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/filters/util/FileExpansionProvider.java @@ -10,7 +10,7 @@ import java.io.*; import java.util.Map; public class FileExpansionProvider { - public static synchronized InputStream replace(Dynamic parameter, InputStream is, Map expansions) throws IOException { + public static synchronized InputStream replace(Dynamic parameter, InputStream is, Map expansions) throws IOException { String s = new String(is.readAllBytes()); for (Map.Entry entry : expansions.entrySet()) { s = s.replace("${" + entry.getKey() + "}", entry.getValue().get(parameter)); diff --git a/src/main/java/io/gitlab/jfronny/respackopts/gson/entry/EnumEntrySerializer.java b/src/main/java/io/gitlab/jfronny/respackopts/gson/entry/EnumEntrySerializer.java index d37163a..10d82d1 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/gson/entry/EnumEntrySerializer.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/gson/entry/EnumEntrySerializer.java @@ -5,6 +5,8 @@ import io.gitlab.jfronny.respackopts.Respackopts; import io.gitlab.jfronny.respackopts.model.tree.ConfigEnumEntry; import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; public class EnumEntrySerializer implements JsonSerializer, JsonDeserializer { @Override @@ -29,18 +31,19 @@ public class EnumEntrySerializer implements JsonSerializer, Jso throw new JsonSyntaxException("Expected primitive string key for enum"); } else if (json.isJsonArray()) { - result.values.clear(); + List replacement = new ArrayList<>(); for (JsonElement e : json.getAsJsonArray()) { if (e.isJsonPrimitive() && e.getAsJsonPrimitive().isString()) { - result.values.add(e.getAsString()); + replacement.add(e.getAsString()); } else throw new JsonSyntaxException("Expected string entry in enum"); } - if (result.values.isEmpty()) + result.setValues(replacement); + if (replacement.isEmpty()) Respackopts.LOGGER.warn("Enum entry empty"); else - result.setDefault(result.values.get(0)); + result.setDefault(replacement.get(0)); return result; } else if (json.isJsonObject()) { diff --git a/src/main/java/io/gitlab/jfronny/respackopts/mixin/ResourcePackManagerMixin.java b/src/main/java/io/gitlab/jfronny/respackopts/mixin/ResourcePackManagerMixin.java index f58a7e5..b0fd131 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/mixin/ResourcePackManagerMixin.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/mixin/ResourcePackManagerMixin.java @@ -84,6 +84,12 @@ public class ResourcePackManagerMixin { private static String rpo$readConfiguration(InputStream is, Path dataLocation, String packName, String displayName, Set dataLocations, Set toRemove) throws IOException { try (InputStreamReader isr = new InputStreamReader(is)) { PackMeta conf = Respackopts.GSON.fromJson(isr, PackMeta.class); + if (!Respackopts.isLegal(conf.id)) { + if (conf.version >= 10) { + Respackopts.LOGGER.error(displayName + " was not loaded as it uses an unsupported pack id"); + return null; + } else conf.id = Respackopts.sanitizeString(conf.id); + } if (RespackoptsConfig.debugLogs) Respackopts.LOGGER.info("Discovered pack: " + conf.id); if (Respackopts.META_VERSION < conf.version) { Respackopts.LOGGER.error(displayName + " was not loaded as it specifies a newer respackopts version than is installed"); diff --git a/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigBooleanEntry.java b/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigBooleanEntry.java index c7551f8..c83550e 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigBooleanEntry.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigBooleanEntry.java @@ -7,7 +7,7 @@ import io.gitlab.jfronny.respackopts.RespackoptsConfig; import java.util.Objects; -public class ConfigBooleanEntry extends ConfigEntry { +public class ConfigBooleanEntry extends ConfigEntry implements DBool { public ConfigBooleanEntry(boolean v) { super(Boolean.class); setValue(v); @@ -29,11 +29,6 @@ public class ConfigBooleanEntry extends ConfigEntry { } } - @Override - public DBool getDynamic() { - return this::getValue; - } - @Override public CategoryBuilder buildEntry(GuiEntryBuilderParam args) { return args.builder().value(args.name(), getDefault(), this::getValue, v -> { diff --git a/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigBranch.java b/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigBranch.java index c6e558b..82952a3 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigBranch.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigBranch.java @@ -5,8 +5,9 @@ import io.gitlab.jfronny.gson.reflect.TypeToken; import io.gitlab.jfronny.libjf.config.api.v1.dsl.CategoryBuilder; import io.gitlab.jfronny.libjf.config.api.v1.dsl.ConfigBuilder; import io.gitlab.jfronny.muscript.data.Scope; -import io.gitlab.jfronny.muscript.data.dynamic.*; +import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal; +import io.gitlab.jfronny.muscript.data.dynamic.additional.DelegateDynamic; import io.gitlab.jfronny.respackopts.Respackopts; import io.gitlab.jfronny.respackopts.RespackoptsConfig; import io.gitlab.jfronny.respackopts.integration.SaveHook; @@ -18,12 +19,30 @@ import io.gitlab.jfronny.respackopts.util.MetaCache; import java.nio.file.Path; import java.util.*; -public class ConfigBranch extends ConfigEntry>> { +public class ConfigBranch extends ConfigEntry>> implements DelegateDynamic { public ConfigBranch() { super(new TypeToken>>(){}.getRawType()); setValue(new LinkedHashMap<>()); } + private void checkValue() { + if (version < 10) return; + for (Iterator iterator = getValue().keySet().iterator(); iterator.hasNext(); ) { + String s = iterator.next(); + if (!Respackopts.isLegal(s)) { + Respackopts.LOGGER.error("Illegal entry for " + getName() + ", skipping: " + s); + iterator.remove(); + } + } + } + + @Override + public Map> setValue(Map> value) { + var res = super.setValue(value); + checkValue(); + return res; + } + @Override public void sync(ConfigEntry>> source, ConfigSyncMode mode) { for (Map.Entry> e : source.getValue().entrySet()) { @@ -59,11 +78,15 @@ public class ConfigBranch extends ConfigEntry>> { } public void add(String name, ConfigEntry val) { + if (version >= 10 && !Respackopts.isLegal(name)) { + Respackopts.LOGGER.error("Illegal entry for " + getName() + ", skipping: " + name); + return; + } val.setVersion(version); val.parent = this; super.getValue().put(name, val); } - + public ConfigEntry get(String key) { return super.getValue().get(key); } @@ -76,7 +99,7 @@ public class ConfigBranch extends ConfigEntry>> { } throw new IndexOutOfBoundsException(); } - + public boolean has(String key) { return super.getValue().containsKey(key); } @@ -94,17 +117,20 @@ public class ConfigBranch extends ConfigEntry>> { } @Override - public DObject getDynamic() { - Map> map = new HashMap<>(); + public Dynamic getDelegate() { + Map> map = new HashMap<>(); super.getValue().forEach((key, value) -> { - map.put(Respackopts.sanitizeString(key), value.getDynamic()); + if (Respackopts.isLegal(key)) map.put(key, value); + else if (version >= 10) { + Respackopts.LOGGER.error("Illegal key in " + getName() + ", skipping: " + key); + } else map.put(Respackopts.sanitizeString(key), value); }); return DFinal.of(map); } public Scope addTo(Scope scope) { super.getValue().forEach((key, value) -> { - scope.set(Respackopts.sanitizeString(key), value.getDynamic()); + scope.set(version >= 10 ? key : Respackopts.sanitizeString(key), value); }); return scope; } @@ -166,6 +192,7 @@ public class ConfigBranch extends ConfigEntry>> { for (ConfigEntry value : getValue().values()) { value.setVersion(version); } + checkValue(); } @Override diff --git a/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigEntry.java b/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigEntry.java index 5272730..c633378 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigEntry.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigEntry.java @@ -1,7 +1,7 @@ package io.gitlab.jfronny.respackopts.model.tree; import io.gitlab.jfronny.libjf.config.api.v1.dsl.CategoryBuilder; -import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; +import io.gitlab.jfronny.muscript.data.dynamic.DynamicBase; import io.gitlab.jfronny.respackopts.Respackopts; import io.gitlab.jfronny.respackopts.model.enums.ConfigSyncMode; import io.gitlab.jfronny.respackopts.model.enums.PackReloadType; @@ -9,7 +9,7 @@ import io.gitlab.jfronny.respackopts.util.IndentingStringBuilder; import java.util.Objects; -public abstract class ConfigEntry { +public abstract class ConfigEntry implements DynamicBase { private final Class entryClass; private T defaultValue; private T value; @@ -53,7 +53,7 @@ public abstract class ConfigEntry { this.value = value; return this.value; } - + public T getDefault() { if (defaultValue == null) { defaultValue = getValue(); @@ -76,7 +76,7 @@ public abstract class ConfigEntry { } public void appendString(IndentingStringBuilder sb) { - sb.line(value + " (" + defaultValue + ")"); + sb.line(getValue() + " (" + getDefault() + ")"); } @Override @@ -102,8 +102,6 @@ public abstract class ConfigEntry { public abstract void buildShader(StringBuilder sb, String valueName); - public abstract Dynamic getDynamic(); - public abstract CategoryBuilder buildEntry(GuiEntryBuilderParam args); public Class getEntryClass() { diff --git a/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigEnumEntry.java b/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigEnumEntry.java index e1ab4ac..e9b39e7 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigEnumEntry.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigEnumEntry.java @@ -1,7 +1,9 @@ package io.gitlab.jfronny.respackopts.model.tree; import io.gitlab.jfronny.libjf.config.api.v1.dsl.CategoryBuilder; +import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.dynamic.additional.DEnum; +import io.gitlab.jfronny.muscript.data.dynamic.additional.DelegateDynamic; import io.gitlab.jfronny.respackopts.Respackopts; import io.gitlab.jfronny.respackopts.RespackoptsConfig; import io.gitlab.jfronny.respackopts.model.enums.ConfigSyncMode; @@ -9,8 +11,8 @@ import io.gitlab.jfronny.respackopts.util.IndentingStringBuilder; import java.util.*; -public class ConfigEnumEntry extends ConfigEntry { - public final List values = new ArrayList<>(); +public class ConfigEnumEntry extends ConfigEntry implements DelegateDynamic { + private final List values = new ArrayList<>(); private Integer nextValue; public ConfigEnumEntry() { @@ -46,27 +48,56 @@ public class ConfigEnumEntry extends ConfigEntry { return v; } + public List getValues() { + return List.copyOf(values); + } + + public void setValues(List replacement) { + values.clear(); + values.addAll(replacement); + checkValues(); + } + @Override - public void sync(ConfigEntry source, ConfigSyncMode mode) { - super.sync(source, mode); - ConfigEnumEntry n = (ConfigEnumEntry) source; - if (mode == ConfigSyncMode.RESPACK_LOAD && !n.values.isEmpty()) { - values.clear(); - values.addAll(n.values); - } - if (getValue() == null && nextValue != null) { - if (n.nextValue >= 0 && n.nextValue < values.size()) { - setValue(values.get(n.nextValue)); - } - else { - Respackopts.LOGGER.error("Could not load default value for enum in " + getName()); + public void setVersion(int version) { + super.setVersion(version); + checkValues(); + } + + private void checkValues() { + if (version < 10) return; + for (Iterator iterator = values.iterator(); iterator.hasNext();) { + String value = iterator.next(); + if (!Respackopts.isLegal(value)) { + Respackopts.LOGGER.error("Illegal enum entry for " + getName() + ", skipping: " + value); + iterator.remove(); } } } + @Override + public void sync(ConfigEntry source, ConfigSyncMode mode) { + super.sync(source, mode); + + ConfigEnumEntry n = (ConfigEnumEntry) source; + if (mode == ConfigSyncMode.RESPACK_LOAD && !n.values.isEmpty()) { + setValues(n.values); + } + if (nextValue != null) { + if (getValue() == null) { + if (n.nextValue >= 0 && n.nextValue < values.size()) { + setValue(values.get(n.nextValue)); + } + else { + Respackopts.LOGGER.error("Could not load default value for enum in " + getName()); + } + } else nextValue = null; + } + } + @Override public void appendString(IndentingStringBuilder sb) { - sb.line(getValue() + " (" + getDefault() + ") of:"); + sb.line(getValue() + " (default=" + getDefault() + ") of:"); IndentingStringBuilder isb = sb.indent(); for (String e : values) { isb.line("- " + e); @@ -110,7 +141,7 @@ public class ConfigEnumEntry extends ConfigEntry { } @Override - public DEnum getDynamic() { + public Dynamic getDelegate() { return new DEnum(values, getValue()); } diff --git a/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigNumericEntry.java b/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigNumericEntry.java index 80e321f..4cdb6e3 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigNumericEntry.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/model/tree/ConfigNumericEntry.java @@ -9,7 +9,7 @@ import io.gitlab.jfronny.respackopts.util.IndentingStringBuilder; import java.util.Objects; -public class ConfigNumericEntry extends ConfigEntry { +public class ConfigNumericEntry extends ConfigEntry implements DNumber { public Double min = null; public Double max = null; @@ -60,11 +60,6 @@ public class ConfigNumericEntry extends ConfigEntry { sb.append(getValue().toString()); } - @Override - public DNumber getDynamic() { - return this::getValue; - } - @Override public CategoryBuilder buildEntry(GuiEntryBuilderParam args) { return args.builder().value(args.name(), getValue(), min == null ? Double.NEGATIVE_INFINITY : min, max == null ? Double.POSITIVE_INFINITY : max, this::getValue, v -> { diff --git a/src/main/java/io/gitlab/jfronny/respackopts/util/MetaCache.java b/src/main/java/io/gitlab/jfronny/respackopts/util/MetaCache.java index a7740b4..7c8abb5 100644 --- a/src/main/java/io/gitlab/jfronny/respackopts/util/MetaCache.java +++ b/src/main/java/io/gitlab/jfronny/respackopts/util/MetaCache.java @@ -161,9 +161,8 @@ public class MetaCache { public static Scope getScope(@Nullable CacheKey key) { Scope scope = (key == null ? Respackopts.ROOT_SCOPE : MetaCache.getState(key).executionScope()).fork(); MetaCache.forEach((id, state) -> { - String packId = Respackopts.sanitizeString(state.packId()); - if (!scope.has(packId)) { - scope.set(packId, state.configBranch().getDynamic()); + if (!scope.has(state.packId())) { + scope.set(state.packId(), state.configBranch()); } }); return scope;