diff --git a/src/main/java/cpw/mods/modlauncher/ModernFixCachingClassTransformer.java b/src/main/java/cpw/mods/modlauncher/ModernFixCachingClassTransformer.java index 6eee287e..e0e9ba5b 100644 --- a/src/main/java/cpw/mods/modlauncher/ModernFixCachingClassTransformer.java +++ b/src/main/java/cpw/mods/modlauncher/ModernFixCachingClassTransformer.java @@ -30,7 +30,7 @@ import javax.lang.model.SourceVersion; public class ModernFixCachingClassTransformer extends ClassTransformer { private static final Logger LOGGER = LogManager.getLogger("ModernFixCachingTransformer"); - private static final File CLASS_CACHE_DAT = childFile(FMLPaths.GAMEDIR.get().resolve("modernfix").resolve("classTransformerV1.cache").toFile()); + private final File CLASS_CACHE_DAT = childFile(FMLPaths.GAMEDIR.get().resolve("modernfix").resolve("classTransformerV1.cache").toFile()); private final LaunchPluginHandler pluginHandler; private final TransformStore transformStore; private final TransformerAuditTrail auditTrail; @@ -169,32 +169,31 @@ public class ModernFixCachingClassTransformer extends ClassTransformer { } } /* Check if the cache contains a transformed class matching these hashes */ - return transformationCache.compute(className, (name, oldPair) -> { - boolean hashesMatch = true; - if(oldPair == null || oldPair.getLeft().size() != hashList.size()) { - hashesMatch = false; - } else { - for(int i = 0; i < oldPair.getLeft().size(); i++) { - if(!Arrays.equals(oldPair.getLeft().get(i), hashList.get(i))) { - hashesMatch = false; - } + Pair, byte[]> oldPair = transformationCache.get(className); + boolean hashesMatch = true; + if(oldPair == null || oldPair.getLeft().size() != hashList.size()) { + hashesMatch = false; + } else { + for(int i = 0; i < oldPair.getLeft().size(); i++) { + if(!Arrays.equals(oldPair.getLeft().get(i), hashList.get(i))) { + hashesMatch = false; } } - if(hashesMatch) - return oldPair; - else { - if(oldPair != null) { - LOGGER.warn("Hashes have changed, discarding cached version of " + name); - } - byte[] transformed = super.transform(inputClass, name, reason); - if(transformed.length == 0) - transformed = EMPTY_BYTE_ARRAY; /* deduplicate */ - return Pair.of(hashList, transformed); + } + if(!hashesMatch) { + if(oldPair != null) { + LOGGER.warn("Hashes have changed, discarding cached version of " + className); } - }).getRight(); + byte[] transformed = super.transform(inputClass, className, reason); + if(transformed.length == 0) + transformed = EMPTY_BYTE_ARRAY; /* deduplicate */ + oldPair = Pair.of(hashList, transformed); + transformationCache.put(className, oldPair); + } + return oldPair.getRight(); } - private static final byte[] FORGE_HASH = LoadingModList.get().getModFileById("forge").getMods().get(0).getVersion().toString().getBytes(StandardCharsets.UTF_8); + private final byte[] FORGE_HASH = LoadingModList.get().getModFileById("forge").getMods().get(0).getVersion().toString().getBytes(StandardCharsets.UTF_8); private byte[] obtainHash(Object o, String className) { if(o instanceof CoreModBaseTransformer) { diff --git a/src/main/java/org/embeddedt/modernfix/classloading/service/EarlyLoadingService.java b/src/main/java/org/embeddedt/modernfix/classloading/service/EarlyLoadingService.java new file mode 100644 index 00000000..edcfa37d --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/classloading/service/EarlyLoadingService.java @@ -0,0 +1,76 @@ +package org.embeddedt.modernfix.classloading.service; + +import cpw.mods.modlauncher.ArgumentHandler; +import cpw.mods.modlauncher.Launcher; +import cpw.mods.modlauncher.api.IEnvironment; +import cpw.mods.modlauncher.api.ITransformationService; +import cpw.mods.modlauncher.api.ITransformer; +import cpw.mods.modlauncher.api.IncompatibleEnvironmentException; +import net.minecraftforge.fml.common.ObfuscationReflectionHelper; +import net.minecraftforge.fml.loading.ModDirTransformerDiscoverer; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.annotation.Nonnull; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * Used as a hook to class load on the ModLauncher class loader. + * + * We also need to ensure the ModernFix JAR is removed from the exclusions list, to ensure it loads as a regular mod. + */ +public class EarlyLoadingService implements ITransformationService { + private static final Logger LOGGER = LogManager.getLogger("ModernFixEarlyLoadingService"); + public Class cachingTransformerClass; + @Nonnull + @Override + public String name() { + return "modernfix"; + } + + public EarlyLoadingService() { + LOGGER.info("ModernFix (very) early loading"); + + + try { + ClassLoader loader = EarlyLoadingService.class.getClassLoader(); + Class.forName("cpw.mods.modlauncher.ClassTransformer", true, loader); + /* Allow ModernFix to be scanned like a mod */ + Field transformersField = ModDirTransformerDiscoverer.class.getDeclaredField("transformers"); + transformersField.setAccessible(true); + List transformers = (List)transformersField.get(null); + transformers.removeIf(path -> path.toString().contains("modernfix")); + + /* Load our new transformer */ + cachingTransformerClass = Class.forName("cpw.mods.modlauncher.ModernFixCachingClassTransformer", true, loader); + } catch(ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + @Override + public void initialize(IEnvironment environment) { + + } + + @Override + public void beginScanning(IEnvironment environment) { + + } + + @Override + public void onLoad(IEnvironment env, Set otherServices) throws IncompatibleEnvironmentException { + + } + + @Nonnull + @Override + public List transformers() { + return Collections.emptyList(); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/classloading/service/ModernFixRetransformingClassLoader.java b/src/main/java/org/embeddedt/modernfix/classloading/service/ModernFixRetransformingClassLoader.java new file mode 100644 index 00000000..27c17ca4 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/classloading/service/ModernFixRetransformingClassLoader.java @@ -0,0 +1,49 @@ +package org.embeddedt.modernfix.classloading.service; + +import com.google.common.collect.ImmutableMap; +import com.google.common.io.Resources; +import cpw.mods.gross.Java9ClassLoaderUtil; + +import java.io.IOException; +import java.net.URLClassLoader; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * This becomes the new "system" classloader and is used to retransform ModLauncher as needed. + */ +public class ModernFixRetransformingClassLoader extends URLClassLoader { + private static final ImmutableMap> TRANSFORMER_MAP = ImmutableMap.>builder() + .put("cpw.mods.modlauncher.Launcher", ModernFixRetransformingClassLoader::transformModLauncher) + .build(); + + static { + ClassLoader.registerAsParallelCapable(); + } + private final ClassLoader resourceFinder; + public ModernFixRetransformingClassLoader(ClassLoader resourceFinder) { + super(Java9ClassLoaderUtil.getSystemClassPathURLs(), null); + this.resourceFinder = resourceFinder; + } + + private static byte[] transformModLauncher(String s, byte[] in) { + return in; + } + + @Override + public Class loadClass(String s) throws ClassNotFoundException { + synchronized(this.getClassLoadingLock(s)) { + if(!TRANSFORMER_MAP.containsKey(s)) { + return super.loadClass(s); + } + byte[] classBytes; + try { + classBytes = Resources.toByteArray(this.resourceFinder.getResource(s.replace('.', '/') + ".class")); + } catch(IOException e) { + throw new ClassNotFoundException("Failed to load class bytes", e); + } + byte[] transformed = TRANSFORMER_MAP.get(s).apply(s, classBytes); + return defineClass(s, transformed, 0, transformed.length); + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java index 012afaf2..2b4b018c 100644 --- a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java +++ b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java @@ -1,5 +1,6 @@ package org.embeddedt.modernfix.core; +import com.google.common.io.Resources; import cpw.mods.modlauncher.*; import cpw.mods.modlauncher.api.LamdbaExceptionUtils; import net.minecraftforge.fml.common.ObfuscationReflectionHelper; @@ -11,8 +12,10 @@ import org.embeddedt.modernfix.core.config.Option; import org.objectweb.asm.tree.ClassNode; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; +import sun.misc.Unsafe; import java.io.File; +import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -27,6 +30,18 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin { private final Logger logger = LogManager.getLogger("ModernFix"); public static ModernFixEarlyConfig config = null; + private static Unsafe unsafe; + + static{ + try{ + final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + unsafe = (Unsafe) unsafeField.get(null); + }catch(Exception ex){ + ex.printStackTrace(); + } + } + public ModernFixMixinPlugin() { try { config = ModernFixEarlyConfig.load(new File("./config/modernfix-mixins.properties")); @@ -51,10 +66,14 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin { TransformStore store = ObfuscationReflectionHelper.getPrivateValue(ClassTransformer.class, t, "transformers"); LaunchPluginHandler pluginHandler = ObfuscationReflectionHelper.getPrivateValue(ClassTransformer.class, t, "pluginHandler"); TransformerAuditTrail trail = ObfuscationReflectionHelper.getPrivateValue(ClassTransformer.class, t, "auditTrail"); - Class newTransformerClass = Class.forName("cpw.mods.modlauncher.ModernFixCachingClassTransformer", true, ClassTransformer.class.getClassLoader()); + injectClassIntoSystemLoader("org.embeddedt.modernfix.classloading.api.IHashableTransformer"); + injectClassIntoSystemLoader("org.embeddedt.modernfix.classloading.hashers.CoreModTransformerHasher"); + injectClassIntoSystemLoader("org.embeddedt.modernfix.classloading.hashers.MixinTransformerHasher"); + Class newTransformerClass = injectClassIntoSystemLoader("cpw.mods.modlauncher.ModernFixCachingClassTransformer"); Constructor constructor = newTransformerClass.getConstructor(TransformStore.class, LaunchPluginHandler.class, TransformingClassLoader.class, TransformerAuditTrail.class); ClassTransformer newTransformer = (ClassTransformer)constructor.newInstance(store, pluginHandler, loader, trail); classTransformerField.set(loader, newTransformer); + logger.info("Successfully injected caching transformer"); } if(isOptionEnabled("launch.class_search_cache.ModernFixResourceFinder")) { @@ -69,11 +88,23 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin { resourceFinder = EnumerationHelper.mergeFunctors(resourceFinder, LamdbaExceptionUtils.rethrowFunction(dcl::findResources)); resourceFinderField.set(loader, resourceFinder); } - } catch(RuntimeException | ReflectiveOperationException e) { + } catch(RuntimeException | ReflectiveOperationException | IOException e) { logger.error("Failed to make classloading changes", e); } } + private Method defineClassMethod = null; + + private Class injectClassIntoSystemLoader(String className) throws ReflectiveOperationException, IOException { + ClassLoader systemLoader = ClassTransformer.class.getClassLoader(); + if(defineClassMethod == null) { + defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); + defineClassMethod.setAccessible(true); + } + byte[] newTransformerBytes = Resources.toByteArray(ModernFixMixinPlugin.class.getResource("/" + className.replace('.', '/') + ".class")); + return (Class)defineClassMethod.invoke(systemLoader, className, newTransformerBytes, 0, newTransformerBytes.length); + } + private Function> constructResourceFinder() throws ReflectiveOperationException { ModernFixResourceFinder.init(); Field servicesHandlerField = Launcher.class.getDeclaredField("transformationServicesHandler");