diff options
7 files changed, 389 insertions, 7 deletions
diff --git a/build.gradle.kts b/build.gradle.kts index defc1052..a661d6be 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,6 +30,7 @@ plugins { id("com.github.johnrengelman.shadow") version "7.1.2" id("io.github.juuxel.loom-quiltflower") version "1.7.3" `maven-publish` + kotlin("jvm") version "1.7.20" } @@ -47,7 +48,7 @@ loom { "client" { property("mixin.debug", "true") property("asmhelper.verbose", "true") - arg("--tweakClass", "org.spongepowered.asm.launch.MixinTweaker") + arg("--tweakClass", "io.github.moulberry.notenoughupdates.loader.NEUDelegatingTweaker") arg("--mixin", "mixins.notenoughupdates.json") } } @@ -90,11 +91,21 @@ val devEnv by configurations.creating { isVisible = false } +val kotlinDependencies by configurations.creating { + configurations.implementation.get().extendsFrom(this) +} + dependencies { + implementation("org.projectlombok:lombok:1.18.22") minecraft("com.mojang:minecraft:1.8.9") mappings("de.oceanlabs.mcp:mcp_stable:22-1.8.9") forge("net.minecraftforge:forge:1.8.9-11.15.1.2318-1.8.9") + + // Please keep this version in sync with KotlinLoadingTweaker + implementation(enforcedPlatform("org.jetbrains.kotlin:kotlin-bom:1.7.21")) + kotlinDependencies(kotlin("stdlib")) + shadowImplementation("org.spongepowered:mixin:0.7.11-SNAPSHOT") { isTransitive = false // Dependencies of mixin are already bundled by minecraft } @@ -128,7 +139,7 @@ tasks.withType(Jar::class) { archiveBaseName.set("NotEnoughUpdates") manifest.attributes.run { this["Main-Class"] = "NotSkyblockAddonsInstallerFrame" - this["TweakClass"] = "org.spongepowered.asm.launch.MixinTweaker" + this["TweakClass"] = "io.github.moulberry.notenoughupdates.loader.NEUDelegatingTweaker" this["MixinConfigs"] = "mixins.notenoughupdates.json" this["FMLCorePluginContainsFMLMod"] = "true" this["ForceLoadAsMod"] = "true" @@ -145,6 +156,16 @@ val remapJar by tasks.named<net.fabricmc.loom.task.RemapJarTask>("remapJar") { } } +/* Bypassing https://github.com/johnrengelman/shadow/issues/111 */ +// Use Zip instead of Jar as to not include META-INF +val kotlinDependencyCollectionJar by tasks.creating(Zip::class) { + archiveFileName.set("kotlin-libraries-wrapped.jar") + destinationDirectory.set(project.layout.buildDirectory.dir("kotlinwrapper")) + from(kotlinDependencies.files) + into("neu-kotlin-libraries-wrapped") +} + + tasks.shadowJar { archiveClassifier.set("dep-dev") configurations = listOf(shadowImplementation, shadowApi) @@ -155,6 +176,8 @@ tasks.shadowJar { listOf("logback-classic", "commons-logging", "commons-codec", "logback-core") } } + from(kotlinDependencyCollectionJar) + dependsOn(kotlinDependencyCollectionJar) fun relocate(name: String) = relocate(name, "io.github.moulberry.notenoughupdates.deps.$name") } diff --git a/buildSrc/src/main/kotlin/neubs/versioning.kt b/buildSrc/src/main/kotlin/neubs/versioning.kt index 254e0012..9294e164 100644 --- a/buildSrc/src/main/kotlin/neubs/versioning.kt +++ b/buildSrc/src/main/kotlin/neubs/versioning.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 NotEnoughUpdates contributors + * Copyright (C) 2022 Linnea Gräf * * This file is part of NotEnoughUpdates. * @@ -44,7 +44,7 @@ fun Project.setVersionFromEnvironment(baseVersion: String) { standardOutput = gitDiffStdout isIgnoreExitValue = true } - if (gitDiffStdout.toByteArray().isNotEmpty()) { + if (gitDiffResult.exitValue == 0 && gitDiffStdout.toByteArray().isNotEmpty()) { buildExtra.add("dirty") } 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]); + } + +} |