From 8fabcfb0d53b84d43e50e8919e4410239530781e Mon Sep 17 00:00:00 2001 From: Lucwousin Date: Tue, 29 Oct 2019 03:11:15 +0100 Subject: [PATCH] MixinInjectorTest --- build.gradle.kts | 2 +- .../injector/injectors/MixinInjector.java | 8 +- .../injector/injectors/MixinInjectorTest.java | 291 ++++++++++++++++++ 3 files changed, 299 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/openosrs/injector/injectors/MixinInjectorTest.java diff --git a/build.gradle.kts b/build.gradle.kts index 20a2041..533b983 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -29,9 +29,9 @@ dependencies { implementation("org.projectlombok:lombok:1.18.10") testImplementation("junit:junit:4.12") + testImplementation("com.openosrs:mixins:${project.version}") // testRuntimeOnly("com.openosrs.rs:rs-client:${project.version}") testCompileOnly("com.openosrs.rs:runescape-api:${project.version}") - // testRuntimeOnly("com.openosrs:mixins:${project.version}") // testRuntimeOnly("net.runelite.rs:vanilla") } diff --git a/src/main/java/com/openosrs/injector/injectors/MixinInjector.java b/src/main/java/com/openosrs/injector/injectors/MixinInjector.java index 49f71ab..3ae4457 100644 --- a/src/main/java/com/openosrs/injector/injectors/MixinInjector.java +++ b/src/main/java/com/openosrs/injector/injectors/MixinInjector.java @@ -1,5 +1,6 @@ package com.openosrs.injector.injectors; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.openosrs.injector.InjectUtil; @@ -62,7 +63,12 @@ public class MixinInjector extends AbstractInjector public void inject() throws Injexception { final Map, List> mixinTargets = initTargets(); + inject(mixinTargets); + } + @VisibleForTesting + void inject(Map, List> mixinTargets) throws Injexception + { for (Map.Entry, List> entry : mixinTargets.entrySet()) { injectFields(entry.getKey(), entry.getValue()); @@ -245,7 +251,7 @@ public class MixinInjector extends AbstractInjector } else { - deobSourceMethod = InjectUtil.findMethodDeep(inject.toDeob(targetClass.getClassName()), copiedName, mixinMethod.getDescriptor().rsApiToRsClient()); + deobSourceMethod = InjectUtil.findMethodDeep(inject.toDeob(targetClass.getName()), copiedName, mixinMethod.getDescriptor().rsApiToRsClient()); } if (mixinMethod.isStatic() != deobSourceMethod.isStatic()) diff --git a/src/test/java/com/openosrs/injector/injectors/MixinInjectorTest.java b/src/test/java/com/openosrs/injector/injectors/MixinInjectorTest.java new file mode 100644 index 0000000..cdb794a --- /dev/null +++ b/src/test/java/com/openosrs/injector/injectors/MixinInjectorTest.java @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.openosrs.injector.injectors; + +import com.google.common.io.ByteStreams; +import com.openosrs.injector.TestInjection; +import com.openosrs.injector.rsapi.RSApi; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.inject.Provider; +import net.runelite.api.mixins.Copy; +import net.runelite.api.mixins.Replace; +import net.runelite.api.mixins.Shadow; +import net.runelite.asm.ClassFile; +import net.runelite.asm.ClassGroup; +import net.runelite.asm.Field; +import net.runelite.asm.Method; +import static net.runelite.asm.Type.INT; +import net.runelite.asm.attributes.code.instructions.GetStatic; +import net.runelite.asm.attributes.code.instructions.InvokeVirtual; +import net.runelite.asm.attributes.code.instructions.LDC; +import net.runelite.asm.signature.Signature; +import net.runelite.asm.visitors.ClassFileVisitor; +import net.runelite.deob.util.JarUtil; +import net.runelite.mapping.ObfuscatedName; +import net.runelite.mapping.ObfuscatedSignature; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import org.junit.Test; +import org.objectweb.asm.ClassReader; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static org.objectweb.asm.Opcodes.ACC_STATIC; + +@ObfuscatedName("com/openosrs/injector/injectors/VanillaTarget") +class DeobTarget +{ + @ObfuscatedName("ob_foo4") + private static int foo4; + + @ObfuscatedName("ob_foo3") + @ObfuscatedSignature( + signature = "(I)V", + garbageValue = "123" + ) + private void foo3() + { + // De-obfuscated foo3 + System.out.println("foo3"); + } +} + +class VanillaTarget +{ + private static int ob_foo4; + + private void ob_foo3(int garbageValue) + { + // Obfuscated foo3 + if (garbageValue != 123) + { + return; + } + System.out.println("foo3"); + } +} + +abstract class Source +{ + @net.runelite.api.mixins.Inject + private static int foo; + @Shadow("foo4") + private static int foo4; + + @net.runelite.api.mixins.Inject + private void foo2() + { + } + + @Copy("foo3") + abstract void foo3(); + + @Replace("foo3") + private void rl$foo3() + { + System.out.println("replaced"); + System.out.println(foo4); + foo3(); + } +} + +// Test shadowing the "foo" field injected by Source +abstract class Source2 +{ + @Shadow("foo") + private static int foo; + + @net.runelite.api.mixins.Inject + private void foo5() + { + System.out.println(foo); + } +} + +public class MixinInjectorTest +{ + @Test + public void testInject() throws Exception + { + InputStream deobIn = getClass().getResourceAsStream("DeobTarget.class"); + ClassFile deobTarget = JarUtil.loadClass(ByteStreams.toByteArray(deobIn)); + + ClassGroup deob = new ClassGroup(); + deob.addClass(deobTarget); + + InputStream vanillaIn = getClass().getResourceAsStream("VanillaTarget.class"); + ClassFile vanillaTarget = JarUtil.loadClass(ByteStreams.toByteArray(vanillaIn)); + + ClassGroup vanilla = new ClassGroup(); + vanilla.addClass(vanillaTarget); + + Map, List> mixinClasses = new HashMap<>(); + mixinClasses.put(() -> loadClass(Source.class), Collections.singletonList(vanillaTarget)); + mixinClasses.put(() -> loadClass(Source2.class), Collections.singletonList(vanillaTarget)); + + TestInjection inject = new TestInjection(vanilla, deob, new ClassGroup(), new RSApi()); + new MixinInjector(inject).inject(mixinClasses); + + // Check if "foo" has been injected + Field foo = vanillaTarget.findField("foo"); + assertNotNull(foo); + assertEquals(INT, foo.getType()); + assertEquals(ACC_PUBLIC | ACC_STATIC, foo.getAccessFlags()); + + // Check if "foo2()V" has been injected + Method foo2 = vanillaTarget.findMethod("foo2"); + assertNotNull(foo2); + assertEquals(new Signature("()V"), foo2.getDescriptor()); + assertEquals(ACC_PUBLIC, foo2.getAccessFlags()); + + // Check if "ob_foo3(I)V" was copied + Method foo3 = vanillaTarget.findMethod("copy$foo3"); + assertNotNull(foo3); + assertEquals(new Signature("(I)V"), foo3.getDescriptor()); + assertEquals(ACC_PUBLIC, foo3.getAccessFlags()); + + // Check if "ob_foo3(I)V" was replaced + Method ob_foo3 = vanillaTarget.findMethod("ob_foo3"); + assertNotNull(ob_foo3); + assertEquals(new Signature("(I)V"), ob_foo3.getDescriptor()); + assertEquals(ob_foo3 + .getCode() + .getInstructions() + .getInstructions() + .stream() + .filter(i -> i instanceof LDC && ((LDC) i).getConstant().equals("replaced")) + .count(), 1); + // Check that the "foo4" field access in the new code body was mapped correctly + assertEquals(ob_foo3 + .getCode() + .getInstructions() + .getInstructions() + .stream() + .filter(i -> + { + if (!(i instanceof GetStatic)) + { + return false; + } + + net.runelite.asm.pool.Field field = ((GetStatic) i).getField(); + + if (!field.getClazz().getName().equals("com/openosrs/injector/injectors/VanillaTarget")) + { + return false; + } + + if (!field.getName().equals("ob_foo4")) + { + return false; + } + + return true; + }) + .count(), 1); + // Check that the "foo3()" call in the new code body was mapped to the copy + assertEquals(ob_foo3 + .getCode() + .getInstructions() + .getInstructions() + .stream() + .filter(i -> + { + if (!(i instanceof InvokeVirtual)) + { + return false; + } + + net.runelite.asm.pool.Method method = ((InvokeVirtual) i).getMethod(); + + if (!method.getClazz().getName().equals("com/openosrs/injector/injectors/VanillaTarget")) + { + return false; + } + + if (!method.getName().equals("copy$foo3")) + { + return false; + } + + return true; + }) + .count(), 1); + + // Check if "foo5()V" was injected + Method foo5 = vanillaTarget.findMethod("foo5"); + assertNotNull(foo5); + assertEquals(new Signature("()V"), foo5.getDescriptor()); + assertEquals(ACC_PUBLIC, foo5.getAccessFlags()); + // Check that the shadow "foo" field access was mapped correctly + assertEquals(foo5 + .getCode() + .getInstructions() + .getInstructions() + .stream() + .filter(i -> + { + if (!(i instanceof GetStatic)) + { + return false; + } + + net.runelite.asm.pool.Field field = ((GetStatic) i).getField(); + + if (!field.getClazz().getName().equals("com/openosrs/injector/injectors/VanillaTarget")) + { + return false; + } + + if (!field.getName().equals("foo")) + { + return false; + } + + return true; + }) + .count(), 1); + } + + private static ClassFile loadClass(Class clazz) + { + try (InputStream is = clazz.getClassLoader().getResourceAsStream(clazz.getName().replace('.', '/') + ".class")) + { + ClassReader reader = new ClassReader(is); + ClassFileVisitor cv = new ClassFileVisitor(); + + reader.accept(cv, 0); + + return cv.getClassFile(); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } +}