Support injecting new transformer outside of dev

This commit is contained in:
embeddedt 2023-01-21 20:31:34 -05:00
parent ac99791d3a
commit 277ea384e7
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
4 changed files with 179 additions and 24 deletions

View File

@ -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) {

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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");