aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/example/init
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-02-05 13:19:59 +0100
committerLinnea Gräf <nea@nea.moe>2024-02-05 13:19:59 +0100
commit09cd2809fbe4c3411c9065fd5b8a7fca52cceafd (patch)
tree0543ba9327e5a5bb7fcf6b1545bd8108e5208a4f /src/main/java/com/example/init
parentd9c6bd7e73673396f13595546edd687b0b3a8b9d (diff)
downloadForge1.8.9Template-09cd2809fbe4c3411c9065fd5b8a7fca52cceafd.tar.gz
Forge1.8.9Template-09cd2809fbe4c3411c9065fd5b8a7fca52cceafd.tar.bz2
Forge1.8.9Template-09cd2809fbe4c3411c9065fd5b8a7fca52cceafd.zip
Add an auto discovery plugin for mixins
This will automatically find mixins in the mixin plugin, not requiring the manual configuration. This is less friction, especially for newer users. Usually this isn't super advisable, or needs more configuration to avoid server/client mixins being incorrectly applied, but with the target demographic of mostly client mods, I think this is a worthwhile change.
Diffstat (limited to 'src/main/java/com/example/init')
-rw-r--r--src/main/java/com/example/init/AutoDiscoveryMixinPlugin.java188
1 files changed, 188 insertions, 0 deletions
diff --git a/src/main/java/com/example/init/AutoDiscoveryMixinPlugin.java b/src/main/java/com/example/init/AutoDiscoveryMixinPlugin.java
new file mode 100644
index 0000000..aa2f995
--- /dev/null
+++ b/src/main/java/com/example/init/AutoDiscoveryMixinPlugin.java
@@ -0,0 +1,188 @@
+package com.example.init;
+
+import org.spongepowered.asm.lib.tree.ClassNode;
+import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
+import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+/**
+ * A mixin plugin to automatically discover all mixins in the current JAR.
+ * <p>
+ * This mixin plugin automatically scans your entire JAR (or class directory, in case of an in-IDE launch) for classes inside of your
+ * mixin package and registers those. It does this recursively for sub packages of the mixin package as well. This means you will need
+ * to only have mixin classes inside of your mixin package, which is good style anyway.
+ *
+ * @author Linnea Gräf
+ */
+public class AutoDiscoveryMixinPlugin implements IMixinConfigPlugin {
+ private static final List<AutoDiscoveryMixinPlugin> mixinPlugins = new ArrayList<>();
+
+ public static List<AutoDiscoveryMixinPlugin> getMixinPlugins() {
+ return mixinPlugins;
+ }
+
+ private String mixinPackage;
+
+ @Override
+ public void onLoad(String mixinPackage) {
+ this.mixinPackage = mixinPackage;
+ mixinPlugins.add(this);
+ }
+
+ /**
+ * Resolves the base class root for a given class URL. This resolves either the JAR root, or the class file root.
+ * In either case the return value of this + the class name will resolve back to the original class url, or to other
+ * class urls for other classes.
+ */
+ public URL getBaseUrlForClassUrl(URL classUrl) {
+ String string = classUrl.toString();
+ if (classUrl.getProtocol().equals("jar")) {
+ try {
+ return new URL(string.substring(4).split("!")[0]);
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ if (string.endsWith(".class")) {
+ try {
+ return new URL(string.replace("\\", "/")
+ .replace(getClass().getCanonicalName()
+ .replace(".", "/") + ".class", ""));
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return classUrl;
+ }
+
+ /**
+ * Get the package that contains all the mixins. This value is set by mixin itself using {@link #onLoad}.
+ */
+ public String getMixinPackage() {
+ return mixinPackage;
+ }
+
+ /**
+ * Get the path inside the class root to the mixin package
+ */
+ public String getMixinBaseDir() {
+ return mixinPackage.replace(".", "/");
+ }
+
+ /**
+ * A list of all discovered mixins.
+ */
+ private List<String> mixins = null;
+
+ /**
+ * Try to add mixin class ot the mixins based on the filepath inside of the class root.
+ * Removes the {@code .class} file suffix, as well as the base mixin package.
+ * <p><b>This method cannot be called after mixin initialization.</p>
+ *
+ * @param className the name or path of a class to be registered as a mixin.
+ */
+ public void tryAddMixinClass(String className) {
+ String norm = (className.endsWith(".class") ? className.substring(0, className.length() - ".class".length()) : className)
+ .replace("\\", "/")
+ .replace("/", ".");
+ if (norm.startsWith(getMixinPackage() + ".") && !norm.endsWith(".")) {
+ mixins.add(norm.substring(getMixinPackage().length() + 1));
+ }
+ }
+
+ /**
+ * Search through the JAR or class directory to find mixins contained in {@link #getMixinPackage()}
+ */
+ @Override
+ public List<String> getMixins() {
+ if (mixins != null) return mixins;
+ System.out.println("Trying to discover mixins");
+ mixins = new ArrayList<>();
+ URL classUrl = getClass().getProtectionDomain().getCodeSource().getLocation();
+ System.out.println("Found classes at " + classUrl);
+ Path file;
+ try {
+ file = Paths.get(getBaseUrlForClassUrl(classUrl).toURI());
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ System.out.println("Base directory found at " + file);
+ if (Files.isDirectory(file)) {
+ walkDir(file);
+ } else {
+ walkJar(file);
+ }
+ System.out.println("Found mixins: " + mixins);
+
+ return mixins;
+ }
+
+ /**
+ * Search through directory for mixin classes based on {@link #getMixinBaseDir}.
+ *
+ * @param classRoot The root directory in which classes are stored for the default package.
+ */
+ private void walkDir(Path classRoot) {
+ System.out.println("Trying to find mixins from directory");
+ try (Stream<Path> classes = Files.walk(classRoot.resolve(getMixinBaseDir()))) {
+ classes.map(it -> classRoot.relativize(it).toString())
+ .forEach(this::tryAddMixinClass);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Read through a JAR file, trying to find all mixins inside.
+ */
+ private void walkJar(Path file) {
+ System.out.println("Trying to find mixins from jar file");
+ try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(file))) {
+ ZipEntry next;
+ while ((next = zis.getNextEntry()) != null) {
+ tryAddMixinClass(next.getName());
+ zis.closeEntry();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
+
+ }
+
+ @Override
+ public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
+
+ }
+
+ @Override
+ public String getRefMapperConfig() {
+ return null;
+ }
+
+ @Override
+ public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
+ return true;
+ }
+
+ @Override
+ public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {
+
+ }
+}