diff --git a/dependencies.gradle b/dependencies.gradle index e4f396c..587c204 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -2,5 +2,8 @@ dependencies { compileOnly("org.projectlombok:lombok:1.18.22") { transitive = false } + compileOnly("org.spongepowered:mixin:0.7.11-SNAPSHOT") { + transitive = false + } annotationProcessor("org.projectlombok:lombok:1.18.22") } diff --git a/repositories.gradle b/repositories.gradle index 0ac475d..aa8bcb1 100644 --- a/repositories.gradle +++ b/repositories.gradle @@ -1 +1,6 @@ -repositories {} +repositories { + maven { + name = "sponge" + url = "https://sponge.falsepattern.com/" + } +} diff --git a/src/main/java/com/falsepattern/lib/mixin/IMixin.java b/src/main/java/com/falsepattern/lib/mixin/IMixin.java new file mode 100644 index 0000000..f8538b9 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/mixin/IMixin.java @@ -0,0 +1,50 @@ +package com.falsepattern.lib.mixin; + +import cpw.mods.fml.relauncher.FMLLaunchHandler; +import lombok.val; + +import java.util.List; +import java.util.function.Predicate; +import java.util.function.Supplier; + +public interface IMixin { + + Side getSide(); + String getMixin(); + Predicate> getFilter(); + + default boolean shouldLoad(List loadedMods) { + val side = getSide(); + return (side == Side.COMMON + || side == Side.SERVER && FMLLaunchHandler.side().isServer() + || side == Side.CLIENT && FMLLaunchHandler.side().isClient()) + && getFilter().test(loadedMods); + } + + static Predicate> never() { + return (list) -> false; + } + + static Predicate> condition(Supplier condition) { + return (list) -> condition.get(); + } + + static Predicate> always() { + return (list) -> true; + } + + static Predicate> require(ITargetedMod mod) { + return (list) -> list.contains(mod); + } + + static Predicate> avoid(ITargetedMod mod) { + return (list) -> !list.contains(mod); + } + + + enum Side { + COMMON, + CLIENT, + SERVER + } +} diff --git a/src/main/java/com/falsepattern/lib/mixin/IMixinPlugin.java b/src/main/java/com/falsepattern/lib/mixin/IMixinPlugin.java new file mode 100644 index 0000000..1de65b6 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/mixin/IMixinPlugin.java @@ -0,0 +1,122 @@ +package com.falsepattern.lib.mixin; + +import lombok.val; +import net.minecraft.launchwrapper.Launch; +import org.apache.logging.log4j.Logger; +import org.spongepowered.asm.lib.tree.ClassNode; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static java.nio.file.Files.walk; + +public interface IMixinPlugin extends IMixinConfigPlugin { + Path MODS_DIRECTORY_PATH = new File(Launch.minecraftHome, "mods/").toPath(); + + Logger getLogger(); + ITargetedMod[] targetedModEnumValues(); + IMixin[] mixinEnumValues(); + + @Override + default void onLoad(String mixinPackage) { + + } + + @Override + default String getRefMapperConfig() { + return null; + } + + @Override + default boolean shouldApplyMixin(String targetClassName, String mixinClassName) { + return true; + } + + @Override + default void acceptTargets(Set myTargets, Set otherTargets) { + + } + + @Override + default List getMixins() { + val isDevelopmentEnvironment = (boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment"); + val targetedMods = targetedModEnumValues(); + val loadedMods = Arrays.stream(targetedMods) + .filter(mod -> (mod.getLoadInDevelopment() && isDevelopmentEnvironment) + || loadJarOf(mod)) + .collect(Collectors.toList()); + + for (val mod : targetedMods) { + if(loadedMods.contains(mod)) { + getLogger().info("Found " + mod.getModName() + "! Integrating now..."); + } + else { + getLogger().info("Could not find " + mod.getModName() + "! Skipping integration...."); + } + } + + List mixins = new ArrayList<>(); + for (val mixin : mixinEnumValues()) { + if (mixin.shouldLoad(loadedMods)) { + String mixinClass = mixin.getMixin(); + mixins.add(mixinClass); + getLogger().info("Loading mixin: " + mixinClass); + } + } + return mixins; + } + + @Override + default void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + + } + + @Override + default void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + + } + + default boolean loadJarOf(final ITargetedMod mod) { + boolean success = false; + try { + File jar = findJarOf(mod); + if(jar == null) { + getLogger().info("Jar not found for " + mod); + return false; + } + getLogger().info("Attempting to add " + jar + " to the URL Class Path"); + success = true; + if(!jar.exists()) { + success = false; + throw new FileNotFoundException(jar.toString()); + } + MinecraftURLClassPath.addJar(jar); + } catch (Throwable e) { + e.printStackTrace(); + } + return success; + } + + static File findJarOf(final ITargetedMod mod) { + try { + return walk(MODS_DIRECTORY_PATH) + .filter(mod::isMatchingJar) + .map(Path::toFile) + .findFirst() + .orElse(null); + } + catch (IOException e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/src/main/java/com/falsepattern/lib/mixin/ITargetedMod.java b/src/main/java/com/falsepattern/lib/mixin/ITargetedMod.java new file mode 100644 index 0000000..c33c660 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/mixin/ITargetedMod.java @@ -0,0 +1,38 @@ +package com.falsepattern.lib.mixin; + +import com.google.common.io.Files; +import lombok.val; +import net.minecraft.launchwrapper.Launch; +import org.apache.logging.log4j.Logger; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; + +import java.nio.file.Path; +import java.util.function.Predicate; + +public interface ITargetedMod { + static Predicate startsWith(String subString) { + return (name) -> name.startsWith(subString); + } + static Predicate contains(String subString) { + return (name) -> name.contains(subString); + } + static Predicate matches(String regex) { + return (name) -> name.matches(regex); + } + + String getModName(); + Predicate getCondition(); + boolean getLoadInDevelopment(); + + default boolean isMatchingJar(Path path) { + String pathString = path.toString(); + String nameLowerCase = Files.getNameWithoutExtension(pathString).toLowerCase(); + String fileExtension = Files.getFileExtension(pathString); + + return "jar".equals(fileExtension) && getCondition().test(nameLowerCase); + } + + default String toStringDefault() { + return String.format("TargetedMod{%s}", getModName()); + } +} diff --git a/src/main/java/com/falsepattern/lib/mixin/MinecraftURLClassPath.java b/src/main/java/com/falsepattern/lib/mixin/MinecraftURLClassPath.java new file mode 100644 index 0000000..327bee5 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/mixin/MinecraftURLClassPath.java @@ -0,0 +1,74 @@ +package com.falsepattern.lib.mixin; + +import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.ModClassLoader; +import lombok.val; +import net.minecraft.launchwrapper.LaunchClassLoader; +import sun.misc.URLClassPath; + +import java.io.File; +import java.lang.reflect.Field; + +/** + * Backport from spongemixins 1.3 for compat with the curseforge 1.2.0 version. + * + * Also added Grimoire protection. + */ +public final class MinecraftURLClassPath { + /** + * Utility to manipulate the minecraft URL ClassPath + */ + + private static final URLClassPath ucp; + private static final boolean GRIMOIRE; + + static { + boolean grimoire; + try { + Class.forName("io.github.crucible.grimoire.Grimoire", false, MinecraftURLClassPath.class.getClassLoader()); + grimoire = true; + } catch (ClassNotFoundException ignored) { + grimoire = false; + } + GRIMOIRE = grimoire; + if (!GRIMOIRE) { + try { + val modClassLoaderField = Loader.class.getDeclaredField("modClassLoader"); + modClassLoaderField.setAccessible(true); + + val loaderinstanceField = Loader.class.getDeclaredField("instance"); + loaderinstanceField.setAccessible(true); + + val mainClassLoaderField = ModClassLoader.class.getDeclaredField("mainClassLoader"); + mainClassLoaderField.setAccessible(true); + + val ucpField = LaunchClassLoader.class.getSuperclass().getDeclaredField("ucp"); + ucpField.setAccessible(true); + + Object loader = loaderinstanceField.get(null); + val modClassLoader = (ModClassLoader) modClassLoaderField.get(loader); + val mainClassLoader = (LaunchClassLoader) mainClassLoaderField.get(modClassLoader); + ucp = (URLClassPath) ucpField.get(mainClassLoader); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e.getMessage()); + } + } else { + ucp = null; + System.err.println("Grimoire detected, disabling jar loading utility"); + } + } + + /** + * Adds a Jar to the Minecraft URL ClassPath + * - Needed when using mixins on classes outside of Minecraft or other coremods + */ + public static void addJar(File pathToJar) throws Exception { + if (!GRIMOIRE) + ucp.addURL(pathToJar.toURI().toURL()); + } + + private MinecraftURLClassPath() { + } + + +}