Use ASM for data manipulation

This commit is contained in:
JFronny 2021-09-29 18:52:43 +02:00
parent 939e7d817e
commit cddfd6c0fa
No known key found for this signature in database
GPG Key ID: BEC5ACBBD4EE17E5
23 changed files with 454 additions and 250 deletions

View File

@ -3,6 +3,7 @@ package io.gitlab.jfronny.libjf;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.gitlab.jfronny.libjf.gson.HiddenAnnotationExclusionStrategy;
import net.fabricmc.loader.api.FabricLoader;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -19,4 +20,5 @@ public class LibJf {
.addSerializationExclusionStrategy(new HiddenAnnotationExclusionStrategy())
.setPrettyPrinting()
.create();
public static boolean DEV = FabricLoader.getInstance().isDevelopmentEnvironment();
}

View File

@ -0,0 +1,5 @@
package io.gitlab.jfronny.libjf.interfaces;
public interface ThrowingRunnable {
void run() throws Throwable;
}

View File

@ -0,0 +1,5 @@
package io.gitlab.jfronny.libjf.interfaces;
public interface ThrowingSupplier<T> {
T get() throws Throwable;
}

View File

@ -2,5 +2,5 @@ archivesBaseName = "libjf-data-manipulation-v0"
version = "1.0"
dependencies {
moduleDependencies(project, ["libjf-base"])
moduleDependencies(project, ["libjf-base", "libjf-unsafe-v0"])
}

View File

@ -0,0 +1,96 @@
package io.gitlab.jfronny.libjf.data.manipulation;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.unsafe.asm.AsmConfig;
import io.gitlab.jfronny.libjf.unsafe.asm.AsmTransformer;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.Patch;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.modification.MethodModificationPatch;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.targeting.InterfaceImplTargetPatch;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import java.io.IOException;
import java.util.Set;
public class DataAsmConfig implements AsmConfig {
private static final String TARGET_CLASS_INTERMEDIARY = "net.minecraft.class_3262";
private static final String HOOK_IMPLEMENTATION = Type.getInternalName(HookImplementation.class);
@Override
public Set<String> skipClasses() {
return Set.of(getClass(TARGET_CLASS_INTERMEDIARY));
}
@Override
public Set<Patch> getPatches() {
// https://maven.fabricmc.net/docs/yarn-21w38a+build.9/net/minecraft/resource/ResourcePack.html
return Set.of(new InterfaceImplTargetPatch(TARGET_CLASS_INTERMEDIARY, new MethodModificationPatch(TARGET_CLASS_INTERMEDIARY, Set.of(
// open root
new MethodModificationPatch.MethodDescriptorPatch("method_14410", "(Ljava/lang/String;)Ljava/io/InputStream;", method -> {
catchEx(method, "hookOpenRootEx", new String[]{"java/lang/String"});
hookReturn(method, "hookOpenRoot", "Ljava/io/InputStream;", new String[]{"java/lang/String"});
}),
// open
new MethodModificationPatch.MethodDescriptorPatch("method_14405", "(Lnet/minecraft/class_3264;Lnet/minecraft/class_2960;)Ljava/io/InputStream;", method -> {
catchEx(method, "hookOpenEx", new String[]{"net/minecraft/class_3264", "net/minecraft/class_2960"});
hookReturn(method, "hookOpen", "Ljava/io/InputStream;", new String[]{"net/minecraft/class_3264", "net/minecraft/class_2960"});
}),
// find resource
new MethodModificationPatch.MethodDescriptorPatch("method_14408", "(Lnet/minecraft/class_3264;Ljava/lang/String;Ljava/lang/String;ILjava/util/function/Predicate;)Ljava/util/Collection;", method -> {
hookReturn(method, "hookFindResources", "Ljava/util/Collection;", new String[]{"net/minecraft/class_3264", "java/lang/String", "java/lang/String", "I", "java/util/function/Predicate"});
}),
// contains
new MethodModificationPatch.MethodDescriptorPatch("method_14411", "(Lnet/minecraft/class_3264;Lnet/minecraft/class_2960;)Z", method -> {
hookReturn(method, "hookContains", "Z", new String[]{"net/minecraft/class_3264", "net/minecraft/class_2960"});
})
))));
}
private void hookReturn(MethodNode method, String targetMethod, String targetType, String[] extraParamTypes) {
for (AbstractInsnNode node : method.instructions) {
if (node.getOpcode() == Opcodes.IRETURN || node.getOpcode() == Opcodes.ARETURN) {
method.instructions.insertBefore(node, buildInvoker(targetMethod, targetType, targetType, extraParamTypes));
}
}
}
private void catchEx(MethodNode method, String targetMethod, String[] extraParamTypes) {
LabelNode start = new LabelNode();
LabelNode end = new LabelNode();
LabelNode handler = new LabelNode();
method.instructions.insertBefore(method.instructions.getFirst(), start);
method.instructions.insertBefore(method.instructions.getLast(), end);
method.instructions.add(handler);
method.instructions.add(buildInvoker(targetMethod, "Ljava/io/IOException;", "Ljava/io/InputStream;", extraParamTypes));
method.instructions.add(new InsnNode(Opcodes.ARETURN));
method.tryCatchBlocks.add(new TryCatchBlockNode(start, end, handler, Type.getInternalName(IOException.class)));
}
private InsnList buildInvoker(String targetMethod, String inType, String outType, String[] extraParamTypes) {
InsnList instructions = new InsnList();
instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
StringBuilder descriptor = new StringBuilder("(").append(inType);
descriptor.append('L').append(getClassInternal("net/minecraft/class_3262")).append(';');
for (int i = 0; i < extraParamTypes.length; i++) {
if (extraParamTypes[i].equals("I"))
instructions.add(new VarInsnNode(Opcodes.ILOAD, i + 1));
else
instructions.add(new VarInsnNode(Opcodes.ALOAD, i + 1));
if (extraParamTypes[i].length() == 1)
descriptor.append(extraParamTypes[i]);
else
descriptor.append('L').append(getClassInternal(extraParamTypes[i])).append(';');
}
descriptor.append(")").append(outType);
instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, HOOK_IMPLEMENTATION, targetMethod, descriptor.toString()));
return instructions;
}
private String getClassInternal(String name) {
return getClass(name.replace('/', '.')).replace('.', '/');
}
private String getClass(String name) {
return AsmTransformer.MAPPING_RESOLVER.mapClassName(AsmTransformer.INTERMEDIARY, name);
}
}

View File

@ -0,0 +1,72 @@
package io.gitlab.jfronny.libjf.data.manipulation;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.ResourcePath;
import net.minecraft.resource.ResourcePack;
import net.minecraft.resource.ResourceType;
import net.minecraft.util.Identifier;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class HookImplementation {
private static boolean disabled = false;
public static <T> T disable(Supplier<T> then) {
try {
disabled = true;
return then.get();
}
finally {
disabled = false;
}
}
public static void disable(Runnable then) {
try {
disabled = true;
then.run();
} finally {
disabled = false;
}
}
public static boolean hookContains(boolean value, ResourcePack pack, ResourceType type, Identifier id) {
return disabled ? value : UserResourceEvents.CONTAINS.invoker().contains(type, id, value, pack);
}
public static InputStream hookOpen(InputStream value, ResourcePack pack, ResourceType type, Identifier id) throws IOException {
InputStream is = UserResourceEvents.OPEN.invoker().open(type, id, value, pack);
if (is == null)
throw new FileNotFoundException(new ResourcePath(type, id).getName() + "CN");
return is;
}
public static InputStream hookOpenEx(IOException ex, ResourcePack pack, ResourceType type, Identifier id) throws IOException {
try {
return hookOpen(null, pack, type, id);
}
catch (Throwable t) {
throw ex;
}
}
public static Collection<Identifier> hookFindResources(Collection<Identifier> value, ResourcePack pack, ResourceType type, String namespace, String prefix, int maxDepth, Predicate<String> pathFilter) {
return UserResourceEvents.FIND_RESOURCE.invoker().findResources(type, namespace, prefix, maxDepth, pathFilter, value, pack);
}
public static InputStream hookOpenRoot(InputStream value, ResourcePack pack, String fileName) throws IOException {
InputStream is = value;
is = UserResourceEvents.OPEN_ROOT.invoker().openRoot(fileName, is, pack);
if (is == null)
throw new FileNotFoundException(fileName);
return is;
}
public static InputStream hookOpenRootEx(IOException ex, ResourcePack pack, String fileName) throws IOException {
try {
return hookOpenRoot(null, pack, fileName);
}
catch (Throwable t) {
throw ex;
}
}
}

View File

@ -2,6 +2,7 @@ package io.gitlab.jfronny.libjf.data.manipulation;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.resource.ResourcePack;
import net.minecraft.resource.ResourceType;
import net.minecraft.util.Identifier;
@ -44,18 +45,18 @@ public class UserResourceEvents {
}));
public interface Contains {
boolean contains(ResourceType type, Identifier id, boolean previous, WrappedPack pack);
boolean contains(ResourceType type, Identifier id, boolean previous, ResourcePack pack);
}
public interface FindResource {
Collection<Identifier> findResources(ResourceType type, String namespace, String prefix, int maxDepth, Predicate<String> pathFilter, Collection<Identifier> previous, WrappedPack pack);
Collection<Identifier> findResources(ResourceType type, String namespace, String prefix, int maxDepth, Predicate<String> pathFilter, Collection<Identifier> previous, ResourcePack pack);
}
public interface Open {
InputStream open(ResourceType type, Identifier id, InputStream previous, WrappedPack pack) throws IOException;
InputStream open(ResourceType type, Identifier id, InputStream previous, ResourcePack pack) throws IOException;
}
public interface OpenRoot {
InputStream openRoot(String fileName, InputStream previous, WrappedPack pack) throws IOException;
InputStream openRoot(String fileName, InputStream previous, ResourcePack pack) throws IOException;
}
}

View File

@ -1,15 +0,0 @@
package io.gitlab.jfronny.libjf.data.manipulation;
import io.gitlab.jfronny.libjf.data.manipulation.impl.WrappedResourcePack;
import net.minecraft.resource.ResourcePack;
//This is a class for binary compatibility with mods using libjf
public interface WrappedPack extends ResourcePack {
ResourcePack getUnderlying();
public static WrappedPack create(ResourcePack underlying) {
//if (underlying instanceof ModNioResourcePack mi)
// return new WrappedModNioResourcePack(mi);
return new WrappedResourcePack(underlying);
}
}

View File

@ -1,45 +0,0 @@
package io.gitlab.jfronny.libjf.data.manipulation.impl;
import io.gitlab.jfronny.libjf.ResourcePath;
import io.gitlab.jfronny.libjf.data.manipulation.UserResourceEvents;
import io.gitlab.jfronny.libjf.data.manipulation.WrappedPack;
import net.minecraft.resource.ResourceType;
import net.minecraft.util.Identifier;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.function.Predicate;
public class EventCallImpl {
public static InputStream hookOpenRoot(WrappedPack pack, String fileName) throws IOException {
InputStream is = null;
IOException ex = null;
try {
is = pack.getUnderlying().openRoot(fileName);
}
catch (IOException ex1) {
ex = ex1;
}
is = UserResourceEvents.OPEN_ROOT.invoker().openRoot(fileName, is, pack);
if (is == null)
throw ex == null ? new FileNotFoundException(fileName) : ex;
return is;
}
public static InputStream hookOpen(WrappedPack pack, ResourceType type, Identifier id) throws IOException {
InputStream is = UserResourceEvents.OPEN.invoker().open(type, id, pack.getUnderlying().contains(type, id) ? pack.getUnderlying().open(type, id) : null, pack);
if (is == null)
throw new FileNotFoundException(new ResourcePath(type, id).getName());
return is;
}
public static Collection<Identifier> hookFindResources(WrappedPack pack, ResourceType type, String namespace, String prefix, int maxDepth, Predicate<String> pathFilter) {
return UserResourceEvents.FIND_RESOURCE.invoker().findResources(type, namespace, prefix, maxDepth, pathFilter, pack.getUnderlying().findResources(type, namespace, prefix, maxDepth, pathFilter), pack);
}
public static boolean hookContains(WrappedPack pack, ResourceType type, Identifier id) {
return UserResourceEvents.CONTAINS.invoker().contains(type, id, pack.getUnderlying().contains(type, id), pack);
}
}

View File

@ -1,67 +0,0 @@
package io.gitlab.jfronny.libjf.data.manipulation.impl;
import io.gitlab.jfronny.libjf.data.manipulation.WrappedPack;
import net.minecraft.resource.ResourcePack;
import net.minecraft.resource.ResourceType;
import net.minecraft.resource.metadata.ResourceMetadataReader;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Set;
import java.util.function.Predicate;
public class WrappedResourcePack implements WrappedPack {
ResourcePack pack;
public WrappedResourcePack(ResourcePack pack) {
this.pack = pack;
}
@Nullable
@Override
public InputStream openRoot(String fileName) throws IOException {
return EventCallImpl.hookOpenRoot(this, fileName);
}
@Override
public InputStream open(ResourceType type, Identifier id) throws IOException {
return EventCallImpl.hookOpen(this, type, id);
}
@Override
public Collection<Identifier> findResources(ResourceType type, String namespace, String prefix, int maxDepth, Predicate<String> pathFilter) {
return EventCallImpl.hookFindResources(this, type, namespace, prefix, maxDepth, pathFilter);
}
@Override
public boolean contains(ResourceType type, Identifier id) {
return EventCallImpl.hookContains(this, type, id);
}
@Override
public Set<String> getNamespaces(ResourceType type) {
return pack.getNamespaces(type);
}
@Nullable
@Override
public <T> T parseMetadata(ResourceMetadataReader<T> metaReader) throws IOException {
return pack.parseMetadata(metaReader);
}
@Override
public String getName() {
return pack.getName();
}
@Override
public void close() {
pack.close();
}
public ResourcePack getUnderlying() {
return pack;
}
}

View File

@ -1,21 +0,0 @@
package io.gitlab.jfronny.libjf.data.manipulation.mixin;
import io.gitlab.jfronny.libjf.data.manipulation.WrappedPack;
import net.fabricmc.fabric.impl.resource.loader.FabricModResourcePack;
import net.fabricmc.fabric.impl.resource.loader.ModNioResourcePack;
import net.minecraft.resource.ReloadableResourceManagerImpl;
import net.minecraft.resource.ResourcePack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
@Mixin(ReloadableResourceManagerImpl.class)
public class ReloadableResourceManagerImplMixin {
@ModifyVariable(method = "addPack(Lnet/minecraft/resource/ResourcePack;)V", at = @At("HEAD"), argsOnly = true, ordinal = 0)
private ResourcePack modifyPack(ResourcePack pack) {
if (pack instanceof WrappedPack || pack instanceof ModNioResourcePack || pack instanceof FabricModResourcePack) { //TODO use ASM for this to allow mod resource packs
return pack;
}
return WrappedPack.create(pack);
}
}

View File

@ -12,6 +12,11 @@
"license": "MIT",
"environment": "*",
"mixins": ["libjf-data-manipulation-v0.mixins.json"],
"entrypoints": {
"libjf:asm": [
"io.gitlab.jfronny.libjf.data.manipulation.DataAsmConfig"
]
},
"custom": {
"modmenu": {
"parent": "libjf"

View File

@ -4,8 +4,7 @@
"package": "io.gitlab.jfronny.libjf.data.manipulation.mixin",
"compatibilityLevel": "JAVA_16",
"mixins": [
"RecipeManagerMixin",
"ReloadableResourceManagerImplMixin"
"RecipeManagerMixin"
],
"client": [
],

View File

@ -5,20 +5,20 @@ import io.gitlab.jfronny.libjf.data.manipulation.RecipeUtil;
import io.gitlab.jfronny.libjf.data.manipulation.UserResourceEvents;
import net.fabricmc.api.ModInitializer;
import net.minecraft.item.Items;
import net.minecraft.resource.AbstractFileResourcePack;
import net.minecraft.resource.DirectoryResourcePack;
public class TestEntrypoint implements ModInitializer {
@Override
public void onInitialize() {
// This should prevent resource packs from doing anything if my hooks are working and
UserResourceEvents.OPEN.register((type, id, previous, pack) -> {
if (pack.getUnderlying() instanceof AbstractFileResourcePack) {
if (pack instanceof DirectoryResourcePack) {
LibJf.LOGGER.info(pack.getName() + " opened " + type.name() + "/" + id.toString());
}
return previous;
});
UserResourceEvents.CONTAINS.register((type, id, previous, pack) -> {
if (pack.getUnderlying() instanceof AbstractFileResourcePack) {
if (pack instanceof DirectoryResourcePack) {
return false;
}
return previous;

View File

@ -2,8 +2,8 @@ package io.gitlab.jfronny.libjf.unsafe;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.unsafe.asm.AsmConfig;
import io.gitlab.jfronny.libjf.unsafe.asm.AsmConfigBaker;
import io.gitlab.jfronny.libjf.unsafe.asm.AsmTransformer;
import io.gitlab.jfronny.libjf.unsafe.asm.BakedAsmConfig;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
@ -11,6 +11,7 @@ import org.spongepowered.asm.mixin.transformer.FabricMixinTransformerProxy;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -33,16 +34,15 @@ public class MixinPlugin implements IMixinConfigPlugin {
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
AsmTransformer mixinTransformer = (AsmTransformer) unsafe.allocateInstance(AsmTransformer.class);
mixinTransformer.delegate = (FabricMixinTransformerProxy) mixinTransformerField.get(delegate);
AsmConfigBaker baker = new AsmConfigBaker();
AsmTransformer.INSTANCE = (AsmTransformer) unsafe.allocateInstance(AsmTransformer.class);
AsmTransformer.INSTANCE.delegate = (FabricMixinTransformerProxy) mixinTransformerField.get(delegate);
AsmTransformer.INSTANCE.asmConfigs = new HashSet<>();
DynamicEntry.execute(LibJf.MOD_ID + ":asm", AsmConfig.class, s -> {
LibJf.LOGGER.info("Discovered LibJF asm plugin in " + s.modId());
baker.add(s.instance());
AsmTransformer.INSTANCE.asmConfigs.add(new BakedAsmConfig(s.instance()));
});
mixinTransformer.asmConfig = baker.bake();
mixinTransformerField.set(delegate, mixinTransformer);
mixinTransformerField.set(delegate, AsmTransformer.INSTANCE);
} catch (NoSuchFieldException | IllegalAccessException | InstantiationException e) {
LibJf.LOGGER.error("Could not initialize LibJF ASM", e);
}

View File

@ -1,38 +0,0 @@
package io.gitlab.jfronny.libjf.unsafe.asm;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.Patch;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
public class AsmConfigBaker {
private final Set<AsmConfig> configs = new LinkedHashSet<>();
public void add(AsmConfig config) {
configs.add(config);
}
public AsmConfig bake() {
Set<String> skipClasses = new HashSet<>();
Set<Patch> patches = new LinkedHashSet<>();
for (AsmConfig config : configs) {
Set<String> skipClassesC = config.skipClasses();
Set<Patch> patchesC = config.getPatches();
if (skipClassesC != null) skipClasses.addAll(skipClassesC);
if (patchesC != null) patches.addAll(patchesC);
}
LibJf.LOGGER.info("Applying " + patches.size() + " ASM patches");
return new AsmConfig() {
@Override
public Set<String> skipClasses() {
return skipClasses;
}
@Override
public Set<Patch> getPatches() {
return patches;
}
};
}
}

View File

@ -1,40 +1,76 @@
package io.gitlab.jfronny.libjf.unsafe.asm;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.Patch;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.MappingResolver;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.transformer.FabricMixinTransformerProxy;
import org.spongepowered.asm.transformers.MixinClassWriter;
import java.util.Set;
public class AsmTransformer extends FabricMixinTransformerProxy {
public static AsmTransformer INSTANCE;
public static final MappingResolver MAPPING_RESOLVER = FabricLoader.getInstance().getMappingResolver();
public static final String INTERMEDIARY = "intermediary";
public FabricMixinTransformerProxy delegate;
public AsmConfig asmConfig;
public Set<AsmConfig> asmConfigs;
private AsmConfig currentConfig = null;
@Override
public byte[] transformClassBytes(String name, String transformedName, byte[] classBytes) {
classBytes = delegate.transformClassBytes(name, transformedName, classBytes);
if (classBytes == null || name == null)
return classBytes;
if (name.startsWith("org.objectweb.asm")
|| name.startsWith("net.fabricmc.loader")
|| name.startsWith("net.minecraft")
|| name.startsWith("io.gitlab.jfronny.libjf.unsafe.asm")
|| name.startsWith("joptsimple")
)
if (isClassUnmoddable(name, null))
return classBytes;
ClassNode klass = new ClassNode();
ClassNode klazz = new ClassNode();
ClassReader reader = new ClassReader(classBytes);
reader.accept(klass, ClassReader.EXPAND_FRAMES);
reader.accept(klazz, ClassReader.EXPAND_FRAMES); //ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG
for (Patch patch : asmConfig.getPatches()) {
patch.apply(klass);
//if ((klazz.access & Opcodes.ACC_INTERFACE) != 0) {
// return classBytes;
//}
for (AsmConfig config : asmConfigs) {
currentConfig = config;
if (!isClassUnmoddable(name, config)) {
for (Patch patch : config.getPatches()) {
patch.apply(klazz);
}
}
}
currentConfig = null;
ClassWriter writer = new MixinClassWriter(reader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
klass.accept(writer);
try {
klazz.accept(writer);
}
catch (NullPointerException t) {
LibJf.LOGGER.error("Could not transform " + transformedName, t);
return null;
}
classBytes = writer.toByteArray();
return classBytes;
return classBytes;
}
public boolean isClassUnmoddable(String className, AsmConfig config) {
if (className.replace('/', '.').startsWith("org.objectweb.asm")
//|| className.startsWith("net.fabricmc.loader")
//|| className.startsWith("io.gitlab.jfronny.libjf.unsafe.asm")
)
return true;
if (config == null) return false;
Set<String> classes = config.skipClasses();
if (classes == null) return false;
return classes.contains(MAPPING_RESOLVER.unmapClassName(INTERMEDIARY, className));
}
public AsmConfig getCurrentConfig() {
return currentConfig;
}
}

View File

@ -0,0 +1,24 @@
package io.gitlab.jfronny.libjf.unsafe.asm;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.Patch;
import java.util.Set;
public class BakedAsmConfig implements AsmConfig {
private final Set<String> skipClasses;
private final Set<Patch> patches;
public BakedAsmConfig(AsmConfig config) {
skipClasses = config.skipClasses();
patches = config.getPatches();
}
@Override
public Set<String> skipClasses() {
return skipClasses;
}
@Override
public Set<Patch> getPatches() {
return patches;
}
}

View File

@ -1,34 +0,0 @@
package io.gitlab.jfronny.libjf.unsafe.asm.patch;
import io.gitlab.jfronny.libjf.LibJf;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodNode;
import java.util.Map;
import java.util.function.Consumer;
public abstract class InterfaceImplMethodReplacePatch implements Patch {
private final String targetInterface;
protected InterfaceImplMethodReplacePatch(String targetInterface) {
this.targetInterface = targetInterface;
}
public abstract Map<String, Consumer<InsnList>> getPatches();
@Override
public void apply(ClassNode klazz) {
if (klazz.interfaces.contains(targetInterface) || targetInterface.equals(klazz.superName)) {
LibJf.LOGGER.info("Found " + klazz.name + " implementing " + targetInterface);
Map<String, Consumer<InsnList>> patches = getPatches();
for (MethodNode method : klazz.methods) {
if (patches.containsKey(method.name)) {
LibJf.LOGGER.info("Patching " + method.name);
method.instructions.clear();
patches.get(method.name).accept(method.instructions);
}
}
}
}
}

View File

@ -0,0 +1,7 @@
package io.gitlab.jfronny.libjf.unsafe.asm.patch;
import org.objectweb.asm.tree.MethodNode;
public interface MethodPatch {
void apply(MethodNode method);
}

View File

@ -0,0 +1,23 @@
package io.gitlab.jfronny.libjf.unsafe.asm.patch.method;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.MethodPatch;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodNode;
public class MethodReplacementPatch implements MethodPatch {
private final InsnList instructions;
public MethodReplacementPatch(InsnList instructions) {
this.instructions = instructions;
}
@Override
public void apply(MethodNode method) {
method.instructions.clear();
InsnList clone = new InsnList();
for (AbstractInsnNode instruction : instructions) {
clone.add(instruction);
}
method.instructions.insert(clone);
}
}

View File

@ -0,0 +1,42 @@
package io.gitlab.jfronny.libjf.unsafe.asm.patch.modification;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.unsafe.asm.AsmTransformer;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.MethodPatch;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.Patch;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public class MethodModificationPatch implements Patch {
private final Map<String, MethodPatch> patches = new LinkedHashMap<>();
public MethodModificationPatch(String targetMethodOwner, Set<MethodDescriptorPatch> patches) {
for (MethodDescriptorPatch patch : patches) {
this.patches.put(
AsmTransformer.MAPPING_RESOLVER.mapMethodName(AsmTransformer.INTERMEDIARY, targetMethodOwner, patch.methodName, patch.methodDescriptor),
patch.patch
);
}
for (String s : this.patches.keySet()) {
if (LibJf.DEV) LibJf.LOGGER.info("Registered patch for " + s);
}
}
@Override
public void apply(ClassNode klazz) {
for (MethodNode method : klazz.methods) {
if (patches.containsKey(method.name)) {
if (LibJf.DEV) LibJf.LOGGER.info("Patching " + method.name);
patches.get(method.name).apply(method);
}
}
}
public static record MethodDescriptorPatch(String methodName, String methodDescriptor, MethodPatch patch) {
}
}

View File

@ -0,0 +1,107 @@
package io.gitlab.jfronny.libjf.unsafe.asm.patch.targeting;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.unsafe.asm.AsmTransformer;
import io.gitlab.jfronny.libjf.unsafe.asm.patch.Patch;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class InterfaceImplTargetPatch implements Patch {
public static final Map<String, Set<String>> INTERFACES = new HashMap<>();
private final String targetInterface;
private final Patch methodPatch;
public InterfaceImplTargetPatch(String targetInterfaceIntermediary, Patch methodPatch) {
this.targetInterface = AsmTransformer.MAPPING_RESOLVER.mapClassName(AsmTransformer.INTERMEDIARY, targetInterfaceIntermediary).replace('.', '/');
this.methodPatch = methodPatch;
}
@Override
public void apply(ClassNode klazz) {
scanInterfaces(klazz);
if (getUpper(klazz.name).contains(targetInterface)) {
if (LibJf.DEV) LibJf.LOGGER.info("Found " + klazz.name + " implementing " + targetInterface);
methodPatch.apply(klazz);
}
}
private static void scanInterfaces(ClassNode klazz) {
if (INTERFACES.containsKey(klazz.name)) return;
INTERFACES.put(klazz.name, new HashSet<>());
for (String anInterface : klazz.interfaces) {
INTERFACES.get(klazz.name).add(anInterface);
}
if (klazz.superName != null) {
INTERFACES.get(klazz.name).add(klazz.superName);
}
INTERFACES.put(klazz.name, Set.copyOf(INTERFACES.get(klazz.name)));
for (String s : INTERFACES.get(klazz.name)) {
String n = s.replace('/', '.');
if (AsmTransformer.INSTANCE.isClassUnmoddable(n, AsmTransformer.INSTANCE.getCurrentConfig()))
continue;
try {
InterfaceImplTargetPatch.class.getClassLoader().loadClass(n);
} catch (Throwable e) {
throw new RuntimeException("Could not load super class " + s + " of " + klazz.name, e);
}
}
}
private static void scanInterfaces(Class<?> klazz) {
String n = Type.getInternalName(klazz);
if (INTERFACES.containsKey(n)) return;
INTERFACES.put(n, new HashSet<>());
for (Class<?> anInterface : klazz.getInterfaces()) {
INTERFACES.get(n).add(Type.getInternalName(anInterface));
}
Class<?> superC = klazz.getSuperclass();
if (superC != null) {
INTERFACES.get(n).add(Type.getInternalName(superC));
}
INTERFACES.put(n, Set.copyOf(INTERFACES.get(n)));
for (String s : INTERFACES.get(n)) {
String nn = s.replace('/', '.');
if (AsmTransformer.INSTANCE.isClassUnmoddable(nn, AsmTransformer.INSTANCE.getCurrentConfig()))
continue;
try {
scanInterfaces(InterfaceImplTargetPatch.class.getClassLoader().loadClass(nn));
} catch (Throwable e) {
throw new RuntimeException("Could not load super class " + s + " of " + n, e);
}
}
}
public static Set<String> getUpper(String className) {
Set<String> s = INTERFACES.get(className);
if (s == null) {
if (!className.startsWith("java/")
&& !className.startsWith("com/mojang/")
&& !className.startsWith("net/minecraft/")
&& !className.startsWith("jdk/")
&& !className.startsWith("it/unimi/dsi/fastutil/")
&& !className.startsWith("com/google/")
) {
if (LibJf.DEV) LibJf.LOGGER.info("Non-default class not considered for interface scanning: " + className);
INTERFACES.put(className, Set.of());
return Set.of();
}
try {
scanInterfaces(Class.forName(className.replace('/', '.')));
s = INTERFACES.get(className);
} catch (ClassNotFoundException e) {
LibJf.LOGGER.error("Could not get base for " + className, e);
return Set.of();
}
}
s = new HashSet<>(s);
for (String s1 : s.toArray(new String[0])) {
s.addAll(getUpper(s1));
}
return s;
}
}