From 4271b0ec7bc39c3e9fb86d008fd8c9f8d534f4df Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 5 Feb 2025 09:03:58 +0100 Subject: [PATCH 01/14] stage1/ml9: Support NeoForge --- stage1/modlauncher9/build.gradle | 3 ++- .../stage1/EssentialTransformationService.java | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/stage1/modlauncher9/build.gradle b/stage1/modlauncher9/build.gradle index 6cbb65f..c085c3e 100644 --- a/stage1/modlauncher9/build.gradle +++ b/stage1/modlauncher9/build.gradle @@ -9,5 +9,6 @@ dependencies { compileOnly("net.sf.jopt-simple:jopt-simple:5.0.4") compileOnly("org.jetbrains:annotations:21.0.1") - compileOnly("net.minecraftforge:fmlloader:1.17.1-37.0.82") + // provided by fmlloader (both forge and neoforge ones) + compileOnly("org.apache.logging.log4j:log4j-api:2.14.1") } diff --git a/stage1/modlauncher9/src/main/java/gg/essential/loader/stage1/EssentialTransformationService.java b/stage1/modlauncher9/src/main/java/gg/essential/loader/stage1/EssentialTransformationService.java index 9fa54d5..8bc60ad 100644 --- a/stage1/modlauncher9/src/main/java/gg/essential/loader/stage1/EssentialTransformationService.java +++ b/stage1/modlauncher9/src/main/java/gg/essential/loader/stage1/EssentialTransformationService.java @@ -4,7 +4,6 @@ import cpw.mods.modlauncher.api.NamedPath; import gg.essential.loader.stage1.util.FallbackTransformationService; import gg.essential.loader.stage1.util.IDelegatingTransformationService; -import net.minecraftforge.fml.loading.ModDirTransformerDiscoverer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -44,7 +43,18 @@ private void setupSourceFile(final Path sourceFile) throws Exception { // Forge will by default ignore a mod file if it contains an implementation of ITransformationService // So we need to remove ourselves from that exclusion list - Field foundField = ModDirTransformerDiscoverer.class.getDeclaredField("found"); + Class cls; + try { + cls = Class.forName("net.minecraftforge.fml.loading.ModDirTransformerDiscoverer"); + } catch (ClassNotFoundException e1) { + try { + cls = Class.forName("net.neoforged.fml.loading.ModDirTransformerDiscoverer"); + } catch (ClassNotFoundException e2) { + e2.addSuppressed(e1); + throw e2; + } + } + Field foundField = cls.getDeclaredField("found"); foundField.setAccessible(true); ((List) foundField.get(null)).removeIf(namedPath -> Arrays.stream(namedPath.paths()).anyMatch(path -> path.normalize().equals(normalizedSourceFile))); From d98c04399788c7f9c7b5886f1b7f2f45f444eb1f Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 5 Feb 2025 09:04:55 +0100 Subject: [PATCH 02/14] stage2/ml9: Move forge37 ModLocator into dedicated subproject --- build.gradle | 1 + settings.gradle.kts | 1 + stage2/modlauncher9/build.gradle | 1 + stage2/modlauncher9/forge37/build.gradle | 9 +++++++++ .../stage2/modlauncher/Forge_37_0_0_ModLocator.java | 0 5 files changed, 12 insertions(+) create mode 100644 stage2/modlauncher9/forge37/build.gradle rename stage2/modlauncher9/{ => forge37}/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_37_0_0_ModLocator.java (100%) diff --git a/build.gradle b/build.gradle index 3aae2ec..e8239ec 100644 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,7 @@ subprojects { def javaVersions = [ "compatibility": 16, "modlauncher9": 16, + "forge37": 16, "forge40": 17, "forge49": 17, "modlauncher10": 17, diff --git a/settings.gradle.kts b/settings.gradle.kts index 3de994f..0263a95 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,6 +23,7 @@ include(":stage2:modlauncher") include(":stage2:modlauncher8") include(":stage2:modlauncher9") include(":stage2:modlauncher9:compatibility") +include(":stage2:modlauncher9:forge37") include(":stage2:modlauncher9:forge40") include(":stage2:modlauncher9:forge49") include(":stage2:modlauncher9:modlauncher10") diff --git a/stage2/modlauncher9/build.gradle b/stage2/modlauncher9/build.gradle index 3c50a53..fb8985b 100644 --- a/stage2/modlauncher9/build.gradle +++ b/stage2/modlauncher9/build.gradle @@ -4,6 +4,7 @@ repositories { dependencies { bundle(implementation(project("compatibility"))) + bundle(project("forge37")) bundle(project("forge40")) bundle(project("forge49")) bundle(project("modlauncher10")) diff --git a/stage2/modlauncher9/forge37/build.gradle b/stage2/modlauncher9/forge37/build.gradle new file mode 100644 index 0000000..28d40f0 --- /dev/null +++ b/stage2/modlauncher9/forge37/build.gradle @@ -0,0 +1,9 @@ +repositories { + maven { url "https://maven.minecraftforge.net/" } +} + +dependencies { + compileOnly(parent.project("compatibility")) + + compileOnly("net.minecraftforge:fmlloader:1.17.1-37.0.82") +} diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_37_0_0_ModLocator.java b/stage2/modlauncher9/forge37/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_37_0_0_ModLocator.java similarity index 100% rename from stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_37_0_0_ModLocator.java rename to stage2/modlauncher9/forge37/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_37_0_0_ModLocator.java From 6692340b534f7be71ea6e39da7611eda60672116 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 5 Feb 2025 09:30:20 +0100 Subject: [PATCH 03/14] stage2/ml9: Move forge41 ModLocator into dedicated subproject --- build.gradle | 1 + settings.gradle.kts | 1 + stage2/modlauncher9/build.gradle | 1 + stage2/modlauncher9/forge41/build.gradle | 9 +++++++++ .../stage2/modlauncher/Forge_41_0_34_ModLocator.java | 0 5 files changed, 12 insertions(+) create mode 100644 stage2/modlauncher9/forge41/build.gradle rename stage2/modlauncher9/{modlauncher10 => forge41}/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_41_0_34_ModLocator.java (100%) diff --git a/build.gradle b/build.gradle index e8239ec..0cd5df1 100644 --- a/build.gradle +++ b/build.gradle @@ -11,6 +11,7 @@ subprojects { "modlauncher9": 16, "forge37": 16, "forge40": 17, + "forge41": 17, "forge49": 17, "modlauncher10": 17, ] diff --git a/settings.gradle.kts b/settings.gradle.kts index 0263a95..9cb7862 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,6 +25,7 @@ include(":stage2:modlauncher9") include(":stage2:modlauncher9:compatibility") include(":stage2:modlauncher9:forge37") include(":stage2:modlauncher9:forge40") +include(":stage2:modlauncher9:forge41") include(":stage2:modlauncher9:forge49") include(":stage2:modlauncher9:modlauncher10") include(":integrationTest:common") diff --git a/stage2/modlauncher9/build.gradle b/stage2/modlauncher9/build.gradle index fb8985b..a89b08e 100644 --- a/stage2/modlauncher9/build.gradle +++ b/stage2/modlauncher9/build.gradle @@ -6,6 +6,7 @@ dependencies { bundle(implementation(project("compatibility"))) bundle(project("forge37")) bundle(project("forge40")) + bundle(project("forge41")) bundle(project("forge49")) bundle(project("modlauncher10")) diff --git a/stage2/modlauncher9/forge41/build.gradle b/stage2/modlauncher9/forge41/build.gradle new file mode 100644 index 0000000..ac0d23f --- /dev/null +++ b/stage2/modlauncher9/forge41/build.gradle @@ -0,0 +1,9 @@ +repositories { + maven { url "https://maven.minecraftforge.net/" } +} + +dependencies { + compileOnly(parent.project("compatibility")) + + compileOnly("net.minecraftforge:fmlloader:1.19-41.0.64") +} diff --git a/stage2/modlauncher9/modlauncher10/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_41_0_34_ModLocator.java b/stage2/modlauncher9/forge41/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_41_0_34_ModLocator.java similarity index 100% rename from stage2/modlauncher9/modlauncher10/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_41_0_34_ModLocator.java rename to stage2/modlauncher9/forge41/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_41_0_34_ModLocator.java From 291da689b3f9dd32917ab2c1527937cbe279a604 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 5 Feb 2025 09:33:24 +0100 Subject: [PATCH 04/14] stage2/ml9: Remove direct Forge dependency from `compatibility` sub-project --- .../modlauncher9/compatibility/build.gradle | 1 - .../modlauncher/EssentialModLocator.java | 12 ++---- stage2/modlauncher9/forge37/build.gradle | 1 + .../EssentialModLocator_Forge.java | 39 +++++++++++++++++++ .../modlauncher/Forge_37_0_0_ModLocator.java | 3 +- stage2/modlauncher9/forge40/build.gradle | 1 + .../modlauncher/Forge_40_1_60_ModLocator.java | 3 +- stage2/modlauncher9/forge41/build.gradle | 1 + .../modlauncher/Forge_41_0_34_ModLocator.java | 3 +- stage2/modlauncher9/forge49/build.gradle | 1 + .../modlauncher/Forge_49_0_38_ModLocator.java | 3 +- .../modlauncher9/modlauncher10/build.gradle | 3 -- .../EssentialTransformationService.java | 22 +---------- 13 files changed, 56 insertions(+), 37 deletions(-) create mode 100644 stage2/modlauncher9/forge37/src/main/java/gg/essential/loader/stage2/modlauncher/EssentialModLocator_Forge.java diff --git a/stage2/modlauncher9/compatibility/build.gradle b/stage2/modlauncher9/compatibility/build.gradle index faaf246..674ec76 100644 --- a/stage2/modlauncher9/compatibility/build.gradle +++ b/stage2/modlauncher9/compatibility/build.gradle @@ -5,5 +5,4 @@ repositories { dependencies { compileOnly("cpw.mods:securejarhandler:0.9.50") compileOnly("cpw.mods:modlauncher:9.0.7") - compileOnly("net.minecraftforge:fmlloader:1.17.1-37.0.82") } \ No newline at end of file diff --git a/stage2/modlauncher9/compatibility/src/main/java/gg/essential/loader/stage2/modlauncher/EssentialModLocator.java b/stage2/modlauncher9/compatibility/src/main/java/gg/essential/loader/stage2/modlauncher/EssentialModLocator.java index a3c7aac..bc7c2d6 100644 --- a/stage2/modlauncher9/compatibility/src/main/java/gg/essential/loader/stage2/modlauncher/EssentialModLocator.java +++ b/stage2/modlauncher9/compatibility/src/main/java/gg/essential/loader/stage2/modlauncher/EssentialModLocator.java @@ -1,13 +1,9 @@ package gg.essential.loader.stage2.modlauncher; -import net.minecraftforge.fml.loading.moddiscovery.ModFile; -import net.minecraftforge.forgespi.locating.IModFile; -import net.minecraftforge.forgespi.locating.IModLocator; +import cpw.mods.jarhandling.SecureJar; -import java.nio.file.Path; -import java.util.function.Consumer; -import java.util.stream.Stream; +import java.util.List; -public interface EssentialModLocator extends IModLocator { - Iterable scanMods(Stream paths); +public interface EssentialModLocator { + boolean injectMods(List modJars) throws ReflectiveOperationException; } diff --git a/stage2/modlauncher9/forge37/build.gradle b/stage2/modlauncher9/forge37/build.gradle index 28d40f0..ea01b97 100644 --- a/stage2/modlauncher9/forge37/build.gradle +++ b/stage2/modlauncher9/forge37/build.gradle @@ -6,4 +6,5 @@ dependencies { compileOnly(parent.project("compatibility")) compileOnly("net.minecraftforge:fmlloader:1.17.1-37.0.82") + compileOnly("cpw.mods:securejarhandler:0.9.50") } diff --git a/stage2/modlauncher9/forge37/src/main/java/gg/essential/loader/stage2/modlauncher/EssentialModLocator_Forge.java b/stage2/modlauncher9/forge37/src/main/java/gg/essential/loader/stage2/modlauncher/EssentialModLocator_Forge.java new file mode 100644 index 0000000..7b5871b --- /dev/null +++ b/stage2/modlauncher9/forge37/src/main/java/gg/essential/loader/stage2/modlauncher/EssentialModLocator_Forge.java @@ -0,0 +1,39 @@ +package gg.essential.loader.stage2.modlauncher; + +import cpw.mods.jarhandling.SecureJar; +import net.minecraftforge.fml.loading.FMLLoader; +import net.minecraftforge.fml.loading.moddiscovery.ModFile; +import net.minecraftforge.fml.loading.moddiscovery.ModValidator; +import net.minecraftforge.forgespi.locating.IModLocator; + +import java.lang.reflect.Field; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Stream; + +public interface EssentialModLocator_Forge extends EssentialModLocator, IModLocator { + Iterable scanMods(Stream paths); + + @Override + default boolean injectMods(List modJars) throws ReflectiveOperationException { + Field modValidatorField = FMLLoader.class.getDeclaredField("modValidator"); + modValidatorField.setAccessible(true); + ModValidator modValidator = (ModValidator) modValidatorField.get(null); + + if (modValidator == null) { + return false; + } + + Field candidateModsField = ModValidator.class.getDeclaredField("candidateMods"); + candidateModsField.setAccessible(true); + @SuppressWarnings("unchecked") + List modFiles = (List) candidateModsField.get(modValidator); + + for (ModFile modFile : this.scanMods(modJars.stream().map(SecureJar::getPrimaryPath))) { + modFile.identifyMods(); + modFiles.add(modFile); + } + + return true; + } +} diff --git a/stage2/modlauncher9/forge37/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_37_0_0_ModLocator.java b/stage2/modlauncher9/forge37/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_37_0_0_ModLocator.java index 315343f..d92b30a 100644 --- a/stage2/modlauncher9/forge37/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_37_0_0_ModLocator.java +++ b/stage2/modlauncher9/forge37/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_37_0_0_ModLocator.java @@ -7,9 +7,10 @@ import java.util.Map; import java.util.stream.Stream; -class Forge_37_0_0_ModLocator extends AbstractJarFileLocator implements EssentialModLocator { +class Forge_37_0_0_ModLocator extends AbstractJarFileLocator implements EssentialModLocator_Forge { private Stream paths; + @Override public Iterable scanMods(Stream paths) { this.paths = paths; return scanMods().stream().map(it -> (ModFile) it)::iterator; diff --git a/stage2/modlauncher9/forge40/build.gradle b/stage2/modlauncher9/forge40/build.gradle index 2f367ac..98d8821 100644 --- a/stage2/modlauncher9/forge40/build.gradle +++ b/stage2/modlauncher9/forge40/build.gradle @@ -4,6 +4,7 @@ repositories { dependencies { compileOnly(parent.project("compatibility")) + compileOnly(parent.project("forge37")) compileOnly("cpw.mods:modlauncher:9.0.7") // modlauncher uses these in its api but does not declare them as such diff --git a/stage2/modlauncher9/forge40/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_40_1_60_ModLocator.java b/stage2/modlauncher9/forge40/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_40_1_60_ModLocator.java index 3979eef..d3335a4 100644 --- a/stage2/modlauncher9/forge40/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_40_1_60_ModLocator.java +++ b/stage2/modlauncher9/forge40/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_40_1_60_ModLocator.java @@ -7,9 +7,10 @@ import java.util.Map; import java.util.stream.Stream; -class Forge_40_1_60_ModLocator extends AbstractJarFileModLocator implements EssentialModLocator { +class Forge_40_1_60_ModLocator extends AbstractJarFileModLocator implements EssentialModLocator_Forge { private Stream paths; + @Override public Iterable scanMods(Stream paths) { this.paths = paths; return scanMods().stream().map(it -> (ModFile) it)::iterator; diff --git a/stage2/modlauncher9/forge41/build.gradle b/stage2/modlauncher9/forge41/build.gradle index ac0d23f..00b8f1f 100644 --- a/stage2/modlauncher9/forge41/build.gradle +++ b/stage2/modlauncher9/forge41/build.gradle @@ -4,6 +4,7 @@ repositories { dependencies { compileOnly(parent.project("compatibility")) + compileOnly(parent.project("forge37")) compileOnly("net.minecraftforge:fmlloader:1.19-41.0.64") } diff --git a/stage2/modlauncher9/forge41/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_41_0_34_ModLocator.java b/stage2/modlauncher9/forge41/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_41_0_34_ModLocator.java index 48267d8..f6f866c 100644 --- a/stage2/modlauncher9/forge41/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_41_0_34_ModLocator.java +++ b/stage2/modlauncher9/forge41/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_41_0_34_ModLocator.java @@ -7,9 +7,10 @@ import java.util.Map; import java.util.stream.Stream; -class Forge_41_0_34_ModLocator extends AbstractJarFileModLocator implements EssentialModLocator { +class Forge_41_0_34_ModLocator extends AbstractJarFileModLocator implements EssentialModLocator_Forge { private Stream paths; + @Override public Iterable scanMods(Stream paths) { this.paths = paths; return scanMods().stream().map(it -> (ModFile) it.file())::iterator; diff --git a/stage2/modlauncher9/forge49/build.gradle b/stage2/modlauncher9/forge49/build.gradle index 60cf93e..9954c08 100644 --- a/stage2/modlauncher9/forge49/build.gradle +++ b/stage2/modlauncher9/forge49/build.gradle @@ -4,6 +4,7 @@ repositories { dependencies { compileOnly(parent.project("compatibility")) + compileOnly(parent.project("forge37")) compileOnly("net.minecraftforge:fmlloader:1.20.4-49.0.38") } diff --git a/stage2/modlauncher9/forge49/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_49_0_38_ModLocator.java b/stage2/modlauncher9/forge49/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_49_0_38_ModLocator.java index d334005..466a3ca 100644 --- a/stage2/modlauncher9/forge49/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_49_0_38_ModLocator.java +++ b/stage2/modlauncher9/forge49/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_49_0_38_ModLocator.java @@ -7,9 +7,10 @@ import java.util.List; import java.util.stream.Stream; -public class Forge_49_0_38_ModLocator extends AbstractModProvider implements EssentialModLocator { +public class Forge_49_0_38_ModLocator extends AbstractModProvider implements EssentialModLocator_Forge { private Stream paths; + @Override public Iterable scanMods(Stream paths) { this.paths = paths; return scanMods().stream().map(it -> (ModFile) it.file())::iterator; diff --git a/stage2/modlauncher9/modlauncher10/build.gradle b/stage2/modlauncher9/modlauncher10/build.gradle index 3f028cc..9e80653 100644 --- a/stage2/modlauncher9/modlauncher10/build.gradle +++ b/stage2/modlauncher9/modlauncher10/build.gradle @@ -11,7 +11,4 @@ dependencies { compileOnly("cpw.mods:securejarhandler:2.0.3") compileOnly("net.sf.jopt-simple:jopt-simple:5.0.4") compileOnly("org.jetbrains:annotations:23.0.0") - - compileOnly("org.apache.logging.log4j:log4j-api:2.8.1") - compileOnly("net.minecraftforge:fmlloader:1.19-41.0.64") } \ No newline at end of file diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java index 634635c..a9fa36d 100644 --- a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java @@ -8,8 +8,6 @@ import gg.essential.loader.stage2.util.KFFMerger; import gg.essential.loader.stage2.util.SortedJarOrPathList; import net.minecraftforge.fml.loading.FMLLoader; -import net.minecraftforge.fml.loading.moddiscovery.ModFile; -import net.minecraftforge.fml.loading.moddiscovery.ModValidator; import net.minecraftforge.forgespi.locating.IModFile; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -149,25 +147,7 @@ private void configureLayerToBeSortedByVersion(IModuleLayerManager.Layer layer) */ private boolean injectMods() { try { - Field modValidatorField = FMLLoader.class.getDeclaredField("modValidator"); - modValidatorField.setAccessible(true); - ModValidator modValidator = (ModValidator) modValidatorField.get(null); - - if (modValidator == null) { - return false; - } - - Field candidateModsField = ModValidator.class.getDeclaredField("candidateMods"); - candidateModsField.setAccessible(true); - @SuppressWarnings("unchecked") - List modFiles = (List) candidateModsField.get(modValidator); - - for (ModFile modFile : this.modLocator.scanMods(this.gameJars.stream().map(SecureJar::getPrimaryPath))) { - modFile.identifyMods(); - modFiles.add(modFile); - } - - return true; + return modLocator.injectMods(gameJars); } catch (Throwable e) { LOGGER.error("Error injecting into mod list:", e); return false; From 83dc2325614ff710c390921ec6bc00f6d0ba9f56 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Thu, 6 Feb 2025 06:58:54 +0100 Subject: [PATCH 05/14] stage2/ml9: Separate ModLocator from CompatibilityLayer Former is Forge specific, latter is ModLauncher specific, and while they tend to change at the same time, that'll no longer be true once we also consider NeoForge. --- .../modlauncher/CompatibilityLayer.java | 2 -- .../modlauncher/Forge_37_0_0_ModLocator.java | 2 +- .../modlauncher/Forge_40_1_60_ModLocator.java | 2 +- .../modlauncher/Forge_41_0_34_ModLocator.java | 2 +- .../modlauncher/ML10CompatibilityLayer.java | 20 ------------- .../EssentialTransformationService.java | 29 ++++++++++++++++++- .../modlauncher/ML9CompatibilityLayer.java | 20 ------------- 7 files changed, 31 insertions(+), 46 deletions(-) diff --git a/stage2/modlauncher9/compatibility/src/main/java/gg/essential/loader/stage2/modlauncher/CompatibilityLayer.java b/stage2/modlauncher9/compatibility/src/main/java/gg/essential/loader/stage2/modlauncher/CompatibilityLayer.java index a6e2a54..e9d6243 100644 --- a/stage2/modlauncher9/compatibility/src/main/java/gg/essential/loader/stage2/modlauncher/CompatibilityLayer.java +++ b/stage2/modlauncher9/compatibility/src/main/java/gg/essential/loader/stage2/modlauncher/CompatibilityLayer.java @@ -9,6 +9,4 @@ */ public interface CompatibilityLayer { Manifest getManifest(SecureJar jar); - - EssentialModLocator makeModLocator(); } diff --git a/stage2/modlauncher9/forge37/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_37_0_0_ModLocator.java b/stage2/modlauncher9/forge37/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_37_0_0_ModLocator.java index d92b30a..5b5adb3 100644 --- a/stage2/modlauncher9/forge37/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_37_0_0_ModLocator.java +++ b/stage2/modlauncher9/forge37/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_37_0_0_ModLocator.java @@ -7,7 +7,7 @@ import java.util.Map; import java.util.stream.Stream; -class Forge_37_0_0_ModLocator extends AbstractJarFileLocator implements EssentialModLocator_Forge { +public class Forge_37_0_0_ModLocator extends AbstractJarFileLocator implements EssentialModLocator_Forge { private Stream paths; @Override diff --git a/stage2/modlauncher9/forge40/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_40_1_60_ModLocator.java b/stage2/modlauncher9/forge40/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_40_1_60_ModLocator.java index d3335a4..56a7328 100644 --- a/stage2/modlauncher9/forge40/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_40_1_60_ModLocator.java +++ b/stage2/modlauncher9/forge40/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_40_1_60_ModLocator.java @@ -7,7 +7,7 @@ import java.util.Map; import java.util.stream.Stream; -class Forge_40_1_60_ModLocator extends AbstractJarFileModLocator implements EssentialModLocator_Forge { +public class Forge_40_1_60_ModLocator extends AbstractJarFileModLocator implements EssentialModLocator_Forge { private Stream paths; @Override diff --git a/stage2/modlauncher9/forge41/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_41_0_34_ModLocator.java b/stage2/modlauncher9/forge41/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_41_0_34_ModLocator.java index f6f866c..aaf630a 100644 --- a/stage2/modlauncher9/forge41/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_41_0_34_ModLocator.java +++ b/stage2/modlauncher9/forge41/src/main/java/gg/essential/loader/stage2/modlauncher/Forge_41_0_34_ModLocator.java @@ -7,7 +7,7 @@ import java.util.Map; import java.util.stream.Stream; -class Forge_41_0_34_ModLocator extends AbstractJarFileModLocator implements EssentialModLocator_Forge { +public class Forge_41_0_34_ModLocator extends AbstractJarFileModLocator implements EssentialModLocator_Forge { private Stream paths; @Override diff --git a/stage2/modlauncher9/modlauncher10/src/main/java/gg/essential/loader/stage2/modlauncher/ML10CompatibilityLayer.java b/stage2/modlauncher9/modlauncher10/src/main/java/gg/essential/loader/stage2/modlauncher/ML10CompatibilityLayer.java index ec7189b..d8725c9 100644 --- a/stage2/modlauncher9/modlauncher10/src/main/java/gg/essential/loader/stage2/modlauncher/ML10CompatibilityLayer.java +++ b/stage2/modlauncher9/modlauncher10/src/main/java/gg/essential/loader/stage2/modlauncher/ML10CompatibilityLayer.java @@ -4,29 +4,9 @@ import java.util.jar.Manifest; -import static gg.essential.loader.stage2.Utils.hasClass; - public class ML10CompatibilityLayer implements CompatibilityLayer { @Override public Manifest getManifest(SecureJar jar) { return jar.moduleDataProvider().getManifest(); } - - @Override - public EssentialModLocator makeModLocator() { - String version; - if (!hasClass("net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileModLocator")) { - version = "49_0_38"; - } else { - version = "41_0_34"; - } - try { - String clsName = "gg.essential.loader.stage2.modlauncher.Forge_" + version + "_ModLocator"; - return (EssentialModLocator) Class.forName(clsName) - .getDeclaredConstructor() - .newInstance(); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - } } diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java index a9fa36d..a5c529e 100644 --- a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java @@ -22,6 +22,8 @@ import java.util.Map; import java.util.Set; +import static gg.essential.loader.stage2.Utils.hasClass; + public class EssentialTransformationService implements ITransformationService { private static final Logger LOGGER = LogManager.getLogger(EssentialTransformationService.class); private static final Map COMPATIBILITY_IMPLEMENTATIONS = Map.of( @@ -71,7 +73,7 @@ private static IModuleLayerManager.Layer determineLayer(SecureJar jar) { public void initialize(IEnvironment environment) { String modLauncherVersion = environment.getProperty(IEnvironment.Keys.MLIMPL_VERSION.get()).orElseThrow(); compatibilityLayer = findCompatibilityLayerImpl(modLauncherVersion); - modLocator = compatibilityLayer.makeModLocator(); + modLocator = findModLocatorImpl(); } @SuppressWarnings("unchecked") @@ -94,6 +96,31 @@ private CompatibilityLayer findCompatibilityLayerImpl(String mlVersion) { } } + private static EssentialModLocator findModLocatorImpl() { + String version; + if (hasClass("net.minecraftforge.forgespi.locating.IModLocator$ModFileOrException")) { + if (!hasClass("net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileModLocator")) { + version = "49_0_38"; + } else { + version = "41_0_34"; + } + } else { + if (hasClass("net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileModLocator")) { + version = "40_1_60"; + } else { + version = "37_0_0"; + } + } + try { + String clsName = "gg.essential.loader.stage2.modlauncher.Forge_" + version + "_ModLocator"; + return (EssentialModLocator) Class.forName(clsName) + .getDeclaredConstructor() + .newInstance(); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + @Override public void onLoad(IEnvironment env, Set otherServices) { } diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/modlauncher/ML9CompatibilityLayer.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/modlauncher/ML9CompatibilityLayer.java index 71e7a70..6a19829 100644 --- a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/modlauncher/ML9CompatibilityLayer.java +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/modlauncher/ML9CompatibilityLayer.java @@ -4,29 +4,9 @@ import java.util.jar.Manifest; -import static gg.essential.loader.stage2.Utils.hasClass; - public class ML9CompatibilityLayer implements CompatibilityLayer { @Override public Manifest getManifest(SecureJar jar) { return jar.getManifest(); } - - @Override - public EssentialModLocator makeModLocator() { - String version; - if (hasClass("net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileModLocator")) { - version = "40_1_60"; - } else { - version = "37_0_0"; - } - try { - String clsName = "gg.essential.loader.stage2.modlauncher.Forge_" + version + "_ModLocator"; - return (EssentialModLocator) Class.forName(clsName) - .getDeclaredConstructor() - .newInstance(); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - } } From 9d405c896efa79ff2ddb6b07cb4153a0e4a0c2fd Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Thu, 6 Feb 2025 07:36:05 +0100 Subject: [PATCH 06/14] stage2/ml9: Support ModLauncher 11 --- build.gradle | 1 + settings.gradle.kts | 1 + .../gg/essential/loader/stage2/util/Lazy.java | 26 +++++++++++++ stage2/modlauncher9/build.gradle | 1 + .../modlauncher9/compatibility/build.gradle | 2 + .../modlauncher/CompatibilityLayer.java | 17 ++++++++ .../modlauncher9/modlauncher11/build.gradle | 14 +++++++ .../modlauncher/ML11CompatibilityLayer.java | 39 +++++++++++++++++++ .../DescriptorRewritingJarMetadata.java | 10 +++-- .../EssentialTransformationService.java | 14 +++++-- .../stage2/SelfRenamingJarMetadata.java | 19 +++++---- .../loader/stage2/util/KFFMerger.java | 12 ++++-- 12 files changed, 137 insertions(+), 19 deletions(-) create mode 100644 stage2/common/src/main/java/gg/essential/loader/stage2/util/Lazy.java create mode 100644 stage2/modlauncher9/modlauncher11/build.gradle create mode 100644 stage2/modlauncher9/modlauncher11/src/main/java/gg/essential/loader/stage2/modlauncher/ML11CompatibilityLayer.java diff --git a/build.gradle b/build.gradle index 0cd5df1..43d6537 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,7 @@ subprojects { "forge41": 17, "forge49": 17, "modlauncher10": 17, + "modlauncher11": 21, ] java.toolchain.languageVersion.set(JavaLanguageVersion.of(javaVersions.getOrDefault(project.name, 8))) diff --git a/settings.gradle.kts b/settings.gradle.kts index 9cb7862..0c45101 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,6 +28,7 @@ include(":stage2:modlauncher9:forge40") include(":stage2:modlauncher9:forge41") include(":stage2:modlauncher9:forge49") include(":stage2:modlauncher9:modlauncher10") +include(":stage2:modlauncher9:modlauncher11") include(":integrationTest:common") include(":integrationTest:fabric") include(":integrationTest:launchwrapper") diff --git a/stage2/common/src/main/java/gg/essential/loader/stage2/util/Lazy.java b/stage2/common/src/main/java/gg/essential/loader/stage2/util/Lazy.java new file mode 100644 index 0000000..44c848d --- /dev/null +++ b/stage2/common/src/main/java/gg/essential/loader/stage2/util/Lazy.java @@ -0,0 +1,26 @@ +package gg.essential.loader.stage2.util; + +import java.util.function.Supplier; + +public class Lazy { + private Supplier supplier; + private T value; + + public Lazy(Supplier supplier) { + this.supplier = supplier; + } + + public Lazy(T value) { + this.value = value; + } + + public T get() { + if (supplier != null) { + synchronized (this) { + value = supplier.get(); + supplier = null; + } + } + return value; + } +} diff --git a/stage2/modlauncher9/build.gradle b/stage2/modlauncher9/build.gradle index a89b08e..ce984fe 100644 --- a/stage2/modlauncher9/build.gradle +++ b/stage2/modlauncher9/build.gradle @@ -9,6 +9,7 @@ dependencies { bundle(project("forge41")) bundle(project("forge49")) bundle(project("modlauncher10")) + bundle(project("modlauncher11")) compileOnly("cpw.mods:modlauncher:9.0.7") // modlauncher uses these in its api but does not declare them as such diff --git a/stage2/modlauncher9/compatibility/build.gradle b/stage2/modlauncher9/compatibility/build.gradle index 674ec76..91a2ba4 100644 --- a/stage2/modlauncher9/compatibility/build.gradle +++ b/stage2/modlauncher9/compatibility/build.gradle @@ -3,6 +3,8 @@ repositories { } dependencies { + compileOnly(project(":stage2:common")) + compileOnly("cpw.mods:securejarhandler:0.9.50") compileOnly("cpw.mods:modlauncher:9.0.7") } \ No newline at end of file diff --git a/stage2/modlauncher9/compatibility/src/main/java/gg/essential/loader/stage2/modlauncher/CompatibilityLayer.java b/stage2/modlauncher9/compatibility/src/main/java/gg/essential/loader/stage2/modlauncher/CompatibilityLayer.java index e9d6243..e832ede 100644 --- a/stage2/modlauncher9/compatibility/src/main/java/gg/essential/loader/stage2/modlauncher/CompatibilityLayer.java +++ b/stage2/modlauncher9/compatibility/src/main/java/gg/essential/loader/stage2/modlauncher/CompatibilityLayer.java @@ -1,12 +1,29 @@ package gg.essential.loader.stage2.modlauncher; +import cpw.mods.jarhandling.JarMetadata; import cpw.mods.jarhandling.SecureJar; +import gg.essential.loader.stage2.util.Lazy; +import java.nio.file.Path; +import java.util.Set; +import java.util.function.BiFunction; import java.util.jar.Manifest; /** * Provides abstractions for things which changed after ModLauncher 9. */ public interface CompatibilityLayer { + default SecureJar newSecureJarWithCustomMetadata(BiFunction, JarMetadata, JarMetadata> metadataWrapper, Path path) { + return SecureJar.from(jar -> metadataWrapper.apply(new Lazy<>(jar), JarMetadata.from(jar, path)), path); + } + Manifest getManifest(SecureJar jar); + + default Set getPackages(SecureJar secureJar) { + return secureJar.getPackages(); + } + + default JarMetadata getJarMetadata(SecureJar secureJar, Path path) { + return JarMetadata.from(secureJar, path); + } } diff --git a/stage2/modlauncher9/modlauncher11/build.gradle b/stage2/modlauncher9/modlauncher11/build.gradle new file mode 100644 index 0000000..8df20f6 --- /dev/null +++ b/stage2/modlauncher9/modlauncher11/build.gradle @@ -0,0 +1,14 @@ +repositories { + maven { url "https://maven.neoforged.net/releases" } +} + +dependencies { + compileOnly(project(":stage2:common")) + compileOnly(parent.project("compatibility")) + + compileOnly("cpw.mods:modlauncher:11.0.2") + // modlauncher uses these in its api but does not declare them as such + compileOnly("cpw.mods:securejarhandler:3.0.4") + compileOnly("net.sf.jopt-simple:jopt-simple:5.0.4") + compileOnly("org.jetbrains:annotations:23.0.0") +} diff --git a/stage2/modlauncher9/modlauncher11/src/main/java/gg/essential/loader/stage2/modlauncher/ML11CompatibilityLayer.java b/stage2/modlauncher9/modlauncher11/src/main/java/gg/essential/loader/stage2/modlauncher/ML11CompatibilityLayer.java new file mode 100644 index 0000000..e71d016 --- /dev/null +++ b/stage2/modlauncher9/modlauncher11/src/main/java/gg/essential/loader/stage2/modlauncher/ML11CompatibilityLayer.java @@ -0,0 +1,39 @@ +package gg.essential.loader.stage2.modlauncher; + +import cpw.mods.jarhandling.JarContents; +import cpw.mods.jarhandling.JarMetadata; +import cpw.mods.jarhandling.SecureJar; +import gg.essential.loader.stage2.util.Lazy; + +import java.nio.file.Path; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.jar.Manifest; + +public class ML11CompatibilityLayer implements CompatibilityLayer { + @Override + public SecureJar newSecureJarWithCustomMetadata(BiFunction, JarMetadata, JarMetadata> metadataWrapper, Path path) { + SecureJar[] jarHolder = new SecureJar[1]; + JarContents contents = JarContents.of(path); + JarMetadata metadata = JarMetadata.from(contents); + metadata = metadataWrapper.apply(new Lazy<>(() -> jarHolder[0]), metadata); + SecureJar jar = SecureJar.from(contents, metadata); + jarHolder[0] = jar; + return jar; + } + + @Override + public Manifest getManifest(SecureJar jar) { + return jar.moduleDataProvider().getManifest(); + } + + @Override + public Set getPackages(SecureJar secureJar) { + return secureJar.moduleDataProvider().descriptor().packages(); + } + + @Override + public JarMetadata getJarMetadata(SecureJar secureJar, Path path) { + return JarMetadata.from(JarContents.of(path)); + } +} diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/DescriptorRewritingJarMetadata.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/DescriptorRewritingJarMetadata.java index ef21637..b562d2b 100644 --- a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/DescriptorRewritingJarMetadata.java +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/DescriptorRewritingJarMetadata.java @@ -2,6 +2,8 @@ import cpw.mods.jarhandling.JarMetadata; import cpw.mods.jarhandling.SecureJar; +import gg.essential.loader.stage2.modlauncher.CompatibilityLayer; +import gg.essential.loader.stage2.util.Lazy; import java.lang.module.ModuleDescriptor; @@ -12,13 +14,13 @@ * a lookup table. */ public class DescriptorRewritingJarMetadata implements JarMetadata { - private final SecureJar secureJar; private final JarMetadata delegate; + private final JarMetadata newPkgsMeta; private ModuleDescriptor descriptor; - public DescriptorRewritingJarMetadata(SecureJar secureJar, JarMetadata delegate) { - this.secureJar = secureJar; + public DescriptorRewritingJarMetadata(JarMetadata delegate, JarMetadata newPkgsMeta) { this.delegate = delegate; + this.newPkgsMeta = newPkgsMeta; } @Override @@ -36,7 +38,7 @@ public ModuleDescriptor descriptor() { if (this.descriptor == null) { ModuleDescriptor org = delegate.descriptor(); ModuleDescriptor.Builder builder = ModuleDescriptor.newModule(org.name(), org.modifiers()); - builder.packages(secureJar.getPackages()); + builder.packages(newPkgsMeta.descriptor().packages()); if (!org.isAutomatic()) { org.requires().forEach(builder::requires); org.exports().forEach(builder::exports); diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java index a5c529e..f0b1c2a 100644 --- a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java @@ -6,6 +6,7 @@ import gg.essential.loader.stage2.modlauncher.CompatibilityLayer; import gg.essential.loader.stage2.modlauncher.EssentialModLocator; import gg.essential.loader.stage2.util.KFFMerger; +import gg.essential.loader.stage2.util.Lazy; import gg.essential.loader.stage2.util.SortedJarOrPathList; import net.minecraftforge.fml.loading.FMLLoader; import net.minecraftforge.forgespi.locating.IModFile; @@ -27,15 +28,16 @@ public class EssentialTransformationService implements ITransformationService { private static final Logger LOGGER = LogManager.getLogger(EssentialTransformationService.class); private static final Map COMPATIBILITY_IMPLEMENTATIONS = Map.of( - "9.", "ML9CompatibilityLayer", - "10.", "ML10CompatibilityLayer" + "11.", "ML11CompatibilityLayer", + "10.", "ML10CompatibilityLayer", + "9.", "ML9CompatibilityLayer" ); private static CompatibilityLayer compatibilityLayer; private final Path gameDir; private final List pluginJars = new ArrayList<>(); private final List gameJars = new ArrayList<>(); - private final KFFMerger kffMerger = new KFFMerger(); + private KFFMerger kffMerger; private EssentialModLocator modLocator; private boolean modsInjected; @@ -44,7 +46,10 @@ public EssentialTransformationService(Path gameDir) { } public void addToClasspath(final Path path) { - final SecureJar jar = SecureJar.from(j -> new SelfRenamingJarMetadata(j, path, determineLayer(j)), path); + final SecureJar jar = compatibilityLayer.newSecureJarWithCustomMetadata( + (j, metadata) -> new SelfRenamingJarMetadata(compatibilityLayer, j, metadata, new Lazy<>(() -> determineLayer(j.get()))), + path + ); if (this.kffMerger.addKotlinJar(path, jar)) { return; } @@ -74,6 +79,7 @@ public void initialize(IEnvironment environment) { String modLauncherVersion = environment.getProperty(IEnvironment.Keys.MLIMPL_VERSION.get()).orElseThrow(); compatibilityLayer = findCompatibilityLayerImpl(modLauncherVersion); modLocator = findModLocatorImpl(); + kffMerger = new KFFMerger(compatibilityLayer); } @SuppressWarnings("unchecked") diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/SelfRenamingJarMetadata.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/SelfRenamingJarMetadata.java index aa5291a..c50a1dd 100644 --- a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/SelfRenamingJarMetadata.java +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/SelfRenamingJarMetadata.java @@ -4,13 +4,14 @@ import cpw.mods.jarhandling.SecureJar; import cpw.mods.modlauncher.Launcher; import cpw.mods.modlauncher.api.IModuleLayerManager; +import gg.essential.loader.stage2.modlauncher.CompatibilityLayer; import gg.essential.loader.stage2.util.DelegatingJarMetadata; +import gg.essential.loader.stage2.util.Lazy; import gg.essential.loader.stage2.util.SortedJarOrPathList; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.lang.reflect.Field; -import java.nio.file.Path; import java.util.ArrayList; import java.util.EnumMap; import java.util.List; @@ -30,11 +31,13 @@ public class SelfRenamingJarMetadata extends DelegatingJarMetadata { private static final Logger LOGGER = LogManager.getLogger(SelfRenamingJarMetadata.class); private static final ThreadLocal RE_ENTRANCE_LOCK = ThreadLocal.withInitial(() -> false); - private final SecureJar secureJar; - private final IModuleLayerManager.Layer layer; + private final CompatibilityLayer compatibilityLayer; + private final Lazy secureJar; + private final Lazy layer; - public SelfRenamingJarMetadata(SecureJar secureJar, Path path, IModuleLayerManager.Layer layer) { - super(JarMetadata.from(secureJar, path)); + public SelfRenamingJarMetadata(CompatibilityLayer compatibilityLayer, Lazy secureJar, JarMetadata delegate, Lazy layer) { + super(delegate); + this.compatibilityLayer = compatibilityLayer; this.secureJar = secureJar; this.layer = layer; } @@ -47,7 +50,7 @@ public String name() { RE_ENTRANCE_LOCK.set(true); } String defaultName = delegate.name(); - Set ourPackages = secureJar.getPackages(); + Set ourPackages = compatibilityLayer.getPackages(secureJar.get()); try { for (SecureJar otherJar : getLayerJars()) { String otherModuleName; @@ -56,7 +59,7 @@ public String name() { } catch (SelfRenamingReEntranceException ignored) { continue; } - if (otherJar.getPackages().stream().anyMatch(ourPackages::contains)) { + if (compatibilityLayer.getPackages(otherJar).stream().anyMatch(ourPackages::contains)) { LOGGER.debug("Found existing module with name {}, renaming {} to match.", otherModuleName, defaultName); return otherModuleName; } @@ -75,7 +78,7 @@ private List getLayerElements() throws Throwable { IModuleLayerManager layerManager = Launcher.INSTANCE.findLayerManager().orElseThrow(); Field layersField = layerManager.getClass().getDeclaredField("layers"); layersField.setAccessible(true); - return ((EnumMap>) layersField.get(layerManager)).get(this.layer); + return ((EnumMap>) layersField.get(layerManager)).get(this.layer.get()); } private List getLayerJars() throws Throwable { diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/KFFMerger.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/KFFMerger.java index c136ed0..b48390f 100644 --- a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/KFFMerger.java +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/KFFMerger.java @@ -3,6 +3,7 @@ import cpw.mods.jarhandling.JarMetadata; import cpw.mods.jarhandling.SecureJar; import gg.essential.loader.stage2.DescriptorRewritingJarMetadata; +import gg.essential.loader.stage2.modlauncher.CompatibilityLayer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -63,10 +64,15 @@ public void maybeUpgrade(List injectedJars, int theirVersion) { } } + private final CompatibilityLayer compatibilityLayer; private final Libraries ourCoreJars = new Libraries("Kotlin core"); private final Libraries ourCoroutinesJars = new Libraries("Kotlin Coroutines"); private final Libraries ourSerializationJars = new Libraries("Kotlin Serialization"); + public KFFMerger(CompatibilityLayer compatibilityLayer) { + this.compatibilityLayer = compatibilityLayer; + } + public boolean addKotlinJar(Path sourceFile, SecureJar secureJar) { String fileName = sourceFile.getFileName().toString(); Matcher matcher = JIJ_KOTLIN_FILES.matcher(fileName); @@ -104,7 +110,7 @@ public SecureJar maybeMergeInto(SecureJar secureJar) { } // Only care about a jar if it contains a Kotlin we can overwrite - if (!secureJar.getPackages().contains("kotlin")) { + if (!compatibilityLayer.getPackages(secureJar).contains("kotlin")) { return secureJar; } @@ -131,7 +137,7 @@ public SecureJar maybeMergeInto(SecureJar secureJar) { } try { - JarMetadata orgMeta = JarMetadata.from(secureJar, secureJar.getPrimaryPath()); + JarMetadata orgMeta = compatibilityLayer.getJarMetadata(secureJar, secureJar.getPrimaryPath()); Path tmpFile = Files.createTempFile("kff-updated-kotlin-", "-" + orgMeta.version() + ".jar"); Files.write(tmpFile, EMPTY_ZIP); @@ -169,7 +175,7 @@ public SecureJar maybeMergeInto(SecureJar secureJar) { } } - return SecureJar.from(j -> new DescriptorRewritingJarMetadata(j, orgMeta) { + return compatibilityLayer.newSecureJarWithCustomMetadata((__, newMeta) -> new DescriptorRewritingJarMetadata(orgMeta, newMeta) { @Override public String name() { // Call the original name from the original SecureJar to allow SelfRenamingJarMetadata to function From 189065bade2d7e16767ddbd77c125bb5ab846bd8 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Thu, 6 Feb 2025 07:37:18 +0100 Subject: [PATCH 07/14] stage2/ml9: Support NeoForge --- build.gradle | 2 + settings.gradle.kts | 2 + stage2/modlauncher9/build.gradle | 5 +- stage2/modlauncher9/neoforge1/build.gradle | 10 ++++ .../EssentialModLocator_NeoForge.java | 38 +++++++++++++ .../NeoForge_1_0_0_ModLocator.java | 24 ++++++++ stage2/modlauncher9/neoforge4/build.gradle | 11 ++++ .../NeoForge_4_0_0_ModLocator.java | 17 ++++++ .../EssentialTransformationService.java | 57 ++++++++++++++----- 9 files changed, 152 insertions(+), 14 deletions(-) create mode 100644 stage2/modlauncher9/neoforge1/build.gradle create mode 100644 stage2/modlauncher9/neoforge1/src/main/java/gg/essential/loader/stage2/modlauncher/EssentialModLocator_NeoForge.java create mode 100644 stage2/modlauncher9/neoforge1/src/main/java/gg/essential/loader/stage2/modlauncher/NeoForge_1_0_0_ModLocator.java create mode 100644 stage2/modlauncher9/neoforge4/build.gradle create mode 100644 stage2/modlauncher9/neoforge4/src/main/java/gg/essential/loader/stage2/modlauncher/NeoForge_4_0_0_ModLocator.java diff --git a/build.gradle b/build.gradle index 43d6537..44cd116 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,8 @@ subprojects { "forge40": 17, "forge41": 17, "forge49": 17, + "neoforge1": 17, + "neoforge4": 21, "modlauncher10": 17, "modlauncher11": 21, ] diff --git a/settings.gradle.kts b/settings.gradle.kts index 0c45101..3bc4712 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -27,6 +27,8 @@ include(":stage2:modlauncher9:forge37") include(":stage2:modlauncher9:forge40") include(":stage2:modlauncher9:forge41") include(":stage2:modlauncher9:forge49") +include(":stage2:modlauncher9:neoforge1") +include(":stage2:modlauncher9:neoforge4") include(":stage2:modlauncher9:modlauncher10") include(":stage2:modlauncher9:modlauncher11") include(":integrationTest:common") diff --git a/stage2/modlauncher9/build.gradle b/stage2/modlauncher9/build.gradle index ce984fe..7448cd5 100644 --- a/stage2/modlauncher9/build.gradle +++ b/stage2/modlauncher9/build.gradle @@ -8,6 +8,8 @@ dependencies { bundle(project("forge40")) bundle(project("forge41")) bundle(project("forge49")) + bundle(project("neoforge1")) + bundle(project("neoforge4")) bundle(project("modlauncher10")) bundle(project("modlauncher11")) @@ -17,6 +19,7 @@ dependencies { compileOnly("net.sf.jopt-simple:jopt-simple:5.0.4") compileOnly("org.jetbrains:annotations:21.0.1") + // provided by fmlloader (both forge and neoforge ones) compileOnly("org.apache.logging.log4j:log4j-api:2.8.1") - compileOnly("net.minecraftforge:fmlloader:1.17.1-37.0.82") + compileOnly("org.apache.maven:maven-artifact:3.8.1") } diff --git a/stage2/modlauncher9/neoforge1/build.gradle b/stage2/modlauncher9/neoforge1/build.gradle new file mode 100644 index 0000000..6a3d769 --- /dev/null +++ b/stage2/modlauncher9/neoforge1/build.gradle @@ -0,0 +1,10 @@ +repositories { + maven { url "https://maven.neoforged.net/releases" } +} + +dependencies { + compileOnly(parent.project("compatibility")) + + compileOnly("net.neoforged.fancymodloader:loader:1.0.0") + compileOnly("cpw.mods:securejarhandler:2.1.23") +} diff --git a/stage2/modlauncher9/neoforge1/src/main/java/gg/essential/loader/stage2/modlauncher/EssentialModLocator_NeoForge.java b/stage2/modlauncher9/neoforge1/src/main/java/gg/essential/loader/stage2/modlauncher/EssentialModLocator_NeoForge.java new file mode 100644 index 0000000..a3ab658 --- /dev/null +++ b/stage2/modlauncher9/neoforge1/src/main/java/gg/essential/loader/stage2/modlauncher/EssentialModLocator_NeoForge.java @@ -0,0 +1,38 @@ +package gg.essential.loader.stage2.modlauncher; + +import cpw.mods.jarhandling.SecureJar; +import net.neoforged.fml.loading.FMLLoader; +import net.neoforged.fml.loading.moddiscovery.ModFile; +import net.neoforged.fml.loading.moddiscovery.ModValidator; + +import java.lang.reflect.Field; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Stream; + +public interface EssentialModLocator_NeoForge extends EssentialModLocator { + Iterable scanMods(Stream paths); + + @Override + default boolean injectMods(List modJars) throws ReflectiveOperationException { + Field modValidatorField = FMLLoader.class.getDeclaredField("modValidator"); + modValidatorField.setAccessible(true); + ModValidator modValidator = (ModValidator) modValidatorField.get(null); + + if (modValidator == null) { + return false; + } + + Field candidateModsField = ModValidator.class.getDeclaredField("candidateMods"); + candidateModsField.setAccessible(true); + @SuppressWarnings("unchecked") + List modFiles = (List) candidateModsField.get(modValidator); + + for (ModFile modFile : this.scanMods(modJars.stream().map(SecureJar::getPrimaryPath))) { + modFile.identifyMods(); + modFiles.add(modFile); + } + + return true; + } +} diff --git a/stage2/modlauncher9/neoforge1/src/main/java/gg/essential/loader/stage2/modlauncher/NeoForge_1_0_0_ModLocator.java b/stage2/modlauncher9/neoforge1/src/main/java/gg/essential/loader/stage2/modlauncher/NeoForge_1_0_0_ModLocator.java new file mode 100644 index 0000000..c4641d0 --- /dev/null +++ b/stage2/modlauncher9/neoforge1/src/main/java/gg/essential/loader/stage2/modlauncher/NeoForge_1_0_0_ModLocator.java @@ -0,0 +1,24 @@ +package gg.essential.loader.stage2.modlauncher; + +import net.neoforged.fml.loading.moddiscovery.AbstractJarFileModProvider; +import net.neoforged.fml.loading.moddiscovery.ModFile; + +import java.nio.file.Path; +import java.util.Map; +import java.util.stream.Stream; + +public class NeoForge_1_0_0_ModLocator extends AbstractJarFileModProvider implements EssentialModLocator_NeoForge { + @Override + public void initArguments(Map map) { + } + + @Override + public Iterable scanMods(Stream paths) { + return paths.map(this::createMod).map(it -> (ModFile) it.file())::iterator; + } + + @Override + public String name() { + return "essential-loader"; + } +} diff --git a/stage2/modlauncher9/neoforge4/build.gradle b/stage2/modlauncher9/neoforge4/build.gradle new file mode 100644 index 0000000..f1a5278 --- /dev/null +++ b/stage2/modlauncher9/neoforge4/build.gradle @@ -0,0 +1,11 @@ +repositories { + maven { url "https://maven.neoforged.net/releases" } +} + +dependencies { + compileOnly(parent.project("compatibility")) + compileOnly(parent.project("neoforge1")) + + compileOnly("net.neoforged.fancymodloader:loader:4.0.0") + compileOnly("cpw.mods:securejarhandler:3.0.4") +} diff --git a/stage2/modlauncher9/neoforge4/src/main/java/gg/essential/loader/stage2/modlauncher/NeoForge_4_0_0_ModLocator.java b/stage2/modlauncher9/neoforge4/src/main/java/gg/essential/loader/stage2/modlauncher/NeoForge_4_0_0_ModLocator.java new file mode 100644 index 0000000..58a9eda --- /dev/null +++ b/stage2/modlauncher9/neoforge4/src/main/java/gg/essential/loader/stage2/modlauncher/NeoForge_4_0_0_ModLocator.java @@ -0,0 +1,17 @@ +package gg.essential.loader.stage2.modlauncher; + +import cpw.mods.jarhandling.JarContents; +import net.neoforged.fml.loading.moddiscovery.ModFile; +import net.neoforged.fml.loading.moddiscovery.readers.JarModsDotTomlModFileReader; +import net.neoforged.neoforgespi.locating.ModFileDiscoveryAttributes; + +import java.nio.file.Path; +import java.util.stream.Stream; + +public class NeoForge_4_0_0_ModLocator implements EssentialModLocator_NeoForge { + @Override + public Iterable scanMods(Stream paths) { + JarModsDotTomlModFileReader modFileReader = new JarModsDotTomlModFileReader(); + return paths.map(it -> (ModFile) modFileReader.read(JarContents.of(it), ModFileDiscoveryAttributes.DEFAULT))::iterator; + } +} diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java index f0b1c2a..7401c98 100644 --- a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java @@ -8,8 +8,6 @@ import gg.essential.loader.stage2.util.KFFMerger; import gg.essential.loader.stage2.util.Lazy; import gg.essential.loader.stage2.util.SortedJarOrPathList; -import net.minecraftforge.fml.loading.FMLLoader; -import net.minecraftforge.forgespi.locating.IModFile; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; @@ -62,7 +60,7 @@ public void addToClasspath(final Path path) { private static IModuleLayerManager.Layer determineLayer(SecureJar jar) { final String modType = compatibilityLayer.getManifest(jar).getMainAttributes().getValue("FMLModType"); - if (IModFile.Type.LANGPROVIDER.name().equals(modType) || IModFile.Type.LIBRARY.name().equals(modType)) { + if ("LANGPROVIDER".equals(modType) || "LIBRARY".equals(modType)) { return IModuleLayerManager.Layer.PLUGIN; } else { return IModuleLayerManager.Layer.GAME; @@ -103,22 +101,33 @@ private CompatibilityLayer findCompatibilityLayerImpl(String mlVersion) { } private static EssentialModLocator findModLocatorImpl() { + String loader; String version; - if (hasClass("net.minecraftforge.forgespi.locating.IModLocator$ModFileOrException")) { - if (!hasClass("net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileModLocator")) { - version = "49_0_38"; + if (hasClass("net.neoforged.fml.loading.FMLLoader")) { + loader = "NeoForge"; + if (!hasClass("net.neoforged.fml.loading.moddiscovery.AbstractJarFileModProvider")) { + version = "4_0_0"; } else { - version = "41_0_34"; + version = "1_0_0"; } } else { - if (hasClass("net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileModLocator")) { - version = "40_1_60"; + loader = "Forge"; + if (hasClass("net.minecraftforge.forgespi.locating.IModLocator$ModFileOrException")) { + if (!hasClass("net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileModLocator")) { + version = "49_0_38"; + } else { + version = "41_0_34"; + } } else { - version = "37_0_0"; + if (hasClass("net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileModLocator")) { + version = "40_1_60"; + } else { + version = "37_0_0"; + } } } try { - String clsName = "gg.essential.loader.stage2.modlauncher.Forge_" + version + "_ModLocator"; + String clsName = "gg.essential.loader.stage2.modlauncher." + loader + "_" + version + "_ModLocator"; return (EssentialModLocator) Class.forName(clsName) .getDeclaredConstructor() .newInstance(); @@ -192,8 +201,30 @@ public List beginScanning(IEnvironment environment) { // Forge makes available the MC version in its `initialize` method, the first of our methods which we can be // sure is called after Forge's method is this one, so this is the earliest point at which we can load essential // proper. - String mcVersion = "forge_" + FMLLoader.versionInfo().mcVersion(); - ActualEssentialLoader essentialLoader = new ActualEssentialLoader(gameDir, mcVersion, this); + Class FMLLoader; + String loader; + try { + FMLLoader = Class.forName("net.minecraftforge.fml.loading.FMLLoader"); + loader = "forge"; + } catch (ClassNotFoundException e1) { + try { + FMLLoader = Class.forName("net.neoforged.fml.loading.FMLLoader"); + loader = "neoforge"; + } catch (ClassNotFoundException e2) { + e2.addSuppressed(e1); + throw new RuntimeException(e2); + } + } + String mcVersion; + try { + Object versionInfo = FMLLoader.getMethod("versionInfo").invoke(null); + mcVersion = (String) versionInfo.getClass().getMethod("mcVersion").invoke(versionInfo); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + String gameVersion = loader + "_" + mcVersion; + + ActualEssentialLoader essentialLoader = new ActualEssentialLoader(gameDir, gameVersion, this); try { essentialLoader.load(); } catch (IOException e) { From 2ef9df5b7e6d7f43f44f04756f9f8c85f869e2e0 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Fri, 7 Feb 2025 17:30:11 +0100 Subject: [PATCH 08/14] stage2/ml9: Automatically determine layer of SelfRenamingJarMetadata Newer Forge versions will put inner jars (such as Kotlin inside KFF) on a layer which depends on the outer jar (KFF), which may be different than what one would expect from just looking at the Kotlin jar. As such we can no longer determine the layer ahead of time and will instead assume that layers are built in order, and just assume whichever layer is the most recent one. Note that we cannot easily just check which layer our SecureJar is in because it may be further wrapped by e.g. KFFMerger and as such may not directly appear in any of the lists at all. --- .../stage2/EssentialTransformationService.java | 3 +-- .../loader/stage2/SelfRenamingJarMetadata.java | 15 +++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java index 7401c98..63fd6d4 100644 --- a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java @@ -6,7 +6,6 @@ import gg.essential.loader.stage2.modlauncher.CompatibilityLayer; import gg.essential.loader.stage2.modlauncher.EssentialModLocator; import gg.essential.loader.stage2.util.KFFMerger; -import gg.essential.loader.stage2.util.Lazy; import gg.essential.loader.stage2.util.SortedJarOrPathList; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -45,7 +44,7 @@ public EssentialTransformationService(Path gameDir) { public void addToClasspath(final Path path) { final SecureJar jar = compatibilityLayer.newSecureJarWithCustomMetadata( - (j, metadata) -> new SelfRenamingJarMetadata(compatibilityLayer, j, metadata, new Lazy<>(() -> determineLayer(j.get()))), + (j, metadata) -> new SelfRenamingJarMetadata(compatibilityLayer, j, metadata), path ); if (this.kffMerger.addKotlinJar(path, jar)) { diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/SelfRenamingJarMetadata.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/SelfRenamingJarMetadata.java index c50a1dd..2001ffa 100644 --- a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/SelfRenamingJarMetadata.java +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/SelfRenamingJarMetadata.java @@ -33,13 +33,11 @@ public class SelfRenamingJarMetadata extends DelegatingJarMetadata { private final CompatibilityLayer compatibilityLayer; private final Lazy secureJar; - private final Lazy layer; - public SelfRenamingJarMetadata(CompatibilityLayer compatibilityLayer, Lazy secureJar, JarMetadata delegate, Lazy layer) { + public SelfRenamingJarMetadata(CompatibilityLayer compatibilityLayer, Lazy secureJar, JarMetadata delegate) { super(delegate); this.compatibilityLayer = compatibilityLayer; this.secureJar = secureJar; - this.layer = layer; } @Override @@ -78,7 +76,16 @@ private List getLayerElements() throws Throwable { IModuleLayerManager layerManager = Launcher.INSTANCE.findLayerManager().orElseThrow(); Field layersField = layerManager.getClass().getDeclaredField("layers"); layersField.setAccessible(true); - return ((EnumMap>) layersField.get(layerManager)).get(this.layer.get()); + EnumMap> map = + (EnumMap>) layersField.get(layerManager); + IModuleLayerManager.Layer[] layers = IModuleLayerManager.Layer.values(); + for (int i = layers.length - 1; i >= 0; i--) { + List elements = map.get(layers[i]); + if (elements != null) { + return elements; + } + } + throw new RuntimeException("Failed to find current layer?!"); } private List getLayerJars() throws Throwable { From 1a4ab4e32e753619b2ddf36ce6431a2d095b748e Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 12 Feb 2025 11:30:19 +0100 Subject: [PATCH 09/14] stage2/ml9: Allow emitting multiple jars for one KFF jar Will be required for KFF5 which uses JarJar, so we want to emit all our Kotlin jars separately instead of merging them into the outer jar. --- .../loader/stage2/util/KFFMerger.java | 14 ++++---- .../stage2/util/SortedJarOrPathList.java | 36 ++++++++++--------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/KFFMerger.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/KFFMerger.java index b48390f..53e4631 100644 --- a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/KFFMerger.java +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/KFFMerger.java @@ -103,15 +103,15 @@ public boolean addKotlinJar(Path sourceFile, SecureJar secureJar) { return true; } - public SecureJar maybeMergeInto(SecureJar secureJar) { + public List maybeMergeInto(SecureJar secureJar) { // Nothing to merge (older Essential version), nothing to do if (ourCoreJars.jars.isEmpty()) { - return secureJar; + return null; } // Only care about a jar if it contains a Kotlin we can overwrite if (!compatibilityLayer.getPackages(secureJar).contains("kotlin")) { - return secureJar; + return null; } LOGGER.info("Found Kotlin-containing mod {}, checking whether we need to upgrade it..", secureJar); @@ -133,7 +133,7 @@ public SecureJar maybeMergeInto(SecureJar secureJar) { if (injectedJars.isEmpty()) { LOGGER.info("All good, no update needed: {}", secureJar); - return secureJar; // all up-to-date, nothing to do + return null; // all up-to-date, nothing to do } try { @@ -175,16 +175,16 @@ public SecureJar maybeMergeInto(SecureJar secureJar) { } } - return compatibilityLayer.newSecureJarWithCustomMetadata((__, newMeta) -> new DescriptorRewritingJarMetadata(orgMeta, newMeta) { + return List.of(compatibilityLayer.newSecureJarWithCustomMetadata((__, newMeta) -> new DescriptorRewritingJarMetadata(orgMeta, newMeta) { @Override public String name() { // Call the original name from the original SecureJar to allow SelfRenamingJarMetadata to function return secureJar.name(); } - }, tmpFile); + }, tmpFile)); } catch (Throwable t) { LOGGER.fatal("Failed to merge updated Kotlin into " + secureJar + ":", t); - return secureJar; // oh well, guess we'll give it a try as is + return null; // oh well, guess we'll give it a try as is } } diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/SortedJarOrPathList.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/SortedJarOrPathList.java index 6b8175f..7c868ac 100644 --- a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/SortedJarOrPathList.java +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/SortedJarOrPathList.java @@ -14,6 +14,7 @@ import java.util.Collection; import java.util.Comparator; import java.util.IdentityHashMap; +import java.util.List; import java.util.Map; import java.util.function.BiFunction; import java.util.function.Function; @@ -38,9 +39,9 @@ public class SortedJarOrPathList extends ArrayList { // This does not actually have anything to do with the original functionality of this class but we needed an entry // point for replacing one jar (KFF with old Kotlin) with another jar (same KFF but with newer Kotlin merged into // it) and this class is perfect for that. - private final Function substitute; + private final Function> substitute; - public SortedJarOrPathList(Function substitute) { + public SortedJarOrPathList(Function> substitute) { this.substitute = substitute; } @@ -93,31 +94,30 @@ private JarMetadata getMetadata(SecureJar jar) { @Override public boolean add(Object o) { - boolean changed = super.add(substitute(o)); - sort(COMPARATOR); - return changed; + //noinspection RedundantCollectionOperation + return addAll(List.of(o)); } @Override public boolean addAll(Collection c) { - boolean changed = super.addAll(c.stream().map(this::substitute).toList()); + boolean changed = super.addAll(c.stream().flatMap(it -> substitute(it).stream()).toList()); sort(COMPARATOR); return changed; } - private Object substitute(Object orgPathOrJar) { + private List substitute(Object orgPathOrJar) { SecureJar orgJar = getJar(orgPathOrJar); if (orgJar == null) { - return orgPathOrJar; + return List.of(orgPathOrJar); } - SecureJar newJar = substitute.apply(orgJar); - if (newJar == orgJar) { - return orgPathOrJar; + List newJars = substitute.apply(orgJar); + if (newJars == null) { + return List.of(orgPathOrJar); } if (orgPathOrJar instanceof SecureJar) { - return newJar; + return newJars; } if (pathOrJarConstructor == null) { @@ -137,11 +137,15 @@ private Object substitute(Object orgPathOrJar) { pathOrJarConstructor = (path, jar) -> null; } } - Object newPathOrJar = pathOrJarConstructor.apply(null, newJar); - if (newPathOrJar == null) { - return orgPathOrJar; + List newPathOrJars = new ArrayList<>(newJars.size()); + for (SecureJar newJar : newJars) { + Object newPathOrJar = pathOrJarConstructor.apply(null, newJar); + if (newPathOrJar == null) { + return List.of(orgPathOrJar); + } + newPathOrJars.add(newPathOrJar); } - return newPathOrJar; + return newPathOrJars; } } From b3f2b610cee5a27ab20c57d023c342b7d53c5971 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 12 Feb 2025 11:32:36 +0100 Subject: [PATCH 10/14] stage2/ml9: Workaround incorrectly reported version on some jars --- .../EssentialTransformationService.java | 2 +- .../stage2/util/SortedJarOrPathList.java | 32 ++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java index 63fd6d4..15cce21 100644 --- a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/EssentialTransformationService.java @@ -164,7 +164,7 @@ private void configureLayerToBeSortedByVersion(IModuleLayerManager.Layer layer) (Map>) layersField.get(layerManager); layers.compute(layer, (__, list) -> { - SortedJarOrPathList sortedList = new SortedJarOrPathList(kffMerger::maybeMergeInto); + SortedJarOrPathList sortedList = new SortedJarOrPathList(compatibilityLayer, kffMerger::maybeMergeInto); if (list != null) { sortedList.addAll(list); } diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/SortedJarOrPathList.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/SortedJarOrPathList.java index 7c868ac..4dfed97 100644 --- a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/SortedJarOrPathList.java +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/SortedJarOrPathList.java @@ -3,6 +3,7 @@ import cpw.mods.jarhandling.JarMetadata; import cpw.mods.jarhandling.SecureJar; import cpw.mods.modlauncher.api.NamedPath; +import gg.essential.loader.stage2.modlauncher.CompatibilityLayer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.maven.artifact.versioning.ArtifactVersion; @@ -18,6 +19,7 @@ import java.util.Map; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.jar.Manifest; /** * List which keeps its JarOrPath elements sorted by their module version, latest first. @@ -36,12 +38,15 @@ public class SortedJarOrPathList extends ArrayList { private final Comparator COMPARATOR = Comparator.comparing(pathOrJar -> versionCache.computeIfAbsent(pathOrJar, this::getVersion)).reversed(); + private final CompatibilityLayer compatibilityLayer; + // This does not actually have anything to do with the original functionality of this class but we needed an entry // point for replacing one jar (KFF with old Kotlin) with another jar (same KFF but with newer Kotlin merged into // it) and this class is perfect for that. private final Function> substitute; - public SortedJarOrPathList(Function> substitute) { + public SortedJarOrPathList(CompatibilityLayer compatibilityLayer, Function> substitute) { + this.compatibilityLayer = compatibilityLayer; this.substitute = substitute; } @@ -52,6 +57,31 @@ private ArtifactVersion getVersion(Object pathOrJar) { if (metadata == null) return FALLBACK_VERSION; String version = metadata.version(); if (version == null) return FALLBACK_VERSION; + // ModLauncher somehow manages to report the version of some jars (really unsure which ones, best guess + // right now is all those without a `FMLModType` manifest attribute? seemingly completely irregardless of + // whether they have an `Implementation-Version` attribute!) + // as "Optional.empty" (yes, that's a String, somewhere someone must have blindly `toString`ed). + // We'll just go fetch it ourselves then I guess. + if (version.equals("Optional.empty")) { + version = null; + } + if (version == null) { + Manifest manifest = compatibilityLayer.getManifest(jar); + if (manifest != null) { + version = manifest.getMainAttributes().getValue("Implementation-Version"); + } + } + // and if that doesn't work (some of the Kotlin libs, e.g. kotlinx-serialization-json-jvm-1.7.3, don't have + // such an attribute), then we'll take a guess based on the file name + if (version == null) { + String name = jar.getPrimaryPath().getFileName().toString(); + if (name.contains("-") && name.endsWith(".jar")) { + version = name.substring(name.lastIndexOf("-") + 1, name.length() - ".jar".length()); + } + } + if (version == null) { + return FALLBACK_VERSION; + } return new DefaultArtifactVersion(version); } From b4b0a7e16bf14ba2da43e8b73ac49f353f55069e Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 12 Feb 2025 11:33:49 +0100 Subject: [PATCH 11/14] stage2/ml9: Support KotlinForForge 5 --- .../stage2/SelfRenamingJarMetadata.java | 13 +++++- .../loader/stage2/util/KFFMerger.java | 40 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/SelfRenamingJarMetadata.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/SelfRenamingJarMetadata.java index 2001ffa..cf3e6f4 100644 --- a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/SelfRenamingJarMetadata.java +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/SelfRenamingJarMetadata.java @@ -17,6 +17,8 @@ import java.util.List; import java.util.Set; +import static gg.essential.loader.stage2.util.KFFMerger.isJarJarKff; + /** * A jar metadata which takes the name of another jar with the same packages in the same layer. * @@ -57,10 +59,19 @@ public String name() { } catch (SelfRenamingReEntranceException ignored) { continue; } - if (compatibilityLayer.getPackages(otherJar).stream().anyMatch(ourPackages::contains)) { + Set otherPackages = compatibilityLayer.getPackages(otherJar); + if (otherPackages.stream().anyMatch(ourPackages::contains)) { LOGGER.debug("Found existing module with name {}, renaming {} to match.", otherModuleName, defaultName); return otherModuleName; } + // Special case for fully-JarJar-reliant KFF which no longer contains any code itself and doesn't + // declare its module name in its manifest either. + // We still need to make the KFF we ship to use the same module name though, because otherwise it'll + // be loaded and we'll end up with two modules exporting Kotlin. + if (defaultName.equals("thedarkcolour.kotlinforforge") && otherPackages.isEmpty() && isJarJarKff(otherJar)) { + LOGGER.debug("Found existing JarJar KFF with name {}, renaming {} to match.", otherModuleName, defaultName); + return otherModuleName; + } } } catch (Throwable e) { LOGGER.error("Exception occurred while trying to self-rename module " + defaultName + ": ", e); diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/KFFMerger.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/KFFMerger.java index 53e4631..6a96329 100644 --- a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/KFFMerger.java +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/KFFMerger.java @@ -109,6 +109,19 @@ public List maybeMergeInto(SecureJar secureJar) { return null; } + // If this is a KotlinForForge version which uses JarJar to bundle Kotlin (KFF 5+, potentially 4.12), + // we no longer need to merge the Kotlin jars into it, we merely need to add our Kotlin jars to the to-be-loaded + // list too. + if (isJarJarKff(secureJar)) { + LOGGER.info("Looks like KotlinForForge is using JarJar now, all good to go: {}", secureJar); + List allJars = new ArrayList<>(); + allJars.add(secureJar); // keep user-installed jar + allJars.addAll(ourCoreJars.jars); + allJars.addAll(ourCoroutinesJars.jars); + allJars.addAll(ourSerializationJars.jars); + return allJars; + } + // Only care about a jar if it contains a Kotlin we can overwrite if (!compatibilityLayer.getPackages(secureJar).contains("kotlin")) { return null; @@ -126,6 +139,13 @@ public List maybeMergeInto(SecureJar secureJar) { boolean updateCoroutines = theirCoroutinesVersion < ourCoroutinesJars.version; int theirSerializationVersion = updateCore || updateCoroutines ? 0 : ourSerializationJars.version; + // If the jar contains only core but not coroutine libs, then it's not the fat KFF jar but rather KFF is + // using JarJar, and we should be able to find that one later. + if (theirCoreVersion != 0 && theirCoroutinesVersion == 0) { + LOGGER.info("Looks like a standalone Kotlin jar. Keeping as is, we should be finding a JarJar KFF jar."); + return null; + } + List injectedJars = new ArrayList<>(); ourCoreJars.maybeUpgrade(injectedJars, theirCoreVersion); ourCoroutinesJars.maybeUpgrade(injectedJars, theirCoroutinesVersion); @@ -188,6 +208,26 @@ public String name() { } } + public static boolean isJarJarKff(SecureJar jar) { + try { + Path jarjarPath = jar.getRootPath().resolve("META-INF").resolve("jarjar"); + if (!Files.exists(jarjarPath)) return false; + try (Stream stream = Files.list(jarjarPath)) { + List files = stream + .map(it -> it.getFileName().toString()) + .filter(it -> it.endsWith(".jar")) + .toList(); + // A JarJar-using KotlinForForge jar can be recognized by the fact that it bundles both the KFF mod as + // well as the Kotlin Standard Library + return files.stream().anyMatch(it -> it.startsWith("kffmod-")) + && files.stream().anyMatch(it -> it.startsWith("kotlin-stdlib-")); + } + } catch (Throwable t) { + LOGGER.error("Failed to determine version of potential KFF jar at " + jar + ":", t); + return false; + } + } + private int detectKotlinCoreVersion(SecureJar jar, Path root) { try { if (Files.notExists(root.resolve("kotlin").resolve("KotlinVersion.class"))) { From 3ad05b6e1d047287e2ba851fe4fcfe0b72a1dee2 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 2 Apr 2025 14:22:54 +0200 Subject: [PATCH 12/14] stage2/ml9: Improve workaround for incorrectly reported versions There are really two independent issues, the `toString` and the fact that it returns no version, which may happen independently. As such, this commit also unwraps the `toString` result of non-empty `Optional`s, and runs the fallback version reading code when the raw version reported is already `null`. --- .../stage2/util/SortedJarOrPathList.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/SortedJarOrPathList.java b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/SortedJarOrPathList.java index 4dfed97..93594ca 100644 --- a/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/SortedJarOrPathList.java +++ b/stage2/modlauncher9/src/main/java/gg/essential/loader/stage2/util/SortedJarOrPathList.java @@ -56,15 +56,22 @@ private ArtifactVersion getVersion(Object pathOrJar) { JarMetadata metadata = getMetadata(jar); if (metadata == null) return FALLBACK_VERSION; String version = metadata.version(); - if (version == null) return FALLBACK_VERSION; - // ModLauncher somehow manages to report the version of some jars (really unsure which ones, best guess - // right now is all those without a `FMLModType` manifest attribute? seemingly completely irregardless of - // whether they have an `Implementation-Version` attribute!) - // as "Optional.empty" (yes, that's a String, somewhere someone must have blindly `toString`ed). - // We'll just go fetch it ourselves then I guess. - if (version.equals("Optional.empty")) { + + // Some revisions of ModLauncher have a bug where they simply call `toString` on `Optional`, resulting + // in versions being reported as the string "Optional.empty" or "Optional[1.2.3]" instead of `null` or "1.2.3". + // See https://github.com/McModLauncher/securejarhandler/blob/7cd8481364d73bacecf2b608479c6b903bff7f6c/src/main/java/cpw/mods/jarhandling/impl/ModuleJarMetadata.java#L137 + // We need to unwrap those to get at the real version. + if (version != null && version.equals("Optional.empty")) { version = null; + } else if (version != null && version.startsWith("Optional[")) { + version = version.substring("Optional[".length(), version.length() - 1); } + + // Additionally, when ModuleJarMetadata is used (not entirely sure when that's the case, at the very least the + // jar must have a `module-info.class` but mods may also use ModJarMetadata instead), ModLauncher only looks at + // the version declared in the `module-info.class`. For most jars that version is `null` though because it + // requires extra setup in Gradle which most people don't do. + // We need a version for correct sorting though, so we'll try to find one ourselves. if (version == null) { Manifest manifest = compatibilityLayer.getManifest(jar); if (manifest != null) { @@ -79,6 +86,7 @@ private ArtifactVersion getVersion(Object pathOrJar) { version = name.substring(name.lastIndexOf("-") + 1, name.length() - ".jar".length()); } } + if (version == null) { return FALLBACK_VERSION; } From 8571fb7b0103b9c4adb6f357aedcf1cd8fc24e72 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Thu, 13 Feb 2025 08:32:38 +0100 Subject: [PATCH 13/14] stage0: Release 1.2.4 --- stage0/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stage0/build.gradle b/stage0/build.gradle index 3aa634e..76a9f03 100644 --- a/stage0/build.gradle +++ b/stage0/build.gradle @@ -1,4 +1,4 @@ -version = "1.2.3" +version = "1.2.4" configure(subprojects.findAll { it.name != "common" && it.name != "modlauncher" }) { apply plugin: 'maven-publish' From 2b35cce10564907327e501af795c89cffcc1cdfb Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Thu, 13 Feb 2025 08:32:46 +0100 Subject: [PATCH 14/14] stage2: Release 1.6.4 --- stage2/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stage2/build.gradle b/stage2/build.gradle index b6dd180..11ee558 100644 --- a/stage2/build.gradle +++ b/stage2/build.gradle @@ -1,4 +1,4 @@ -version = "1.6.3" +version = "1.6.4" configure(subprojects) { version = parent.version