diff --git a/build.gradle b/build.gradle index aaafa52f..d4a819ef 100644 --- a/build.gradle +++ b/build.gradle @@ -167,6 +167,9 @@ dependencies { // Forge mods.toml parsing implementation libs.night.config.toml + // quilt.mod.json5 parsing + implementation libs.jankson + // Testing testImplementation(gradleTestKit()) testImplementation(testLibs.spock) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cf5519f7..658ae395 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,7 +20,7 @@ test-retry = "1.5.6" checkstyle = "10.17.0" codenarc = "3.4.0" -# Forge libraries +# Architectury libraries forge-installer-tools = "1.2.0" lorenz = "0.5.3" mcinjector = "3.8.0" @@ -29,6 +29,7 @@ forge-diffpatch = "2.0.7" night-config = "3.6.6" datafixerupper = "6.0.8" at = "1.0.1" +jankson = "1.2.3" [libraries] # Loom compile libraries @@ -56,7 +57,7 @@ fabric-unpick-utils = { module = "net.fabricmc.unpick:unpick-format-utils", vers kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlin-metadata = { module = "org.jetbrains.kotlin:kotlin-metadata-jvm", version.ref = "kotlin" } -# Forge support +# Architectury forge-installer-tools = { module = "net.minecraftforge:installertools", version.ref = "forge-installer-tools" } lorenz = { module = "org.cadixdev:lorenz", version.ref = "lorenz" } mcinjector = { module = "de.oceanlabs.mcp:mcinjector", version.ref = "mcinjector" } @@ -65,6 +66,7 @@ forge-diffpatch = { module = "net.minecraftforge:DiffPatch", version.ref = "forg night-config-toml = { module = "com.electronwill.night-config:toml", version.ref = "night-config" } datafixerupper = { module = "com.mojang:datafixerupper", version.ref = "datafixerupper" } at = { module = "dev.architectury:at", version.ref = "at" } +jankson = { module = "blue.endless:jankson", version.ref = "jankson" } [plugins] kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } diff --git a/src/main/java/dev/architectury/loom/metadata/ModMetadataFiles.java b/src/main/java/dev/architectury/loom/metadata/ModMetadataFiles.java index 42f7a385..e2df6e03 100644 --- a/src/main/java/dev/architectury/loom/metadata/ModMetadataFiles.java +++ b/src/main/java/dev/architectury/loom/metadata/ModMetadataFiles.java @@ -2,12 +2,17 @@ package dev.architectury.loom.metadata; import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; import java.util.function.Function; import java.util.function.Supplier; +import blue.endless.jankson.Jankson; +import blue.endless.jankson.JsonElement; +import blue.endless.jankson.JsonGrammar; +import blue.endless.jankson.api.SyntaxError; import com.google.common.collect.ImmutableMap; import org.gradle.api.Project; import org.gradle.api.logging.Logger; @@ -15,6 +20,7 @@ import org.gradle.api.logging.Logging; import org.gradle.api.tasks.SourceSet; import org.jetbrains.annotations.Nullable; +import net.fabricmc.loom.util.ExceptionUtil; import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.loom.util.gradle.SourceSetHelper; @@ -25,6 +31,7 @@ public final class ModMetadataFiles { private static final Logger LOGGER = Logging.getLogger(ModMetadataFiles.class); private static final Map> SINGLE_FILE_METADATA_TYPES = ImmutableMap.>builder() .put(QuiltModJson.FILE_NAME, QuiltModJson::of) + .put(QuiltModJson.JSON5_FILE_NAME, convertJson5ToJson(QuiltModJson::of)) .put(ArchitecturyCommonJson.FILE_NAME, ArchitecturyCommonJson::of) .put(ModsToml.FILE_PATH, onError(ModsToml::of, "Could not load mods.toml", () -> new ErroringModMetadataFile("mods.toml"))) .put(ModsToml.NEOFORGE_FILE_PATH, onError(ModsToml::of, "Could not load neoforge.mods.toml", () -> new ErroringModMetadataFile("neoforge.mods.toml"))) @@ -41,6 +48,20 @@ public final class ModMetadataFiles { }; } + private static Function convertJson5ToJson(Function next) { + return bytes -> { + var text = new String(bytes, StandardCharsets.UTF_8); + Jankson jankson = Jankson.builder().build(); + String json; + try { + json = jankson.fromJson(text, JsonElement.class).toJson(JsonGrammar.STRICT); + } catch (SyntaxError e) { + throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Could not read JSON5 file", e); + } + return next.apply(json.getBytes(StandardCharsets.UTF_8)); + }; + } + /** * Reads the mod metadata file from a jar. * diff --git a/src/main/java/dev/architectury/loom/metadata/QuiltModJson.java b/src/main/java/dev/architectury/loom/metadata/QuiltModJson.java index 311831b4..aba209cb 100644 --- a/src/main/java/dev/architectury/loom/metadata/QuiltModJson.java +++ b/src/main/java/dev/architectury/loom/metadata/QuiltModJson.java @@ -25,6 +25,8 @@ import net.fabricmc.loom.util.function.CollectionUtil; public final class QuiltModJson implements JsonBackedModMetadataFile, SingleIdModMetadataFile { public static final String FILE_NAME = "quilt.mod.json"; + // See RFC 0083 + public static final String JSON5_FILE_NAME = "quilt.mod.json5"; private static final Logger LOGGER = LoggerFactory.getLogger(QuiltModJson.class); private static final String ACCESS_WIDENER_KEY = "access_widener"; private static final String MIXIN_KEY = "mixin"; diff --git a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java index 28888275..f11e7e02 100644 --- a/src/main/java/net/fabricmc/loom/task/RemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/RemapJarTask.java @@ -25,6 +25,7 @@ package net.fabricmc.loom.task; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; @@ -34,8 +35,13 @@ import java.util.stream.Stream; import javax.inject.Inject; +import blue.endless.jankson.Jankson; +import blue.endless.jankson.JsonElement; +import blue.endless.jankson.JsonGrammar; +import blue.endless.jankson.api.SyntaxError; import com.google.gson.JsonObject; import dev.architectury.loom.extensions.ModBuildExtensions; +import dev.architectury.loom.metadata.QuiltModJson; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.FileCollection; @@ -72,6 +78,7 @@ import net.fabricmc.loom.task.service.MixinRefmapService; import net.fabricmc.loom.task.service.TinyRemapperService; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.ExceptionUtil; +import net.fabricmc.loom.util.FileSystemUtil; import net.fabricmc.loom.util.ModPlatform; import net.fabricmc.loom.util.Pair; import net.fabricmc.loom.util.SidedClassVisitor; @@ -280,6 +287,10 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { ModBuildExtensions.convertAwToAt(serviceFactory, getParameters().getAtAccessWideners().get(), outputFile, mappingsServiceOptions); } + if (getParameters().getPlatform().get() == ModPlatform.QUILT) { + convertQmj5(); + } + if (!getParameters().getPlatform().get().isForgeLike()) { modifyJarManifest(); } @@ -425,6 +436,31 @@ public abstract class RemapJarTask extends AbstractRemapJarTask { ZipUtils.transformJson(JsonObject.class, outputFile, FabricModJsonFactory.FABRIC_MOD_JSON, FabricModJsonUtils::optimizeFmj); } + + private void convertQmj5() throws IOException { + byte[] bytes = ZipUtils.unpackNullable(outputFile, QuiltModJson.JSON5_FILE_NAME); + if (bytes == null) return; + + if (ZipUtils.contains(outputFile, QuiltModJson.FILE_NAME)) { + throw new IllegalStateException("Output file contains both quilt.mod.json and quilt.mod.json5"); + } + + Jankson jankson = Jankson.builder().build(); + JsonElement json; + + try { + json = jankson.fromJson(new String(bytes, StandardCharsets.UTF_8), JsonElement.class); + } catch (SyntaxError e) { + throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Could not read quilt.mod.json5", e); + } + + String qmj = json.toJson(JsonGrammar.STRICT); + + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(outputFile, false)) { + Files.delete(fs.getPath(QuiltModJson.JSON5_FILE_NAME)); + Files.writeString(fs.getPath(QuiltModJson.FILE_NAME), qmj, StandardCharsets.UTF_8); + } + } } @Override diff --git a/src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java b/src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java index d8cabad3..18bd1791 100644 --- a/src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java +++ b/src/main/java/net/fabricmc/loom/task/launch/GenerateDLIConfigTask.java @@ -216,7 +216,8 @@ public abstract class GenerateDLIConfigTask extends AbstractLoomTask { if (quilt) { launchConfig .argument("client", "--version") - .argument("client", "Architectury Loom"); + .argument("client", "Architectury Loom") + .property("loader.enable_quilt_mod_json5_in_dev_env", "true"); } if (platform.isForgeLike()) {