From 41eef0b6ab2d68ddd37a40fe3f13401508ac0030 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sat, 21 Jan 2023 12:33:17 -0500 Subject: [PATCH] New class transformer cache Hashes individual mixins/coremods and therefore needs no special invalidation logic --- .../ModernFixCachingClassTransformer.java | 365 +++++++----------- .../api/IHashableTransformer.java | 11 + .../hashers/CoreModTransformerHasher.java | 56 +++ .../hashers/MixinTransformerHasher.java | 97 +++++ .../modernfix/core/ModernFixMixinPlugin.java | 10 +- 5 files changed, 313 insertions(+), 226 deletions(-) create mode 100644 src/main/java/org/embeddedt/modernfix/classloading/api/IHashableTransformer.java create mode 100644 src/main/java/org/embeddedt/modernfix/classloading/hashers/CoreModTransformerHasher.java create mode 100644 src/main/java/org/embeddedt/modernfix/classloading/hashers/MixinTransformerHasher.java diff --git a/src/main/java/cpw/mods/modlauncher/ModernFixCachingClassTransformer.java b/src/main/java/cpw/mods/modlauncher/ModernFixCachingClassTransformer.java index aa38efeb..ec7f42b2 100644 --- a/src/main/java/cpw/mods/modlauncher/ModernFixCachingClassTransformer.java +++ b/src/main/java/cpw/mods/modlauncher/ModernFixCachingClassTransformer.java @@ -1,51 +1,45 @@ package cpw.mods.modlauncher; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.EOFException; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.BlockingQueue; +import java.io.*; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.LinkedBlockingQueue; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.io.Files; +import cpw.mods.modlauncher.api.ITransformer; import cpw.mods.modlauncher.api.ITransformerActivity; +import cpw.mods.modlauncher.serviceapi.ILaunchPluginService; +import net.minecraftforge.coremod.transformer.CoreModBaseTransformer; import net.minecraftforge.fml.loading.FMLPaths; +import net.minecraftforge.fml.loading.LoadingModList; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.classloading.api.IHashableTransformer; +import org.embeddedt.modernfix.classloading.hashers.CoreModTransformerHasher; +import org.embeddedt.modernfix.classloading.hashers.MixinTransformerHasher; +import org.objectweb.asm.Type; +import org.spongepowered.asm.launch.MixinLaunchPluginLegacy; import javax.lang.model.SourceVersion; public class ModernFixCachingClassTransformer extends ClassTransformer { private static final Logger LOGGER = LogManager.getLogger("ModernFixCachingTransformer"); - private Map> cache = new ConcurrentHashMap<>(); - private final static int QUEUE_SIZE = 512; // Config.recentCacheSize; - Optional> recentCache = QUEUE_SIZE < 0 ? Optional.empty() : - Optional.of(CacheBuilder.newBuilder().maximumSize(QUEUE_SIZE).build()); - private static final boolean FORCE_REBUILD_CACHE = Boolean.parseBoolean(System.getProperty("coretweaks.transformerCache.full.forceRebuild", "false")); + private static 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; + private final TransformingClassLoader transformingClassLoader; + private final HashMap>> transformersByClass; - public static final boolean DEBUG_PRINT = true; + private static final int MAX_NUM_CLASSES = 10000; - private int lastSaveSize = 0; - private BlockingQueue dirtyClasses = new LinkedBlockingQueue(); - private SaveThread saveThread = new SaveThread(this); - - private static final File CLASS_CACHE_DAT = childFile(FMLPaths.GAMEDIR.get().resolve("modernfix").resolve("classTransformerFull.cache").toFile()); - private static final File CLASS_CACHE_DAT_ERRORED = childFile(FMLPaths.GAMEDIR.get().resolve("modernfix").resolve("classTransformerFull.cache.errored").toFile()); - private static final File CLASS_CACHE_DAT_TMP = childFile(FMLPaths.GAMEDIR.get().resolve("modernfix").resolve("classTransformerFull.cache~").toFile()); + private ConcurrentHashMap, byte[]>> transformationCache; private static File childFile(File file) { file.getParentFile().mkdirs(); @@ -60,216 +54,143 @@ public class ModernFixCachingClassTransformer extends ClassTransformer { return SourceVersion.isName(className); } - static class SaveThread extends Thread { - - private ModernFixCachingClassTransformer cacheTransformer; - - private int saveInterval = 10000; - - public SaveThread(ModernFixCachingClassTransformer ct) { - this.cacheTransformer = ct; - setName("CacheTransformer save thread"); - setDaemon(false); - } - - @Override - public void run() { - while(true) { - try { - Thread.sleep(saveInterval); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - cacheTransformer.doSave(); - } - } - } - public ModernFixCachingClassTransformer(TransformStore transformStore, LaunchPluginHandler pluginHandler, TransformingClassLoader transformingClassLoader, TransformerAuditTrail trail) { super(transformStore, pluginHandler, transformingClassLoader, trail); - - if(FORCE_REBUILD_CACHE) {// || Persistence.modsChanged()) { - clearCache(FORCE_REBUILD_CACHE ? "forceRebuild JVM flag was set." : "mods have changed."); - } else { - loadCache(); - } - saveThread.start(); - } - - private void clearCache(String reason) { - LOGGER.info("Rebuilding class cache, because " + reason); - CLASS_CACHE_DAT.delete(); - } - - public void doSave() { - saveCache(); - } - - private void loadCache() { - File inFile = CLASS_CACHE_DAT; - - if(inFile.exists()) { - LOGGER.info("Loading class cache."); - cache.clear(); - - try (DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(inFile)))){ - try { - while(true) { // EOFException should break the loop - String className = in.readUTF(); - int classLength = in.readInt(); - byte[] classData = new byte[classLength]; - int bytesRead = in.read(classData, 0, classLength); - - if(!isValidClassName(className)) { - throw new RuntimeException("Invalid class name: " + className); - } else if(bytesRead != classLength) { - throw new RuntimeException("Length of " + className + " doesn't match advertised length of " + classLength); - } else { - cache.put(className, Optional.of(classData)); - - superDebug("Loaded " + className); - } - } - } catch(EOFException eof) {} - } catch (Exception e) { - LOGGER.error("There was an error reading the transformer cache. A new one will be created. The previous one has been saved as " + CLASS_CACHE_DAT_ERRORED.getName() + " for inspection."); - CLASS_CACHE_DAT.renameTo(CLASS_CACHE_DAT_ERRORED); - e.printStackTrace(); - cache.clear(); - } - LOGGER.info("Loaded " + cache.size() + " cached classes."); - - lastSaveSize = cache.size(); - } else { - LOGGER.info("Couldn't find class cache file"); - } - } - - private void saveCacheFully() { - File outFile = CLASS_CACHE_DAT; - File outFileTmp = CLASS_CACHE_DAT_TMP; - - LOGGER.info("Performing full save of class cache (size: " + cache.size() + ")"); - saveCacheChunk(cache.keySet(), outFileTmp, false); - + this.transformStore = transformStore; + this.pluginHandler = pluginHandler; + this.transformingClassLoader = transformingClassLoader; + this.auditTrail = trail; + /* Build a lookup table of all transformers for a given class */ + this.transformersByClass = new HashMap<>(); try { - Files.move(outFileTmp, outFile); - } catch (IOException e) { - LOGGER.error("Failed to finish saving class cache"); - e.printStackTrace(); - } - } - - private void saveCache() { - if(dirtyClasses.isEmpty()) { - return; // don't save if the cache hasn't changed - } - - File outFile = CLASS_CACHE_DAT; - try { - outFile.createNewFile(); - } catch (IOException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } - - List classesToSave = new ArrayList(); - dirtyClasses.drainTo(classesToSave); - - if(DEBUG_PRINT) { - LOGGER.info("Saving class cache (size: " + lastSaveSize + " -> " + cache.size() + " | +" + classesToSave.size() + ")"); - } - saveCacheChunk(classesToSave, outFile, true); - - lastSaveSize += classesToSave.size(); - } - - private void saveCacheChunk(Collection classesToSave, File outFile, boolean append) { - try(DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(outFile, append)))){ - for(String name : classesToSave) { - Optional data = cache.get(name); - if(data != null && data.isPresent()) { - out.writeUTF(name); - out.writeInt(data.get().length); - out.write(data.get()); + Field transformersByTypeField = TransformStore.class.getDeclaredField("transformers"); + transformersByTypeField.setAccessible(true); + Field transformersMapField = TransformList.class.getDeclaredField("transformers"); + transformersMapField.setAccessible(true); + EnumMap> transformersByType = (EnumMap>)transformersByTypeField.get(this.transformStore); + for(TransformList transformList : transformersByType.values()) { + Map>> transformers = (Map>>)transformersMapField.get(transformList); + for(Map.Entry>> entry : transformers.entrySet()) { + String className = entry.getKey().getClassName().getClassName(); + List> transformerList = this.transformersByClass.computeIfAbsent(className, k -> new ArrayList<>()); + transformerList.addAll(entry.getValue()); } } - if(DEBUG_PRINT) { - LOGGER.info("Saved class cache"); + for(List> transformerList : this.transformersByClass.values()) { + transformerList.sort((t1, t2) -> Comparator.naturalOrder().compare(StringUtils.join(t1.labels(), " "), StringUtils.join(t2.labels(), " "))); } - } catch (IOException e) { - LOGGER.info("Exception saving class cache"); - // TODO Auto-generated catch block - e.printStackTrace(); + } catch(ReflectiveOperationException e) { + throw new RuntimeException(e); } + this.transformationCache = new ConcurrentHashMap<>(); + try(ObjectInputStream inStream = new ObjectInputStream(new FileInputStream(CLASS_CACHE_DAT))) { + this.transformationCache = (ConcurrentHashMap,byte[]>>)inStream.readObject(); + int size = 0; + /* Approximate the size in bytes */ + for(Map.Entry,byte[]>> entry : this.transformationCache.entrySet()) { + size += entry.getKey().length(); + size += entry.getValue().getRight().length; + for(byte[] hash : entry.getValue().getLeft()) { + size += hash.length; + } + } + LOGGER.info("Loaded transformer cache, contains " + this.transformationCache.size() + " classes and in-memory size is approximately " + FileUtils.byteCountToDisplaySize(size)); + } catch(IOException | ClassNotFoundException e) { + LOGGER.error("An error occured while loading transform cache", e); + } + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + LOGGER.info("Serializing transform cache to disk"); + try(ObjectOutputStream outStream = new ObjectOutputStream(new FileOutputStream(CLASS_CACHE_DAT))) { + outStream.writeObject(transformationCache); + } catch(IOException e) { + LOGGER.error("An error occured while serializing transform cache", e); + } + }, "ModernFix transformer shutdown thread")); } - private String describeBytecode(byte[] basicClass) { - return basicClass == null ? "null" : String.format("length: %d, hash: %x", basicClass.length, basicClass.hashCode()); - } - + /** + * Check the hashed list of transformers and use a cached version of the class if possible. This code needs + * to be very fast as the entire point is to spend very little time doing transformation work that was done before. + * @param inputClass The bytecode to be transformed + * @param className Name of the class + * @param reason Reason for the class being loaded + * @return The transformed version of the class + */ @Override - public byte[] transform(byte[] basicClass, String transformedName, String reason) { + public byte[] transform(byte[] inputClass, String className, String reason) { /* We only want to cache actual transformations */ - if(!ITransformerActivity.CLASSLOADING_REASON.equals(reason) || basicClass.length == 0) { - return super.transform(basicClass, transformedName, reason); + if(!ITransformerActivity.CLASSLOADING_REASON.equals(reason)) { + return super.transform(inputClass, className, reason); } - byte[] result = null; - String name = transformedName; - - try { - boolean dontCache = false; - /* - for(String badPrefix : badClasses) { - if(transformedName.startsWith(badPrefix)){ - dontCache = true; - break; + final String internalName = className.replace('.', '/'); + final Type classDesc = Type.getObjectType(internalName); + final EnumMap> launchPluginTransformerSet = pluginHandler.computeLaunchPluginTransformerSet(classDesc, false, reason, this.auditTrail); + final boolean needsTransforming = transformStore.needsTransforming(internalName); + if (!needsTransforming && launchPluginTransformerSet.isEmpty()) { + return inputClass; + } + /* Now compute the hash list for the required transformers */ + ArrayList hashList = new ArrayList<>(); + for(List pluginList : launchPluginTransformerSet.values()) { + pluginList.sort((service1, service2) -> Comparator.naturalOrder().compare(service1.name(), service2.name())); + for(ILaunchPluginService service : pluginList) { + byte[] hash = obtainHash(service, className); + if(hash == null) { + return super.transform(inputClass, className, reason); + } + hashList.add(hash); + } + } + if(needsTransforming) { + List> transformers = this.transformersByClass.get(internalName); + if(transformers != null) { + for(ITransformer transformer : transformers) { + byte[] hash = obtainHash(transformer, className); + if(hash == null) { + return super.transform(inputClass, className, reason); + } + hashList.add(hash); } } - */ - - if(cache.containsKey(transformedName) && !dontCache) { - if(cache.get(transformedName).isPresent()) { // we still remember it - result = cache.get(transformedName).get(); - - if(recentCache.isPresent()) { - // classes are only loaded once, so no need to keep it around in RAM - cache.put(transformedName, Optional.empty()); - - // but keep it around in case it's needed again by another transformer in the chain - recentCache.get().put(transformedName, result); - } - } else if(recentCache.isPresent()){ // we have forgotten it, hopefully it's still around in the recent queue - result = recentCache.get().getIfPresent(transformedName); - if(result == null) { - LOGGER.warn("Couldn't find " + transformedName + " in cache. Is recent queue too small? (" + QUEUE_SIZE + ")"); + } + /* 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; } } } - if(result == null){ - basicClass = super.transform(basicClass, transformedName, reason); - - if(basicClass != null && !dontCache) { - cache.put(transformedName, Optional.of(basicClass)); // then cache it - dirtyClasses.add(transformedName); - } - result = basicClass; + 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); + return Pair.of(hashList, transformed); } - if(result != null && recentCache.isPresent() && !dontCache) { - recentCache.get().put(transformedName, result); - } - } catch(Exception e) { - throw e; // pass it to LaunchClassLoader, who will handle it - } finally { - //wrappedTransformers.alt = this; - } - return result; + }).getRight(); } - private void superDebug(String msg) { - if(DEBUG_PRINT) { - LOGGER.debug(msg); + private static 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) { + return CoreModTransformerHasher.obtainHash((CoreModBaseTransformer)o); + } else if(o instanceof MixinLaunchPluginLegacy) { + return MixinTransformerHasher.obtainHash((MixinLaunchPluginLegacy)o, className); + } else if(o instanceof IHashableTransformer) { + return ((IHashableTransformer)o).getHashForClass(className); + } else if(o.getClass().getName().startsWith("net.minecraftforge.")) { + return FORGE_HASH; + } else { + LOGGER.warn("No hash implementation found for: " + o.getClass().getName()); + return null; } } } diff --git a/src/main/java/org/embeddedt/modernfix/classloading/api/IHashableTransformer.java b/src/main/java/org/embeddedt/modernfix/classloading/api/IHashableTransformer.java new file mode 100644 index 00000000..f867a16e --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/classloading/api/IHashableTransformer.java @@ -0,0 +1,11 @@ +package org.embeddedt.modernfix.classloading.api; + +public interface IHashableTransformer { + /** + * Called on an ILaunchPluginService or ITransformer to obtain a unique hash of the transformations that will be applied. + * Used to invalidate the transformation cache when needed. + * @param className Name of class being transformed + * @return A unique hash of the transformations that will be applied + */ + byte[] getHashForClass(String className); +} diff --git a/src/main/java/org/embeddedt/modernfix/classloading/hashers/CoreModTransformerHasher.java b/src/main/java/org/embeddedt/modernfix/classloading/hashers/CoreModTransformerHasher.java new file mode 100644 index 00000000..da6e70fe --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/classloading/hashers/CoreModTransformerHasher.java @@ -0,0 +1,56 @@ +package org.embeddedt.modernfix.classloading.hashers; + +import net.minecraftforge.coremod.CoreMod; +import net.minecraftforge.coremod.transformer.CoreModBaseTransformer; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.file.Files; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.concurrent.ConcurrentHashMap; + +public class CoreModTransformerHasher { + private static final ConcurrentHashMap hashForCoremod; + private static Field coremodField; + private static ThreadLocal coremodHasher = ThreadLocal.withInitial(() -> { + try { + return MessageDigest.getInstance("SHA-256"); + } catch(NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + }); + + static { + hashForCoremod = new ConcurrentHashMap<>(); + try { + coremodField = CoreModBaseTransformer.class.getDeclaredField("coreMod"); + coremodField.setAccessible(true); + } catch(ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + private static byte[] hashCoreMod(CoreMod coreMod) { + byte[] coreModContents; + try { + coreModContents = Files.readAllBytes(coreMod.getPath()); + } catch(IOException e) { + throw new RuntimeException(e); + } + MessageDigest hasher = coremodHasher.get(); + byte[] hash = hasher.digest(coreModContents); + hasher.reset(); + return hash; + } + + public static byte[] obtainHash(CoreModBaseTransformer transformer) { + CoreMod coremod; + try { + coremod = (CoreMod)coremodField.get(transformer); + } catch(ReflectiveOperationException e) { + throw new RuntimeException(e); + } + return hashForCoremod.computeIfAbsent(coremod, CoreModTransformerHasher::hashCoreMod); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/classloading/hashers/MixinTransformerHasher.java b/src/main/java/org/embeddedt/modernfix/classloading/hashers/MixinTransformerHasher.java new file mode 100644 index 00000000..63d31c7c --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/classloading/hashers/MixinTransformerHasher.java @@ -0,0 +1,97 @@ +package org.embeddedt.modernfix.classloading.hashers; + +import com.google.common.collect.HashMultimap; +import com.google.common.io.Resources; +import org.spongepowered.asm.launch.IClassProcessor; +import org.spongepowered.asm.launch.MixinLaunchPluginLegacy; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; +import org.spongepowered.asm.service.MixinService; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.URL; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; + +public class MixinTransformerHasher { + private static HashMap hashesByClass = null; + private final static MessageDigest hasher; + + static { + try { + hasher = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + public static byte[] obtainHash(MixinLaunchPluginLegacy plugin, String className) { + synchronized (MixinTransformerHasher.class) { + if(hashesByClass == null) { + hashesByClass = new HashMap<>(); + HashMap> mixinsByClass = new HashMap<>(); + try { + Field processorsField = MixinLaunchPluginLegacy.class.getDeclaredField("processors"); + processorsField.setAccessible(true); + List processors = (List)processorsField.get(plugin); + Object transformHandler = null; + for(IClassProcessor processor : processors) { + if(processor.getClass().getName().equals("org.spongepowered.asm.service.modlauncher.MixinTransformationHandler")) { + transformHandler = processor; + break; + } + } + if(transformHandler == null) + throw new IllegalStateException("Mixin transform handler not found"); + Field transformerField = transformHandler.getClass().getDeclaredField("transformer"); + transformerField.setAccessible(true); + Object transformer = transformerField.get(transformHandler); + Field processorField = transformer.getClass().getDeclaredField("processor"); + processorField.setAccessible(true); + Object processor = processorField.get(transformer); + Field configsField = processor.getClass().getDeclaredField("configs"); + configsField.setAccessible(true); + List configs = (List)configsField.get(processor); + Field mixinsField = Class.forName("org.spongepowered.asm.mixin.transformer.MixinConfig").getDeclaredField("mixins"); + mixinsField.setAccessible(true); + /* getTargetClasses can't be used because it's package-private */ + Field classNamesField = Class.forName("org.spongepowered.asm.mixin.transformer.MixinInfo").getDeclaredField("targetClassNames"); + classNamesField.setAccessible(true); + for(Object config : configs) { + List mixins = (List)mixinsField.get(config); + for(IMixinInfo mixin : mixins) { + List targetClassNames = (List)classNamesField.get(mixin); + for(String s : targetClassNames) { + mixinsByClass.computeIfAbsent(s, k -> new ArrayList<>()).add(mixin); + } + } + } + for(ArrayList infos : mixinsByClass.values()) { + infos.sort((info1, info2) -> Comparator.naturalOrder().compare(info1.getClassName(), info2.getClassName())); + } + /* Now go through each class name and hash it */ + for(Map.Entry> mixinsForClass : mixinsByClass.entrySet()) { + hasher.reset(); + for(IMixinInfo mixin : mixinsForClass.getValue()) { + URL url = Thread.currentThread().getContextClassLoader().getResource(mixin.getClassName().replace('.', '/') + ".class"); + if(url == null) + throw new IllegalStateException("Can't find " + mixin.getClassName()); + byte[] bytecode; + try { + bytecode = Resources.asByteSource(url).read(); + } catch(IOException e) { + throw new RuntimeException(e); + } + hasher.update(bytecode); + } + hashesByClass.put(mixinsForClass.getKey().replace('/', '.'), hasher.digest()); + } + } catch(ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + } + return hashesByClass.getOrDefault(className, new byte[0]); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java index dde272a8..012afaf2 100644 --- a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java +++ b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java @@ -13,6 +13,7 @@ import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; import java.io.File; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URL; @@ -26,9 +27,6 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin { private final Logger logger = LogManager.getLogger("ModernFix"); public static ModernFixEarlyConfig config = null; - private static final boolean USE_TRANSFORMER_CACHE = false; - private static final boolean USE_CLASS_LOCATION_CACHE = false; - public ModernFixMixinPlugin() { try { config = ModernFixEarlyConfig.load(new File("./config/modernfix-mixins.properties")); @@ -53,7 +51,11 @@ 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"); - classTransformerField.set(loader, new ModernFixCachingClassTransformer(store, pluginHandler, (TransformingClassLoader)loader, trail)); + Class newTransformerClass = Class.forName("cpw.mods.modlauncher.ModernFixCachingClassTransformer", true, ClassTransformer.class.getClassLoader()); + 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")) { Field resourceFinderField = TransformingClassLoader.class.getDeclaredField("resourceFinder");