Support injecting new transformer outside of dev
This commit is contained in:
parent
ac99791d3a
commit
277ea384e7
|
|
@ -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<List<byte[]>, 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) {
|
||||
|
|
|
|||
|
|
@ -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<Path> transformers = (List<Path>)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<String> otherServices) throws IncompatibleEnvironmentException {
|
||||
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<ITransformer> transformers() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, BiFunction<String, byte[], byte[]>> TRANSFORMER_MAP = ImmutableMap.<String, BiFunction<String, byte[], byte[]>>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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, Enumeration<URL>> constructResourceFinder() throws ReflectiveOperationException {
|
||||
ModernFixResourceFinder.init();
|
||||
Field servicesHandlerField = Launcher.class.getDeclaredField("transformationServicesHandler");
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user