All Downloads are FREE. Search and download functionalities are using the official Maven repository.

top.hendrixshen.magiclib.util.MixinUtil Maven / Gradle / Ivy

There is a newer version: 0.7.398+fe2125a-beta
Show newest version
package top.hendrixshen.magiclib.util;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import org.spongepowered.asm.mixin.MixinEnvironment;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import org.spongepowered.asm.mixin.transformer.ClassInfo;
import org.spongepowered.asm.mixin.transformer.ext.ITargetClassContext;
import org.spongepowered.asm.service.MixinService;
import org.spongepowered.asm.util.Annotations;
import top.hendrixshen.magiclib.compat.api.annotation.*;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

public class MixinUtil {
    private static final Field mixinInfoStateField;
    private static final Field mixinInfoInfoField;
    private static final Field mixinInfoStateClassNodeField;
    private static final Field mixinsField;
    private static final ConcurrentLinkedQueue magicClassesQueue = new ConcurrentLinkedQueue<>();
    public static Map classMap = new ConcurrentHashMap<>();

    static {
        try {
            mixinInfoStateField = Class.forName("org.spongepowered.asm.mixin.transformer.MixinInfo")
                    .getDeclaredField("state");
            mixinInfoStateField.setAccessible(true);

            mixinInfoInfoField = Class.forName("org.spongepowered.asm.mixin.transformer.MixinInfo")
                    .getDeclaredField("info");
            mixinInfoInfoField.setAccessible(true);

            mixinInfoStateClassNodeField = Class.forName("org.spongepowered.asm.mixin.transformer.MixinInfo$State")
                    .getDeclaredField("classNode");
            mixinInfoStateClassNodeField.setAccessible(true);

            mixinsField = Class.forName("org.spongepowered.asm.mixin.transformer.TargetClassContext")
                    .getDeclaredField("mixins");
            mixinsField.setAccessible(true);
        } catch (NoSuchFieldException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    @SuppressWarnings("unchecked")
    public static SortedSet getMixins(ITargetClassContext iTargetClassContext) {
        try {
            return (SortedSet) mixinsField.get(iTargetClassContext);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    public static ClassInfo getClassInfo(IMixinInfo iMixinInfo) {
        try {
            return (ClassInfo) mixinInfoInfoField.get(iMixinInfo);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    public static ClassNode getMixinClassNode(IMixinInfo iMixinInfo) {
        try {
            return (ClassNode) mixinInfoStateClassNodeField.get(mixinInfoStateField.get(iMixinInfo));
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    public static @Nullable String getMinecraftTypeStr(@NotNull String str, int startIdx) {
        int lIndex = str.indexOf("Lnet/minecraft/", startIdx);

        if (lIndex == -1) {
            lIndex = str.indexOf("Lcom/mojang/", startIdx);
            if (lIndex == -1) {
                return null;
            }
        }

        int rIndex = str.indexOf(";", lIndex);
        assert rIndex != -1;
        return str.substring(lIndex + 1, rIndex);
    }

    public static String remap(String str) {
        if (str == null) {
            return null;
        }

        int nextIdx = -1;

        for (String minecraftTypeStr = getMinecraftTypeStr(str, 0); minecraftTypeStr != null; minecraftTypeStr = getMinecraftTypeStr(str, nextIdx)) {
            nextIdx = str.indexOf(minecraftTypeStr, nextIdx + 1);
            str = str.replace(String.format("L%s;", minecraftTypeStr),
                    String.format("L%s;", classMap.getOrDefault(minecraftTypeStr, minecraftTypeStr)));
        }

        // Remap str without L%s;
        return classMap.getOrDefault(str, str);
    }

    private static boolean containsMethodNode(@NotNull Collection methodNodes, String name, String desc) {
        return methodNodes.stream().anyMatch(
                methodNode -> methodNode.name.equals(name) &&
                        methodNode.desc.equals(desc));
    }

    public static void applyInit(@NotNull ClassNode classNode) {
        Set thisInitMethodSet = new HashSet<>();
        Set superInitMethodSet = new HashSet<>();

        // Get thisInitMethodSet and superInitMethodSet.
        for (MethodNode methodNode : classNode.methods) {
            AnnotationNode thisInitMethodAnnotation = Annotations.getVisible(methodNode, ThisInitMethod.class);
            AnnotationNode superInitMethodAnnotation = Annotations.getVisible(methodNode, SuperInitMethod.class);

            if (thisInitMethodAnnotation != null) {
                thisInitMethodSet.add(methodNode);
            }

            if (superInitMethodAnnotation != null) {
                superInitMethodSet.add(methodNode);
            }
        }

        // The constructor cannot be called directly, it acts as a wrapper here, similar to the effect of @Shadow, and needs to be removed.
        classNode.methods.removeAll(thisInitMethodSet);
        classNode.methods.removeAll(superInitMethodSet);

        for (MethodNode methodNode : classNode.methods) {
            AnnotationNode initMethodAnnotation = Annotations.getVisible(methodNode, InitMethod.class);
            // Remap this and super
            if (initMethodAnnotation != null) {
                methodNode.name = "";
                boolean initInvoke = false;

                for (AbstractInsnNode abstractInsnNode : methodNode.instructions) {
                    if (abstractInsnNode instanceof MethodInsnNode) {
                        MethodInsnNode methodInsnNode = (MethodInsnNode) abstractInsnNode;

                        if (methodInsnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) {
                            if (containsMethodNode(thisInitMethodSet, methodInsnNode.name, methodInsnNode.desc)) {
                                methodInsnNode.setOpcode(Opcodes.INVOKESPECIAL);
                                methodInsnNode.name = "";
                                initInvoke = true;
                                break;
                            } else if (containsMethodNode(superInitMethodSet, methodInsnNode.name, methodInsnNode.desc)) {
                                methodInsnNode.setOpcode(Opcodes.INVOKESPECIAL);
                                methodInsnNode.name = "";
                                methodInsnNode.owner = classNode.superName;
                                initInvoke = true;
                                break;
                            }
                        }
                    }
                }

                // Add default constructor
                if (!initInvoke) {
                    methodNode.instructions.insert(new MethodInsnNode(Opcodes.INVOKESPECIAL, classNode.superName, "", "()V"));
                    methodNode.instructions.insert(new VarInsnNode(Opcodes.ALOAD, 0));
                }

                methodNode.visibleAnnotations.removeIf(annotationNode -> annotationNode.desc.equals(Type.getDescriptor(InitMethod.class)));
            }
        }
    }

//    public static void applyInnerClass(ClassNode classNode, ClassNode mixinClassNode) {
//        AnnotationNode innerClassAnnotation = Annotations.getVisible(mixinClassNode, InnerClass.class);
//        if (innerClassAnnotation == null) {
//            return;
//        }
//
//        ArrayList innerClassAnnotations = new ArrayList<>();
//        Object classInnerAnnotationValue = Annotations.getValue(innerClassAnnotation, "value");
//
//        if (classInnerAnnotationValue instanceof Type) {
//            innerClassAnnotations.add(((Type) classInnerAnnotationValue).getClassName());
//        } else {
//            for (Object t : (ArrayList) classInnerAnnotationValue) {
//                innerClassAnnotations.add(((Type) t).getClassName());
//            }
//        }
//
//        for (String innerClassName : innerClassAnnotations) {
//            ClassNode innerClassNode;
//            try {
//                innerClassNode = MixinService.getService().getBytecodeProvider().getClassNode(innerClassName);
//            } catch (ClassNotFoundException | IOException e) {
//                throw new RuntimeException(e);
//            }
//            innerClassNode.innerClasses.clear();
//
//            remapAndLoadClass(innerClassNode, true);
//            // TODO add inner class to classNode
//        }
//    }

    public static void applyPublic(@NotNull ClassNode classNode) {
        for (FieldNode fieldNode : classNode.fields) {
            AnnotationNode fieldPublicAnnotation = Annotations.getVisible(fieldNode, Public.class);

            if (fieldPublicAnnotation != null) {
                fieldNode.access = (fieldNode.access & ~(Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) | Opcodes.ACC_PUBLIC;
            }
        }

        for (MethodNode methodNode : classNode.methods) {
            AnnotationNode publicAnnotation = Annotations.getVisible(methodNode, Public.class);

            if (publicAnnotation != null) {
                methodNode.access = (methodNode.access & ~(Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) | Opcodes.ACC_PUBLIC;
            }
        }
    }

    public static void addMagicClass(String className) throws IOException, ClassNotFoundException {
        ClassNode classNode = MixinService.getService().getBytecodeProvider().getClassNode(className);
        magicClassesQueue.add(classNode);
    }

    public static void commitMagicClass() {
        for (ClassNode classNode : magicClassesQueue) {
            if (classNode.innerClasses == null) {
                continue;
            }

            for (InnerClassNode innerClassNode : classNode.innerClasses) {
                try {
                    ClassNode i = (MixinService.getService().getBytecodeProvider()
                            .getClassNode(innerClassNode.name));
                    remapAndLoadClass(i);
                } catch (ClassNotFoundException | IOException e) {
                    throw new RuntimeException(e);
                }
            }

            remapAndLoadClass(classNode);
        }

        magicClassesQueue.clear();
    }

    public static void remapAndLoadClass(ClassNode classNode) {
        // TODO: add innerclass to target class
        AnnotationNode classRemapAnnotation = Annotations.getVisible(classNode, Remap.class);

        if (classRemapAnnotation == null) {
            return;
        }

        String oldName = classNode.name;

        // remap first time
        if (!classMap.containsKey(oldName)) {
            classMap.put(oldName, Annotations.getValue(classRemapAnnotation, "value"));
            classNode.name = remap(classNode.name);
            classNode.visibleAnnotations.removeIf(annotationNode -> annotationNode.desc.equals(Type.getDescriptor(Remap.class)));
            applyRemap(classNode);
            MagicStreamHandler.addClass(classNode);
        }

        if (classNode.innerClasses != null) {
            for (InnerClassNode innerClassNode : classNode.innerClasses) {
                innerClassNode.name = remap(innerClassNode.name);
                innerClassNode.outerName = remap(innerClassNode.outerName);
            }
        }
    }

    private static @NotNull MethodNode copyMethodNode(@NotNull MethodNode methodNode) {
        MethodNode ret = new MethodNode(methodNode.access, methodNode.name, methodNode.desc,
                methodNode.signature, methodNode.exceptions.toArray(new String[]{}));
        methodNode.accept(ret);
        return ret;
    }

    public static void applyRemap(@NotNull ClassNode classNode) {
        // Remap interfaces name
        for (int i = 0; i < classNode.interfaces.size(); ++i) {
            String classIntermediaryName = classMap.getOrDefault(classNode.interfaces.get(i), null);

            if (classIntermediaryName != null) {
                classNode.interfaces.set(i, classIntermediaryName);
            }
        }

        classNode.signature = remap(classNode.signature);
        // Remap field
        Map remappedFieldsMap = new HashMap<>();

        for (FieldNode fieldNode : classNode.fields) {
            AnnotationNode fieldRemapAnnotation = Annotations.getVisible(fieldNode, Remap.class);
            if (fieldRemapAnnotation != null) {
                String intermediaryName = Annotations.getValue(fieldRemapAnnotation, "value");
                remappedFieldsMap.put(fieldNode.name, fieldNode);
                fieldNode.name = intermediaryName;
                fieldNode.visibleAnnotations.removeIf(annotationNode -> annotationNode.desc.equals(Type.getDescriptor(Remap.class)));
            }
            fieldNode.desc = remap(fieldNode.desc);
            fieldNode.signature = remap(fieldNode.signature);
        }

        // Remove existing field of the same name.
        classNode.fields.removeIf(fieldNode ->
                remappedFieldsMap.values().stream().anyMatch(remappedFieldNode ->
                        remappedFieldNode.name.equals(fieldNode.name) && remappedFieldNode != fieldNode));

        Map remappedMethodsMap = new HashMap<>();
        Set dupMethodNodes = new HashSet<>();

        // Remap method name
        for (MethodNode methodNode : classNode.methods) {
            AnnotationNode remapAnnotation = Annotations.getVisible(methodNode, Remap.class);

            if (remapAnnotation != null) {
                boolean dup = Annotations.getValue(remapAnnotation, "dup", Remap.class);
                String newName = Annotations.getValue(remapAnnotation, "value");

                if (dup) {
                    MethodNode dupMethodNode = copyMethodNode(methodNode);
                    dupMethodNode.visibleAnnotations.removeIf(annotationNode -> annotationNode.desc.equals(Type.getDescriptor(Remap.class)));
                    dupMethodNodes.add(dupMethodNode);
                }

                remappedMethodsMap.put(methodNode.name + methodNode.desc, methodNode);
                methodNode.name = newName;
                methodNode.visibleAnnotations.removeIf(annotationNode -> annotationNode.desc.equals(Type.getDescriptor(Remap.class)));
            }
        }
        classNode.methods.addAll(dupMethodNodes);

        // Remap method desc and instructions
        for (MethodNode methodNode : classNode.methods) {
            methodNode.desc = remap(methodNode.desc);

            for (AbstractInsnNode abstractInsnNode : methodNode.instructions) {
                if (abstractInsnNode instanceof FieldInsnNode) {
                    FieldInsnNode fieldInsnNode = (FieldInsnNode) abstractInsnNode;
                    FieldNode remappedFieldNode = remappedFieldsMap.getOrDefault(fieldInsnNode.name, null);

                    if (remappedFieldNode != null) {
                        fieldInsnNode.name = remappedFieldNode.name;
                    }

                    fieldInsnNode.desc = remap(fieldInsnNode.desc);
                    fieldInsnNode.owner = remap(fieldInsnNode.owner);
                }

                if (abstractInsnNode instanceof MethodInsnNode) {
                    MethodInsnNode methodInsnNode = (MethodInsnNode) abstractInsnNode;
                    methodInsnNode.owner = remap(methodInsnNode.owner);

                    if (Objects.equals(methodInsnNode.owner, classNode.name)) {
                        MethodNode remappedMethodNode = remappedMethodsMap.getOrDefault(methodInsnNode.name + methodInsnNode.desc, null);

                        if (remappedMethodNode != null) {
                            methodInsnNode.name = remappedMethodNode.name;
                        }
                    }

                    methodInsnNode.desc = remap(methodInsnNode.desc);
                }

                if (abstractInsnNode instanceof TypeInsnNode) {
                    TypeInsnNode typeInsnNode = (TypeInsnNode) abstractInsnNode;
                    typeInsnNode.desc = remap(typeInsnNode.desc);
                }

                if (abstractInsnNode instanceof FrameNode) {
                    FrameNode frameNode = (FrameNode) abstractInsnNode;

                    if (frameNode.local != null) {
                        for (int i = 0; i < frameNode.local.size(); ++i) {
                            Object obj = frameNode.local.get(i);

                            if (obj instanceof String) {
                                frameNode.local.set(i, remap((String) obj));
                            }
                        }
                    }
                }
            }
        }

        // Remap local var
        for (MethodNode methodNode : classNode.methods) {
            if (methodNode.localVariables != null) {
                for (LocalVariableNode localVariableNode : methodNode.localVariables) {
                    localVariableNode.desc = remap(localVariableNode.desc);
                    localVariableNode.signature = remap(localVariableNode.signature);
                }
            }
        }

        // Remove existing method with the same name.
        classNode.methods.removeIf(methodNode -> remappedMethodsMap.values().stream().anyMatch(
                remappedMethodNode -> remappedMethodNode.name.equals(methodNode.name) &&
                        remappedMethodNode.desc.equals(methodNode.desc) &&
                        remappedMethodNode != methodNode));
    }

    public static void audit() {
        MixinEnvironment.getCurrentEnvironment().audit();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy