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 {
|
public class ModernFixCachingClassTransformer extends ClassTransformer {
|
||||||
private static final Logger LOGGER = LogManager.getLogger("ModernFixCachingTransformer");
|
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 LaunchPluginHandler pluginHandler;
|
||||||
private final TransformStore transformStore;
|
private final TransformStore transformStore;
|
||||||
private final TransformerAuditTrail auditTrail;
|
private final TransformerAuditTrail auditTrail;
|
||||||
|
|
@ -169,32 +169,31 @@ public class ModernFixCachingClassTransformer extends ClassTransformer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Check if the cache contains a transformed class matching these hashes */
|
/* Check if the cache contains a transformed class matching these hashes */
|
||||||
return transformationCache.compute(className, (name, oldPair) -> {
|
Pair<List<byte[]>, byte[]> oldPair = transformationCache.get(className);
|
||||||
boolean hashesMatch = true;
|
boolean hashesMatch = true;
|
||||||
if(oldPair == null || oldPair.getLeft().size() != hashList.size()) {
|
if(oldPair == null || oldPair.getLeft().size() != hashList.size()) {
|
||||||
hashesMatch = false;
|
hashesMatch = false;
|
||||||
} else {
|
} else {
|
||||||
for(int i = 0; i < oldPair.getLeft().size(); i++) {
|
for(int i = 0; i < oldPair.getLeft().size(); i++) {
|
||||||
if(!Arrays.equals(oldPair.getLeft().get(i), hashList.get(i))) {
|
if(!Arrays.equals(oldPair.getLeft().get(i), hashList.get(i))) {
|
||||||
hashesMatch = false;
|
hashesMatch = false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(hashesMatch)
|
}
|
||||||
return oldPair;
|
if(!hashesMatch) {
|
||||||
else {
|
if(oldPair != null) {
|
||||||
if(oldPair != null) {
|
LOGGER.warn("Hashes have changed, discarding cached version of " + className);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}).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) {
|
private byte[] obtainHash(Object o, String className) {
|
||||||
if(o instanceof CoreModBaseTransformer) {
|
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;
|
package org.embeddedt.modernfix.core;
|
||||||
|
|
||||||
|
import com.google.common.io.Resources;
|
||||||
import cpw.mods.modlauncher.*;
|
import cpw.mods.modlauncher.*;
|
||||||
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
|
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
|
||||||
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
|
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.objectweb.asm.tree.ClassNode;
|
||||||
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
|
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
|
||||||
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
|
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
|
||||||
|
import sun.misc.Unsafe;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
@ -27,6 +30,18 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin {
|
||||||
private final Logger logger = LogManager.getLogger("ModernFix");
|
private final Logger logger = LogManager.getLogger("ModernFix");
|
||||||
public static ModernFixEarlyConfig config = null;
|
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() {
|
public ModernFixMixinPlugin() {
|
||||||
try {
|
try {
|
||||||
config = ModernFixEarlyConfig.load(new File("./config/modernfix-mixins.properties"));
|
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");
|
TransformStore store = ObfuscationReflectionHelper.getPrivateValue(ClassTransformer.class, t, "transformers");
|
||||||
LaunchPluginHandler pluginHandler = ObfuscationReflectionHelper.getPrivateValue(ClassTransformer.class, t, "pluginHandler");
|
LaunchPluginHandler pluginHandler = ObfuscationReflectionHelper.getPrivateValue(ClassTransformer.class, t, "pluginHandler");
|
||||||
TransformerAuditTrail trail = ObfuscationReflectionHelper.getPrivateValue(ClassTransformer.class, t, "auditTrail");
|
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);
|
Constructor<?> constructor = newTransformerClass.getConstructor(TransformStore.class, LaunchPluginHandler.class, TransformingClassLoader.class, TransformerAuditTrail.class);
|
||||||
ClassTransformer newTransformer = (ClassTransformer)constructor.newInstance(store, pluginHandler, loader, trail);
|
ClassTransformer newTransformer = (ClassTransformer)constructor.newInstance(store, pluginHandler, loader, trail);
|
||||||
classTransformerField.set(loader, newTransformer);
|
classTransformerField.set(loader, newTransformer);
|
||||||
|
|
||||||
logger.info("Successfully injected caching transformer");
|
logger.info("Successfully injected caching transformer");
|
||||||
}
|
}
|
||||||
if(isOptionEnabled("launch.class_search_cache.ModernFixResourceFinder")) {
|
if(isOptionEnabled("launch.class_search_cache.ModernFixResourceFinder")) {
|
||||||
|
|
@ -69,11 +88,23 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin {
|
||||||
resourceFinder = EnumerationHelper.mergeFunctors(resourceFinder, LamdbaExceptionUtils.rethrowFunction(dcl::findResources));
|
resourceFinder = EnumerationHelper.mergeFunctors(resourceFinder, LamdbaExceptionUtils.rethrowFunction(dcl::findResources));
|
||||||
resourceFinderField.set(loader, resourceFinder);
|
resourceFinderField.set(loader, resourceFinder);
|
||||||
}
|
}
|
||||||
} catch(RuntimeException | ReflectiveOperationException e) {
|
} catch(RuntimeException | ReflectiveOperationException | IOException e) {
|
||||||
logger.error("Failed to make classloading changes", 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 {
|
private Function<String, Enumeration<URL>> constructResourceFinder() throws ReflectiveOperationException {
|
||||||
ModernFixResourceFinder.init();
|
ModernFixResourceFinder.init();
|
||||||
Field servicesHandlerField = Launcher.class.getDeclaredField("transformationServicesHandler");
|
Field servicesHandlerField = Launcher.class.getDeclaredField("transformationServicesHandler");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user