From a0e055d31a4ddeb4fae515e7c94b41a49a99334e Mon Sep 17 00:00:00 2001 From: Su5eD Date: Sun, 22 Oct 2023 17:48:59 +0200 Subject: [PATCH] Add Global Mod Aliases config file Relates to #399 --- README.md | 32 ++++ gradle.properties | 4 +- .../sinytra/connector/ConnectorUtil.java | 2 +- .../connector/locator/DependencyResolver.java | 3 +- .../connector/locator/GlobalModAliases.java | 137 ++++++++++++++++++ 5 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 src/main/java/dev/su5ed/sinytra/connector/locator/GlobalModAliases.java diff --git a/README.md b/README.md index 9e687668..ec1b09f5 100644 --- a/README.md +++ b/README.md @@ -119,3 +119,35 @@ Here's a few tips to help get your PR approved: * A PR should be focused on content, rather than syntax changes. * Use the file you are editing as a style guide. * Make sure your feature isn't already in the works, or hasn't been rejected previously. + +## Configuration + +### Global Mod Aliases + +To improve mod compatibility, Connector provides a Global Mod Alias feature that can be used to provide alternative IDs +for mods in the fabric loader. Similar to fabric's [Dependency Overrides](https://fabricmc.net/wiki/tutorial:dependency_overrides), +it uses a json config file to define aliases. + +Global Mod Aliases are defined in a file named `connector_global_mod_aliases.json`, located inside your config folder. +If it doesn't exist yet, Connector will create a new one with its default mod aliases. + +Here's a minimal configuration example: +```json +{ + "version": 1, + "aliases": { + "cloth_config": "cloth-config2", + "embeddium": [ + "sodium", + "rubidium" + ] + } +} +``` + +Let's go over it line-by-line. +- First, we have `version`, which specifies the config file spec version we would like to use. + At the time of writing, the latest version is version 1. +- Secondly, we have `aliases`. This JSON object contains all of our alises for various mods. + Keys inside the object represent mod IDs to be aliased. The value can be either a single **string**, or an **array** in case + we want to provide multiple aliases for one mod. diff --git a/gradle.properties b/gradle.properties index 6241232d..9e74c1a8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,14 +6,14 @@ org.gradle.daemon=true # Versions versionConnector=1.0.0-beta.20 versionAdapter=1.7.0-1.20.1-20231012.193807 -versionAdapterDefinition=1.8.0 +versionAdapterDefinition=1.8.1 versionMc=1.20.1 versionForge=47.1.3 versionForgeAutoRenamingTool=1.0.8 versionFabricLoader=2.5.1+0.14.21+1.20.1 versionAccessWidener=2.1.0 -versionFabricApi=0.90.0+1.9.26+1.20.1 +versionFabricApi=0.90.0+1.9.27+1.20.1 versionMixin=0.12.5+mixin.0.8.5 versionMixinTransmog=0.4.0+1.20.1 diff --git a/src/main/java/dev/su5ed/sinytra/connector/ConnectorUtil.java b/src/main/java/dev/su5ed/sinytra/connector/ConnectorUtil.java index fcf8f2d7..b1094abf 100644 --- a/src/main/java/dev/su5ed/sinytra/connector/ConnectorUtil.java +++ b/src/main/java/dev/su5ed/sinytra/connector/ConnectorUtil.java @@ -103,7 +103,7 @@ public final class ConnectorUtil { ); // Common aliased mod dependencies that don't work with forge ports, which use a different modid. // They're too annoying to override individually in each mod, so we provide this small QoL feature for the user's comfort - public static final Multimap GLOBAL_MOD_ALIASES = ImmutableMultimap.of( + public static final Multimap DEFAULT_GLOBAL_MOD_ALIASES = ImmutableMultimap.of( "cloth_config", "cloth-config2" ); diff --git a/src/main/java/dev/su5ed/sinytra/connector/locator/DependencyResolver.java b/src/main/java/dev/su5ed/sinytra/connector/locator/DependencyResolver.java index 9f88b066..1dd6e67e 100644 --- a/src/main/java/dev/su5ed/sinytra/connector/locator/DependencyResolver.java +++ b/src/main/java/dev/su5ed/sinytra/connector/locator/DependencyResolver.java @@ -42,10 +42,11 @@ public final class DependencyResolver { private static final Logger LOGGER = LogUtils.getLogger(); public static final VersionOverrides VERSION_OVERRIDES = new VersionOverrides(); public static final DependencyOverrides DEPENDENCY_OVERRIDES = new DependencyOverrides(FMLPaths.CONFIGDIR.get()); + private static final GlobalModAliases GLOBAL_MOD_ALIASES = new GlobalModAliases(FMLPaths.CONFIGDIR.get(), ConnectorUtil.DEFAULT_GLOBAL_MOD_ALIASES); public static List resolveDependencies(Collection keys, Multimap jars, Iterable loadedMods) { // Add global mod aliases - FabricLoaderImpl.INSTANCE.aliasMods(ConnectorUtil.GLOBAL_MOD_ALIASES); + FabricLoaderImpl.INSTANCE.aliasMods(GLOBAL_MOD_ALIASES.getAliases()); BiMap jarToCandidate = HashBiMap.create(); // Fabric candidates List candidates = createCandidatesRecursive(keys, keys, jars, jarToCandidate); diff --git a/src/main/java/dev/su5ed/sinytra/connector/locator/GlobalModAliases.java b/src/main/java/dev/su5ed/sinytra/connector/locator/GlobalModAliases.java new file mode 100644 index 00000000..5d6b73ea --- /dev/null +++ b/src/main/java/dev/su5ed/sinytra/connector/locator/GlobalModAliases.java @@ -0,0 +1,137 @@ +package dev.su5ed.sinytra.connector.locator; + +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.stream.JsonWriter; +import com.mojang.logging.LogUtils; +import net.fabricmc.loader.impl.FormattedException; +import net.fabricmc.loader.impl.lib.gson.JsonReader; +import net.fabricmc.loader.impl.lib.gson.JsonToken; +import net.fabricmc.loader.impl.metadata.ParseMetadataException; +import net.fabricmc.loader.impl.util.LoaderUtil; +import org.slf4j.Logger; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Map; + +public class GlobalModAliases { + private static final Logger LOGGER = LogUtils.getLogger(); + + private final Multimap aliases; + + public GlobalModAliases(Path configDir, Multimap defaultValues) { + Path path = configDir.resolve("connector_global_mod_aliases.json"); + + if (!Files.exists(path)) { + this.aliases = ImmutableMultimap.copyOf(defaultValues); + write(path); + return; + } + + try (JsonReader reader = new JsonReader(new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8))) { + this.aliases = parse(reader); + } catch (IOException | ParseMetadataException e) { + throw FormattedException.ofLocalized("exception.parsingOverride", "Failed to parse " + LoaderUtil.normalizePath(path), e); + } + } + + public Multimap getAliases() { + return this.aliases; + } + + private Multimap parse(JsonReader reader) throws IOException, ParseMetadataException { + if (reader.peek() != JsonToken.BEGIN_OBJECT) { + throw new ParseMetadataException("Root must be an object", reader); + } + + reader.beginObject(); + + if (!reader.nextName().equals("version")) { + throw new ParseMetadataException("First key must be \"version\"", reader); + } + + if (reader.peek() != JsonToken.NUMBER || reader.nextInt() != 1) { + throw new ParseMetadataException("Unsupported \"version\", must be 1", reader); + } + + ImmutableMultimap.Builder aliases = ImmutableMultimap.builder(); + + while (reader.hasNext()) { + String key = reader.nextName(); + + if ("aliases".equals(key)) { + reader.beginObject(); + + while (reader.hasNext()) { + String modid = reader.nextName(); + + switch (reader.peek()) { + case STRING -> { + String alias = reader.nextString(); + aliases.put(modid, alias); + } + case BEGIN_ARRAY -> { + reader.beginArray(); + while (reader.hasNext()) { + String alias = reader.nextString(); + aliases.put(modid, alias); + } + reader.endArray(); + } + default -> throw new ParseMetadataException("Mod aliases must be a string or string array!", reader); + } + } + + reader.endObject(); + } + else { + throw new ParseMetadataException("Unsupported root key: " + key, reader); + } + } + + reader.endObject(); + + return aliases.build(); + } + + private void write(Path path) { + Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); + try { + Files.createDirectories(path.getParent()); + + try (JsonWriter writer = gson.newJsonWriter(Files.newBufferedWriter(path))) { + writer.beginObject(); + + writer.name("version").value(1); + + writer.name("aliases").beginObject(); + for (Map.Entry> entry : this.aliases.asMap().entrySet()) { + Collection values = entry.getValue(); + writer.name(entry.getKey()); + if (values.size() == 1) { + writer.value(values.iterator().next()); + } + else { + writer.beginArray(); + for (String value : values) { + writer.value(value); + } + writer.endArray(); + } + } + writer.endObject(); + + writer.endObject(); + } + } catch (IOException e) { + LOGGER.error("Error writing default global mod aliases", e); + } + } +}