Search in sources :

Example 41 with ClassFile

use of net.runelite.asm.ClassFile in project runelite by runelite.

the class MixinInjector method inject.

public void inject() throws InjectionException {
    ClassPath classPath;
    try {
        classPath = ClassPath.from(this.getClass().getClassLoader());
    } catch (IOException ex) {
        throw new InjectionException(ex);
    }
    // key: mixin class
    // value: mixin targets
    Map<Class<?>, List<ClassFile>> mixinClasses = new HashMap<>();
    // Find mixins and populate mixinClasses
    for (ClassInfo classInfo : classPath.getTopLevelClasses(MIXIN_BASE)) {
        Class<?> mixinClass = classInfo.load();
        List<ClassFile> mixinTargets = new ArrayList<>();
        for (Mixin mixin : mixinClass.getAnnotationsByType(Mixin.class)) {
            Class<?> implementInto = mixin.value();
            ClassFile targetCf = inject.findVanillaForInterface(implementInto);
            if (targetCf == null) {
                throw new InjectionException("No class implements " + implementInto + " for mixin " + mixinClass);
            }
            mixinTargets.add(targetCf);
        }
        mixinClasses.put(mixinClass, mixinTargets);
    }
    inject(mixinClasses);
}
Also used : ClassPath(com.google.common.reflect.ClassPath) ClassFile(net.runelite.asm.ClassFile) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) IOException(java.io.IOException) Mixin(net.runelite.api.mixins.Mixin) ArrayList(java.util.ArrayList) List(java.util.List) ClassInfo(com.google.common.reflect.ClassPath.ClassInfo)

Example 42 with ClassFile

use of net.runelite.asm.ClassFile in project runelite by runelite.

the class MixinInjector method injectMethods.

private void injectMethods(ClassFile mixinCf, ClassFile cf, Map<net.runelite.asm.pool.Field, Field> shadowFields) throws InjectionException {
    // Keeps mappings between methods annotated with @Copy -> the copied method within the vanilla pack
    Map<net.runelite.asm.pool.Method, CopiedMethod> copiedMethods = new HashMap<>();
    // Handle the copy mixins first, so all other mixins know of the copies
    for (Method method : mixinCf.getMethods()) {
        Annotation copyAnnotation = method.getAnnotations().find(COPY);
        if (copyAnnotation == null) {
            continue;
        }
        String deobMethodName = (String) copyAnnotation.getElement().getValue();
        ClassFile deobCf = inject.toDeobClass(cf);
        Method deobMethod = findDeobMethod(deobCf, deobMethodName, method.getDescriptor());
        if (deobMethod == null) {
            throw new InjectionException("Failed to find the deob method " + deobMethodName + " for mixin " + mixinCf);
        }
        if (method.isStatic() != deobMethod.isStatic()) {
            throw new InjectionException("Mixin method " + method + " should be " + (deobMethod.isStatic() ? "static" : "non-static"));
        }
        // Find the vanilla class where the method to copy is in
        String obClassName = DeobAnnotations.getObfuscatedName(deobMethod.getClassFile().getAnnotations());
        ClassFile obCf = inject.getVanilla().findClass(obClassName);
        assert obCf != null : "unable to find vanilla class from obfuscated name " + obClassName;
        String obMethodName = DeobAnnotations.getObfuscatedName(deobMethod.getAnnotations());
        Signature obMethodSignature = DeobAnnotations.getObfuscatedSignature(deobMethod);
        if (obMethodName == null) {
            obMethodName = deobMethod.getName();
        }
        if (obMethodSignature == null) {
            obMethodSignature = deobMethod.getDescriptor();
        }
        Method obMethod = obCf.findMethod(obMethodName, obMethodSignature);
        if (obMethod == null) {
            throw new InjectionException("Failed to find the ob method " + obMethodName + " for mixin " + mixinCf);
        }
        if (method.getDescriptor().size() > obMethod.getDescriptor().size()) {
            throw new InjectionException("Mixin methods cannot have more parameters than their corresponding ob method");
        }
        Method copy = new Method(cf, "copy$" + deobMethodName, obMethodSignature);
        moveCode(copy, obMethod.getCode());
        copy.setAccessFlags(obMethod.getAccessFlags());
        copy.setPublic();
        copy.getExceptions().getExceptions().addAll(obMethod.getExceptions().getExceptions());
        copy.getAnnotations().getAnnotations().addAll(obMethod.getAnnotations().getAnnotations());
        cf.addMethod(copy);
        /*
				If the desc for the mixin method and the desc for the ob method
				are the same in length, assume that the mixin method is taking
				care of the garbage parameter itself.
			 */
        boolean hasGarbageValue = method.getDescriptor().size() != obMethod.getDescriptor().size() && deobMethod.getDescriptor().size() < obMethodSignature.size();
        copiedMethods.put(method.getPoolMethod(), new CopiedMethod(copy, hasGarbageValue));
        logger.debug("Injected copy of {} to {}", obMethod, copy);
    }
    // Handle the rest of the mixin types
    for (Method method : mixinCf.getMethods()) {
        boolean isClinit = "<clinit>".equals(method.getName());
        boolean isInit = "<init>".equals(method.getName());
        boolean hasInject = method.getAnnotations().find(INJECT) != null;
        // You can't annotate clinit, so its always injected
        if ((hasInject && isInit) || isClinit) {
            if (!"()V".equals(method.getDescriptor().toString())) {
                throw new InjectionException("Injected constructors cannot have arguments");
            }
            Method[] originalMethods = cf.getMethods().stream().filter(n -> n.getName().equals(method.getName())).toArray(Method[]::new);
            // If there isn't a <clinit> already just inject ours, otherwise rename it
            // This is always true for <init>
            String name = method.getName();
            if (originalMethods.length > 0) {
                name = "rl$$" + (isInit ? "init" : "clinit");
            }
            String numberlessName = name;
            for (int i = 1; cf.findMethod(name, method.getDescriptor()) != null; i++) {
                name = numberlessName + i;
            }
            Method copy = new Method(cf, name, method.getDescriptor());
            moveCode(copy, method.getCode());
            copy.setAccessFlags(method.getAccessFlags());
            copy.setPrivate();
            assert method.getExceptions().getExceptions().isEmpty();
            // Remove the call to the superclass's ctor
            if (isInit) {
                Instructions instructions = copy.getCode().getInstructions();
                ListIterator<Instruction> listIter = instructions.getInstructions().listIterator();
                for (; listIter.hasNext(); ) {
                    Instruction instr = listIter.next();
                    if (instr instanceof InvokeSpecial) {
                        InvokeSpecial invoke = (InvokeSpecial) instr;
                        assert invoke.getMethod().getName().equals("<init>");
                        listIter.remove();
                        int pops = invoke.getMethod().getType().getArguments().size() + 1;
                        for (int i = 0; i < pops; i++) {
                            listIter.add(new Pop(instructions));
                        }
                        break;
                    }
                }
            }
            setOwnersToTargetClass(mixinCf, cf, copy, shadowFields, copiedMethods);
            cf.addMethod(copy);
            // Call our method at the return point of the matching method(s)
            for (Method om : originalMethods) {
                Instructions instructions = om.getCode().getInstructions();
                ListIterator<Instruction> listIter = instructions.getInstructions().listIterator();
                for (; listIter.hasNext(); ) {
                    Instruction instr = listIter.next();
                    if (instr instanceof ReturnInstruction) {
                        listIter.previous();
                        if (isInit) {
                            listIter.add(new ALoad(instructions, 0));
                            listIter.add(new InvokeSpecial(instructions, copy.getPoolMethod()));
                        } else if (isClinit) {
                            listIter.add(new InvokeStatic(instructions, copy.getPoolMethod()));
                        }
                        listIter.next();
                    }
                }
            }
            logger.debug("Injected mixin method {} to {}", copy, cf);
        } else if (hasInject) {
            // Make sure the method doesn't invoke copied methods
            for (Instruction i : method.getCode().getInstructions().getInstructions()) {
                if (i instanceof InvokeInstruction) {
                    InvokeInstruction ii = (InvokeInstruction) i;
                    if (copiedMethods.containsKey(ii.getMethod())) {
                        throw new InjectionException("Injected methods cannot invoke copied methods");
                    }
                }
            }
            Method copy = new Method(cf, method.getName(), method.getDescriptor());
            moveCode(copy, method.getCode());
            copy.setAccessFlags(method.getAccessFlags());
            copy.setPublic();
            assert method.getExceptions().getExceptions().isEmpty();
            setOwnersToTargetClass(mixinCf, cf, copy, shadowFields, copiedMethods);
            cf.addMethod(copy);
            logger.debug("Injected mixin method {} to {}", copy, cf);
        } else if (method.getAnnotations().find(REPLACE) != null) {
            Annotation replaceAnnotation = method.getAnnotations().find(REPLACE);
            String deobMethodName = (String) replaceAnnotation.getElement().getValue();
            ClassFile deobCf = inject.toDeobClass(cf);
            Method deobMethod = findDeobMethod(deobCf, deobMethodName, method.getDescriptor());
            if (deobMethod == null) {
                throw new InjectionException("Failed to find the deob method " + deobMethodName + " for mixin " + mixinCf);
            }
            if (method.isStatic() != deobMethod.isStatic()) {
                throw new InjectionException("Mixin method " + method + " should be " + (deobMethod.isStatic() ? "static" : "non-static"));
            }
            String obMethodName = DeobAnnotations.getObfuscatedName(deobMethod.getAnnotations());
            Signature obMethodSignature = DeobAnnotations.getObfuscatedSignature(deobMethod);
            // Deob signature is the same as ob signature
            if (obMethodName == null) {
                obMethodName = deobMethod.getName();
            }
            if (obMethodSignature == null) {
                obMethodSignature = deobMethod.getDescriptor();
            }
            // Find the vanilla class where the method to copy is in
            String obClassName = DeobAnnotations.getObfuscatedName(deobMethod.getClassFile().getAnnotations());
            ClassFile obCf = inject.getVanilla().findClass(obClassName);
            Method obMethod = obCf.findMethod(obMethodName, obMethodSignature);
            assert obMethod != null : "obfuscated method " + obMethodName + obMethodSignature + " does not exist";
            if (method.getDescriptor().size() > obMethod.getDescriptor().size()) {
                throw new InjectionException("Mixin methods cannot have more parameters than their corresponding ob method");
            }
            Type returnType = method.getDescriptor().getReturnValue();
            Type deobReturnType = inject.apiTypeToDeobfuscatedType(returnType);
            if (!returnType.equals(deobReturnType)) {
                ClassFile deobReturnTypeClassFile = inject.getDeobfuscated().findClass(deobReturnType.getInternalName());
                if (deobReturnTypeClassFile != null) {
                    ClassFile obReturnTypeClass = inject.toObClass(deobReturnTypeClassFile);
                    Instructions instructions = method.getCode().getInstructions();
                    ListIterator<Instruction> listIter = instructions.getInstructions().listIterator();
                    for (; listIter.hasNext(); ) {
                        Instruction instr = listIter.next();
                        if (instr instanceof ReturnInstruction) {
                            listIter.previous();
                            CheckCast checkCast = new CheckCast(instructions);
                            checkCast.setType(new Type(obReturnTypeClass.getName()));
                            listIter.add(checkCast);
                            listIter.next();
                        }
                    }
                }
            }
            moveCode(obMethod, method.getCode());
            boolean hasGarbageValue = method.getDescriptor().size() != obMethod.getDescriptor().size() && deobMethod.getDescriptor().size() < obMethodSignature.size();
            if (hasGarbageValue) {
                int garbageIndex = obMethod.isStatic() ? obMethod.getDescriptor().size() - 1 : obMethod.getDescriptor().size();
                /*
						If the mixin method doesn't have the garbage parameter,
						the compiler will have produced code that uses the garbage
						parameter's local variable index for other things,
						so we'll have to add 1 to all loads/stores to indices
						that are >= garbageIndex.
					 */
                shiftLocalIndices(obMethod.getCode().getInstructions(), garbageIndex);
            }
            setOwnersToTargetClass(mixinCf, cf, obMethod, shadowFields, copiedMethods);
            logger.debug("Replaced method {} with mixin method {}", obMethod, method);
        }
    }
}
Also used : FieldInstruction(net.runelite.asm.attributes.code.instruction.types.FieldInstruction) ListIterator(java.util.ListIterator) PushConstantInstruction(net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction) GetField(net.runelite.asm.attributes.code.instructions.GetField) LoggerFactory(org.slf4j.LoggerFactory) HashMap(java.util.HashMap) Code(net.runelite.asm.attributes.Code) ALoad(net.runelite.asm.attributes.code.instructions.ALoad) ArrayList(java.util.ArrayList) Method(net.runelite.asm.Method) ILoad(net.runelite.asm.attributes.code.instructions.ILoad) Map(java.util.Map) InvokeInstruction(net.runelite.asm.attributes.code.instruction.types.InvokeInstruction) ClassPath(com.google.common.reflect.ClassPath) Pop(net.runelite.asm.attributes.code.instructions.Pop) LVTInstruction(net.runelite.asm.attributes.code.instruction.types.LVTInstruction) InvokeDynamic(net.runelite.asm.attributes.code.instructions.InvokeDynamic) DeobAnnotations(net.runelite.deob.DeobAnnotations) Logger(org.slf4j.Logger) ClassFileVisitor(net.runelite.asm.visitors.ClassFileVisitor) Field(net.runelite.asm.Field) ReturnInstruction(net.runelite.asm.attributes.code.instruction.types.ReturnInstruction) IOException(java.io.IOException) ClassInfo(com.google.common.reflect.ClassPath.ClassInfo) Type(net.runelite.asm.Type) InvokeStatic(net.runelite.asm.attributes.code.instructions.InvokeStatic) PutField(net.runelite.asm.attributes.code.instructions.PutField) Mixin(net.runelite.api.mixins.Mixin) List(java.util.List) ClassFile(net.runelite.asm.ClassFile) Annotation(net.runelite.asm.attributes.annotation.Annotation) ClassReader(org.objectweb.asm.ClassReader) Instructions(net.runelite.asm.attributes.code.Instructions) CheckCast(net.runelite.asm.attributes.code.instructions.CheckCast) InvokeSpecial(net.runelite.asm.attributes.code.instructions.InvokeSpecial) Signature(net.runelite.asm.signature.Signature) Instruction(net.runelite.asm.attributes.code.Instruction) InputStream(java.io.InputStream) HashMap(java.util.HashMap) FieldInstruction(net.runelite.asm.attributes.code.instruction.types.FieldInstruction) PushConstantInstruction(net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction) InvokeInstruction(net.runelite.asm.attributes.code.instruction.types.InvokeInstruction) LVTInstruction(net.runelite.asm.attributes.code.instruction.types.LVTInstruction) ReturnInstruction(net.runelite.asm.attributes.code.instruction.types.ReturnInstruction) Instruction(net.runelite.asm.attributes.code.Instruction) ReturnInstruction(net.runelite.asm.attributes.code.instruction.types.ReturnInstruction) ClassFile(net.runelite.asm.ClassFile) InvokeSpecial(net.runelite.asm.attributes.code.instructions.InvokeSpecial) Instructions(net.runelite.asm.attributes.code.Instructions) Method(net.runelite.asm.Method) CheckCast(net.runelite.asm.attributes.code.instructions.CheckCast) Annotation(net.runelite.asm.attributes.annotation.Annotation) Pop(net.runelite.asm.attributes.code.instructions.Pop) InvokeInstruction(net.runelite.asm.attributes.code.instruction.types.InvokeInstruction) Type(net.runelite.asm.Type) Signature(net.runelite.asm.signature.Signature) ALoad(net.runelite.asm.attributes.code.instructions.ALoad) InvokeStatic(net.runelite.asm.attributes.code.instructions.InvokeStatic)

Example 43 with ClassFile

use of net.runelite.asm.ClassFile in project runelite by runelite.

the class MixinInjector method findDeobMethod.

private Method findDeobMethod(ClassFile deobCf, String deobMethodName, Signature descriptor) throws InjectionException {
    List<Method> matchingMethods = new ArrayList<>();
    for (Method m : deobCf.getMethods()) {
        if (!deobMethodName.equals(m.getName())) {
            continue;
        }
        Type returnType = inject.apiTypeToDeobfuscatedType(descriptor.getReturnValue());
        Type returnType2 = m.getDescriptor().getReturnValue();
        if (!returnType.equals(returnType2)) {
            continue;
        }
        List<Type> args = descriptor.getArguments();
        List<Type> args2 = m.getDescriptor().getArguments();
        if (args.size() > args2.size()) {
            continue;
        }
        boolean matchingArgs = true;
        for (int i = 0; i < args.size(); i++) {
            Type type = inject.apiTypeToDeobfuscatedType(args.get(i));
            Type type2 = args2.get(i);
            if (!type.equals(type2)) {
                matchingArgs = false;
                break;
            }
        }
        if (!matchingArgs) {
            continue;
        }
        matchingMethods.add(m);
    }
    if (matchingMethods.size() > 1) {
        // to get rid of the error, refine your search by making your mixin method have more parameters
        throw new InjectionException("There are several matching methods when there should only be one");
    } else if (matchingMethods.size() == 1) {
        return matchingMethods.get(0);
    }
    Method method = deobCf.findMethod(deobMethodName);
    if (method == null) {
        // Look for static methods if an instance method couldn't be found
        for (ClassFile deobCf2 : inject.getDeobfuscated().getClasses()) {
            if (deobCf2 != deobCf) {
                method = deobCf2.findMethod(deobMethodName);
                if (method != null) {
                    break;
                }
            }
        }
    }
    return method;
}
Also used : Type(net.runelite.asm.Type) ClassFile(net.runelite.asm.ClassFile) ArrayList(java.util.ArrayList) Method(net.runelite.asm.Method)

Example 44 with ClassFile

use of net.runelite.asm.ClassFile in project runelite by runelite.

the class MixinInjector method injectFieldHooks.

private void injectFieldHooks(Map<Class<?>, List<ClassFile>> mixinClasses) throws InjectionException {
    InjectHook injectHook = new InjectHook(inject);
    for (Class<?> mixinClass : mixinClasses.keySet()) {
        ClassFile mixinCf;
        try {
            mixinCf = loadClass(mixinClass);
        } catch (IOException ex) {
            throw new InjectionException(ex);
        }
        List<ClassFile> targetCfs = mixinClasses.get(mixinClass);
        for (ClassFile cf : targetCfs) {
            for (Method method : mixinCf.getMethods()) {
                Annotation fieldHook = method.getAnnotations().find(FIELDHOOK);
                if (fieldHook != null) {
                    String hookName = fieldHook.getElement().getString();
                    ClassFile deobCf = inject.toDeobClass(cf);
                    Field targetField = deobCf.findField(hookName);
                    if (targetField == null) {
                        // first try non static fields, then static
                        targetField = findDeobField(hookName);
                    }
                    if (targetField == null) {
                        throw new InjectionException("Field hook for nonexistent field " + hookName + " on " + method);
                    }
                    Field obField = inject.toObField(targetField);
                    if (method.isStatic() != targetField.isStatic()) {
                        throw new InjectionException("Field hook method static flag must match target field");
                    }
                    // cf is the target class to invoke
                    InjectHook.HookInfo hookInfo = new InjectHook.HookInfo();
                    hookInfo.clazz = cf.getName();
                    hookInfo.fieldName = hookName;
                    hookInfo.method = method.getName();
                    hookInfo.staticMethod = method.isStatic();
                    injectHook.hook(obField, hookInfo);
                }
            }
        }
    }
    injectHook.run();
    logger.info("Injected {} field hooks", injectHook.getInjectedHooks());
}
Also used : GetField(net.runelite.asm.attributes.code.instructions.GetField) Field(net.runelite.asm.Field) PutField(net.runelite.asm.attributes.code.instructions.PutField) ClassFile(net.runelite.asm.ClassFile) IOException(java.io.IOException) Method(net.runelite.asm.Method) Annotation(net.runelite.asm.attributes.annotation.Annotation)

Example 45 with ClassFile

use of net.runelite.asm.ClassFile in project runelite by runelite.

the class InjectHookMethodTest method testProcess.

@Test
public void testProcess() throws IOException, InjectionException {
    InputStream in = getClass().getResourceAsStream("Actor.class");
    ClassFile cf = ClassUtil.loadClass(in);
    cf.setName("Actor");
    cf.findMethod("bar").setDescriptor(new Signature("(LActor;I)I"));
    ClassGroup deobfuscated = new ClassGroup();
    deobfuscated.addClass(cf);
    in = getClass().getResourceAsStream("Obfuscated.class");
    ClassFile obcf = ClassUtil.loadClass(in);
    obcf.setName("Obfuscated");
    obcf.findMethod("foo").setDescriptor(new Signature("(LObfuscated;I)I"));
    ClassGroup obfuscated = new ClassGroup();
    obfuscated.addClass(obcf);
    Method method = cf.findMethod("bar");
    assert method != null;
    Inject inject = new Inject(deobfuscated, obfuscated);
    InjectHookMethod injectHookMethod = new InjectHookMethod(inject);
    injectHookMethod.process(method);
    method = obcf.findMethod("foo");
    assert method != null;
    Code code = method.getCode();
    List<InvokeStatic> invokeIns = code.getInstructions().getInstructions().stream().filter(i -> i instanceof InvokeStatic).map(i -> (InvokeStatic) i).filter(i -> i.getMethod().getClazz().getName().equals(HOOKS)).collect(Collectors.toList());
    assertTrue(!invokeIns.isEmpty());
    assertEquals(2, invokeIns.size());
    InvokeStatic invokeStatic = invokeIns.get(0);
    Signature signature = invokeStatic.getMethod().getType();
    // this + patamers
    assertEquals(3, signature.size());
    Type arg = signature.getTypeOfArg(1);
    assertEquals(RL_API_PACKAGE_BASE.replace('.', '/') + "Actor", arg.getInternalName());
}
Also used : ObfuscatedName(net.runelite.mapping.ObfuscatedName) HOOKS(net.runelite.injector.InjectHookMethod.HOOKS) Assert.assertTrue(org.junit.Assert.assertTrue) IOException(java.io.IOException) Code(net.runelite.asm.attributes.Code) RL_API_PACKAGE_BASE(net.runelite.injector.Inject.RL_API_PACKAGE_BASE) Test(org.junit.Test) Type(net.runelite.asm.Type) Collectors(java.util.stream.Collectors) InvokeStatic(net.runelite.asm.attributes.code.instructions.InvokeStatic) ClassGroup(net.runelite.asm.ClassGroup) List(java.util.List) ClassFile(net.runelite.asm.ClassFile) Method(net.runelite.asm.Method) ObfuscatedSignature(net.runelite.mapping.ObfuscatedSignature) ClassUtil(net.runelite.asm.ClassUtil) Hook(net.runelite.mapping.Hook) Signature(net.runelite.asm.signature.Signature) Assert.assertEquals(org.junit.Assert.assertEquals) InputStream(java.io.InputStream) Type(net.runelite.asm.Type) ClassFile(net.runelite.asm.ClassFile) InputStream(java.io.InputStream) ClassGroup(net.runelite.asm.ClassGroup) ObfuscatedSignature(net.runelite.mapping.ObfuscatedSignature) Signature(net.runelite.asm.signature.Signature) Method(net.runelite.asm.Method) Code(net.runelite.asm.attributes.Code) InvokeStatic(net.runelite.asm.attributes.code.instructions.InvokeStatic) Test(org.junit.Test)

Aggregations

ClassFile (net.runelite.asm.ClassFile)103 Method (net.runelite.asm.Method)62 Field (net.runelite.asm.Field)39 ClassGroup (net.runelite.asm.ClassGroup)32 Code (net.runelite.asm.attributes.Code)21 Instruction (net.runelite.asm.attributes.code.Instruction)18 Test (org.junit.Test)18 Signature (net.runelite.asm.signature.Signature)17 Type (net.runelite.asm.Type)16 Instructions (net.runelite.asm.attributes.code.Instructions)16 ArrayList (java.util.ArrayList)14 List (java.util.List)13 Logger (org.slf4j.Logger)10 LoggerFactory (org.slf4j.LoggerFactory)10 IOException (java.io.IOException)9 InputStream (java.io.InputStream)9 Annotation (net.runelite.asm.attributes.annotation.Annotation)9 PushConstantInstruction (net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction)9 HashMap (java.util.HashMap)8 GetFieldInstruction (net.runelite.asm.attributes.code.instruction.types.GetFieldInstruction)7