aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/io
diff options
context:
space:
mode:
authorRoman / Linnea Gräf <roman.graef@gmail.com>2022-11-22 20:22:20 +0100
committerGitHub <noreply@github.com>2022-11-22 20:22:20 +0100
commita17412e1f1c829dd6d085b4849dae4ca571fd16b (patch)
treeaa7b0edebbe8f027d2e093fb534c35fb16a3ec2f /src/main/java/io
parent3d37cee20ddcf92b47144cb1e3ae25785a5e8bc2 (diff)
downloadNotEnoughUpdates-a17412e1f1c829dd6d085b4849dae4ca571fd16b.tar.gz
NotEnoughUpdates-a17412e1f1c829dd6d085b4849dae4ca571fd16b.tar.bz2
NotEnoughUpdates-a17412e1f1c829dd6d085b4849dae4ca571fd16b.zip
Terrible Kotlin! (#435)
* Terrible Kotlin! You are unloved and everybody dies * Load Kotlin via a Tweaker * Fix version comparison * Allow user to prevent loading of Kotlin libraries using system properties * Remove testing files
Diffstat (limited to 'src/main/java/io')
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/commands/dev/DevTestCommand.java3
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/envcheck/EnvironmentScan.java2
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/loader/KotlinLoadingTweaker.java193
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/loader/ModLoadingTweaker.java67
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/loader/NEUDelegatingTweaker.java100
5 files changed, 362 insertions, 3 deletions
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/commands/dev/DevTestCommand.java b/src/main/java/io/github/moulberry/notenoughupdates/commands/dev/DevTestCommand.java
index 5f1fe191..5ee31aa9 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/commands/dev/DevTestCommand.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/commands/dev/DevTestCommand.java
@@ -166,8 +166,7 @@ public class DevTestCommand extends ClientCommandBase {
}
if (args.length == 2 && args[0].equalsIgnoreCase("pt")) {
- EnumParticleTypes t = EnumParticleTypes.valueOf(args[1]);
- FishingHelper.type = t;
+ FishingHelper.type = EnumParticleTypes.valueOf(args[1]);
return;
}
if (args.length == 1 && args[0].equalsIgnoreCase("dev")) {
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/envcheck/EnvironmentScan.java b/src/main/java/io/github/moulberry/notenoughupdates/envcheck/EnvironmentScan.java
index 8e0f24b9..1ae5199a 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/envcheck/EnvironmentScan.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/envcheck/EnvironmentScan.java
@@ -34,7 +34,7 @@ public class EnvironmentScan {
static boolean shouldCheckOnce = true;
- static void checkEnvironmentOnce() {
+ public static void checkEnvironmentOnce() {
if (shouldCheckOnce) checkEnvironment();
}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/loader/KotlinLoadingTweaker.java b/src/main/java/io/github/moulberry/notenoughupdates/loader/KotlinLoadingTweaker.java
new file mode 100644
index 00000000..68aa8adb
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/loader/KotlinLoadingTweaker.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2022 Linnea Gräf
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.loader;
+
+import net.minecraft.launchwrapper.ITweaker;
+import net.minecraft.launchwrapper.Launch;
+import net.minecraft.launchwrapper.LaunchClassLoader;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Stream;
+
+/**
+ * <h3>TO OTHER MOD AUTHORS THAT WANT TO BUNDLE KOTLIN AND ARE RUNNING INTO CONFLICTS WITH NEU:</h3>
+ *
+ * <p>
+ * NEU allows you to stop it from loading Kotlin by specifying one of two properties in the
+ * {@link Launch#blackboard}. This needs to be done before {@link #injectIntoClassLoader}, so do it in your Tweakers
+ * constructor. Most likely you will not need to do this, since different Kotlin versions rarely contain breaking changes
+ * and most people do not use any of the newer features.
+ * </p>
+ *
+ * <ul>
+ * <li>
+ * {@code neu.relinquishkotlin.ifbelow}. Set to an int array, like so: {@code [1, 7, 20]}.
+ * See {@link #BUNDLED_KOTLIN_VERSION}
+ * </li>
+ * <li>
+ * {@code neu.relinquishkotlin.always}. If set to {@code true}, will prevent NEU from loading any kotlin.
+ * Before you use this, consider: are you sure you are always gonna have a more update version of Kotlin than NEU?
+ * And, even if you are sure, do you really want to block us from updating our Kotlin, after we so graciously allowed
+ * you to stop us from breaking your mod.
+ * </li>
+ * <li>
+ * Additionally, setting the jvm property {@code neu.relinquishkotlin} to 1, will prevent NEU from loading Kotlin.
+ * </li>
+ * </ul>
+ *
+ * <p>
+ * Sadly the Kotlin stdlib cannot be relocated (and we wouldn't want to do that either, because it would prevent us
+ * from interoperating with other Kotlin mods in some ways (for example: reflection)), so in order to minimize conflicts,
+ * we allow other mods to stop us from loading Kotlin. Usually this would be handled with a common library provider, but
+ * since those come bundled with literal Remote Code Execution, Cosmetics, and unskippable Updates that can just not be
+ * considered a good thing by any developer with a conscience nowadays, those are not an option for us. Another option
+ * would be a dependency resolution algorithm like Jar-in-Jar, but sadly we have to mod on a 7 year old modding platform,
+ * so we have to make due with our crude hack.
+ * </p>
+ */
+public class KotlinLoadingTweaker implements ITweaker {
+
+ /**
+ * Full version format: [1, 7, 20] (1.7.20)
+ * RC version format: [1, 7, 20, 1] (1.7.20-rc1)
+ */
+ public static final int[] BUNDLED_KOTLIN_VERSION = new int[]{1, 7, 20};
+
+ @Override
+ public void acceptOptions(List<String> args, File gameDir, File assetsDir, String profile) {
+
+ }
+
+ public boolean areWeBundlingAKotlinVersionHigherThan(int[] x) {
+ for (int i = 0; ; i++) {
+ boolean doWeHaveMoreVersionIdsLeft = i < BUNDLED_KOTLIN_VERSION.length;
+ boolean doTheyHaveMoreVersionIdsLeft = i < x.length;
+ if (doWeHaveMoreVersionIdsLeft && !doTheyHaveMoreVersionIdsLeft) return false;
+ if (doTheyHaveMoreVersionIdsLeft && !doWeHaveMoreVersionIdsLeft) return true;
+ if (!doTheyHaveMoreVersionIdsLeft) return true;
+ if (x[i] > BUNDLED_KOTLIN_VERSION[i]) return false;
+ }
+ }
+
+ @Override
+ public void injectIntoClassLoader(LaunchClassLoader classLoader) {
+ FileSystem fs = null;
+ try {
+ if ("1".equals(System.getProperty("neu.relinquishkotlin"))) {
+ System.out.println("NEU is forced to relinquish Kotlin by user configuration.");
+ return;
+ }
+ if (Launch.blackboard.get("fml.deobfuscatedEnvironment") == Boolean.TRUE) {
+ System.out.println("Skipping NEU Kotlin loading in development environment.");
+ return;
+ }
+ Object relinquishAlways = Launch.blackboard.get("neu.relinquishkotlin.always");
+ if (relinquishAlways == Boolean.TRUE) {
+ System.err.println(
+ "NEU is forced to blanket relinquish loading Kotlin. This is probably a bad judgement call by another developer.");
+ return;
+ }
+ Object relinquishIfBelow = Launch.blackboard.get("neu.relinquishkotlin.ifbelow");
+ if ((relinquishIfBelow instanceof int[])) {
+ int[] requiredVersion = (int[]) relinquishIfBelow;
+ if (!areWeBundlingAKotlinVersionHigherThan(requiredVersion)) {
+ System.err.println(
+ "NEU is relinquishing loading Kotlin because a higher version is requested. This may lead to errors if the advertised Kotlin version is not found. (" +
+ Arrays.toString(requiredVersion) + " required, " + Arrays.toString(BUNDLED_KOTLIN_VERSION) + " available)");
+ return;
+ }
+ }
+
+ System.out.println("Attempting to load Kotlin from NEU wrapped libraries.");
+
+ URI uri = getClass().getResource("/neu-kotlin-libraries-wrapped").toURI();
+ Path p;
+ if ("jar".equals(uri.getScheme())) {
+ fs = FileSystems.newFileSystem(uri, Collections.emptyMap());
+ p = fs.getPath(
+ "/neu-kotlin-libraries-wrapped");
+ } else {
+ p = Paths.get(uri);
+ }
+ System.out.println("Loading NEU Kotlin from " + p.toAbsolutePath());
+ Path tempDirectory = Files.createTempDirectory("notenoughupdates-extracted-kotlin");
+ System.out.println("Using temporary directory " + tempDirectory + " to store extracted kotlin.");
+ tempDirectory.toFile().deleteOnExit();
+ try (Stream<Path> libraries = Files.walk(p, 1)) {
+ libraries.filter(it -> it.getFileName().toString().toLowerCase(Locale.ROOT).endsWith(".jar"))
+ .forEach(it -> {
+ try {
+ Path extractedPath = tempDirectory.resolve(it.getFileName().toString());
+ extractedPath.toFile().deleteOnExit();
+ Files.copy(it, extractedPath);
+ addClassSourceTwice(classLoader, extractedPath.toUri().toURL());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+ classLoader.loadClass("kotlin.KotlinVersion");
+ System.out.println("Could successfully load a Kotlin class.");
+ } catch (Exception e) {
+ System.err.println("Failed to load Kotlin into NEU. This is most likely a bad thing.");
+ e.printStackTrace();
+ } finally {
+ if (fs != null) {
+ try {
+ fs.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ private void addClassSourceTwice(LaunchClassLoader classLoader, URL url) throws Exception {
+ classLoader.addURL(url);
+ ClassLoader classLoaderYetAgain = classLoader.getClass().getClassLoader();
+ Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
+ addURL.setAccessible(true);
+ addURL.invoke(classLoaderYetAgain, url);
+ }
+
+ @Override
+ public String getLaunchTarget() {
+ return null;
+ }
+
+ @Override
+ public String[] getLaunchArguments() {
+ return new String[0];
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/loader/ModLoadingTweaker.java b/src/main/java/io/github/moulberry/notenoughupdates/loader/ModLoadingTweaker.java
new file mode 100644
index 00000000..c3428046
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/loader/ModLoadingTweaker.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 Linnea Gräf
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.loader;
+
+import net.minecraft.launchwrapper.ITweaker;
+import net.minecraft.launchwrapper.LaunchClassLoader;
+import net.minecraftforge.fml.relauncher.CoreModManager;
+import org.spongepowered.asm.launch.MixinBootstrap;
+
+import java.io.File;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.List;
+
+/**
+ * The mod loading tweaker makes sure that we are recognized as a Forge Mod, despite having a Tweaker.
+ * We also add ourselves as a mixin container for integration with other mixin loaders.
+ */
+public class ModLoadingTweaker implements ITweaker {
+ @Override
+ public void acceptOptions(List<String> args, File gameDir, File assetsDir, String profile) {
+ URL location = ModLoadingTweaker.class.getProtectionDomain().getCodeSource().getLocation();
+ if (location == null) return;
+ if (!"file".equals(location.getProtocol())) return;
+ try {
+ MixinBootstrap.getPlatform().addContainer(location.toURI());
+ String file = new File(location.toURI()).getName();
+ CoreModManager.getIgnoredMods().remove(file);
+ CoreModManager.getReparseableCoremods().add(file);
+ } catch (URISyntaxException e) {
+ System.err.println("NEU could not re-add itself as mod.");
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void injectIntoClassLoader(LaunchClassLoader classLoader) {
+
+ }
+
+ @Override
+ public String getLaunchTarget() {
+ return null;
+ }
+
+ @Override
+ public String[] getLaunchArguments() {
+ return new String[0];
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/loader/NEUDelegatingTweaker.java b/src/main/java/io/github/moulberry/notenoughupdates/loader/NEUDelegatingTweaker.java
new file mode 100644
index 00000000..1d91ca76
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/loader/NEUDelegatingTweaker.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 Linnea Gräf
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.loader;
+
+import io.github.moulberry.notenoughupdates.envcheck.EnvironmentScan;
+import net.minecraft.launchwrapper.ITweaker;
+import net.minecraft.launchwrapper.LaunchClassLoader;
+import org.spongepowered.asm.launch.MixinTweaker;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tweaker used by NEU to allow delegating to multiple tweakers. The following Tweakers are currently delegated to:
+ *
+ * <ul>
+ * <li>{@link KotlinLoadingTweaker} for late loading Kotlin</li>
+ * <li>{@link MixinTweaker} for loading Mixins</li>
+ * <li>{@link ModLoadingTweaker} to ensure we are recognized as a forge mod</li>
+ * </ul>
+ *
+ * <p>We also run an environment check, to make sure we are running on the correct Forge and Minecraft version.</p>
+ *
+ * @see EnvironmentScan
+ */
+@SuppressWarnings("unused")
+public class NEUDelegatingTweaker implements ITweaker {
+ static {
+ EnvironmentScan.checkEnvironmentOnce();
+ }
+
+ List<ITweaker> delegates = new ArrayList<>();
+
+ public NEUDelegatingTweaker() {
+ discoverTweakers();
+ System.out.println("NEU Delegating Tweaker is loaded with: " + delegates);
+ }
+
+ private void discoverTweakers() {
+ delegates.add(new MixinTweaker());
+ delegates.add(new ModLoadingTweaker());
+ delegates.add(new KotlinLoadingTweaker());
+ }
+
+ @Override
+ public void acceptOptions(List<String> args, File gameDir, File assetsDir, String profile) {
+ for (ITweaker delegate : delegates) {
+ delegate.acceptOptions(args, gameDir, assetsDir, profile);
+ }
+ }
+
+ @Override
+ public void injectIntoClassLoader(LaunchClassLoader classLoader) {
+ for (ITweaker delegate : delegates) {
+ delegate.injectIntoClassLoader(classLoader);
+ }
+ }
+
+ @Override
+ public String getLaunchTarget() {
+ String target = null;
+ for (ITweaker delegate : delegates) {
+ String launchTarget = delegate.getLaunchTarget();
+ if (launchTarget != null)
+ target = launchTarget;
+ }
+ return target;
+ }
+
+ @Override
+ public String[] getLaunchArguments() {
+ List<String> launchArguments = new ArrayList<>();
+ for (ITweaker delegate : delegates) {
+ String[] delegateLaunchArguments = delegate.getLaunchArguments();
+ if (delegateLaunchArguments != null)
+ launchArguments.addAll(Arrays.asList(delegateLaunchArguments));
+ }
+ return launchArguments.toArray(new String[0]);
+ }
+
+}