Use ASM for data manipulation
This commit is contained in:
parent
939e7d817e
commit
cddfd6c0fa
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package io.gitlab.jfronny.libjf.interfaces;
|
||||
|
||||
public interface ThrowingRunnable {
|
||||
void run() throws Throwable;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package io.gitlab.jfronny.libjf.interfaces;
|
||||
|
||||
public interface ThrowingSupplier<T> {
|
||||
T get() throws Throwable;
|
||||
}
|
|
@ -2,5 +2,5 @@ archivesBaseName = "libjf-data-manipulation-v0"
|
|||
version = "1.0"
|
||||
|
||||
dependencies {
|
||||
moduleDependencies(project, ["libjf-base"])
|
||||
moduleDependencies(project, ["libjf-base", "libjf-unsafe-v0"])
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
"package": "io.gitlab.jfronny.libjf.data.manipulation.mixin",
|
||||
"compatibilityLevel": "JAVA_16",
|
||||
"mixins": [
|
||||
"RecipeManagerMixin",
|
||||
"ReloadableResourceManagerImplMixin"
|
||||
"RecipeManagerMixin"
|
||||
],
|
||||
"client": [
|
||||
],
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue