Readd (and fix) injector tests

This commit is contained in:
Lucwousin
2022-01-25 20:44:32 +01:00
parent 225ee103bf
commit 3ec6dfd113
17 changed files with 610 additions and 0 deletions

View File

@@ -14,8 +14,10 @@ dependencies {
vanillaDep(group = "net.runelite.rs", name = "vanilla", version = rsversion.toString())
annotationProcessor(group = "org.projectlombok", name = "lombok", version = ProjectVersions.lombokVersion)
testAnnotationProcessor(group = "org.projectlombok", name = "lombok", version = ProjectVersions.lombokVersion)
compileOnly(group = "org.projectlombok", name = "lombok", version = ProjectVersions.lombokVersion)
testCompileOnly(group = "org.projectlombok", name = "lombok", version = ProjectVersions.lombokVersion)
implementation(gradleApi())
@@ -29,6 +31,8 @@ dependencies {
implementation(group = "org.jetbrains", name = "annotations", version = "22.0.0")
implementation(group = "com.google.guava", name = "guava", version = "30.1.1-jre")
implementation(group = "net.sf.jopt-simple", name = "jopt-simple", version = "5.0.4")
testImplementation(group = "junit", name = "junit", version = "4.12")
}
tasks.register<JavaExec>("inject") {

View File

@@ -13,7 +13,9 @@ import com.openosrs.injector.rsapi.RSApi;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
@@ -24,6 +26,8 @@ import net.runelite.asm.signature.Signature;
/**
* Abstract class meant as the interface of {@link com.openosrs.injector.Injector injection} for injectors
*/
@AllArgsConstructor
@NoArgsConstructor
public abstract class InjectData
{
public static final String HOOKS = "net/runelite/client/callback/Hooks";

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.injectors.Injector;
import com.openosrs.injector.rsapi.RSApi;
import net.runelite.asm.ClassGroup;
public class TestInjection extends InjectData
{
public TestInjection(ClassGroup vanilla, ClassGroup deobfuscated, ClassGroup mixins, RSApi rsApi)
{
super(vanilla, deobfuscated, mixins, rsApi);
}
@Override
public void runChildInjector(Injector injector) throws InjectException
{
injector.inject();
}
}

View File

@@ -0,0 +1,311 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* 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.ListIterator;
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.Instruction;
import net.runelite.asm.attributes.code.instructions.GetStatic;
import net.runelite.asm.attributes.code.instructions.IMul;
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.ObfuscatedGetter;
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")
@ObfuscatedGetter(intValue = 1157381415)
static int foo4;
@ObfuscatedName("ob_foo3")
@ObfuscatedSignature(
descriptor = "(I)V",
garbageValue = "123"
)
private void foo3()
{
// De-obfuscated foo3
System.out.println("foo3");
}
}
class VanillaTarget
{
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")
@Replace("foo3")
private void copy$foo3()
{
System.out.println("replaced");
System.out.println(foo4);
copy$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<Provider<ClassFile>, List<ClassFile>> 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());
inject.initToVanilla();
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);
assert getStaticHasGetter(ob_foo3, "ob_foo4");
// 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 boolean getStaticHasGetter(Method ob_foo3, String gottenField)
{
ListIterator<Instruction> it = ob_foo3.getCode().getInstructions().listIterator();
Instruction i;
while (it.hasNext() &&
!((i = it.next()) instanceof GetStatic &&
((GetStatic) i).getField().getName().equals(gottenField)));
return
(i = it.next()) instanceof LDC &&
((LDC) i).getConstantAsInt() == 1157381415 &&
it.next() instanceof IMul;
}
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);
}
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright (c) 2018, Lotto <https://github.com/devLotto>
* 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.raw;
import com.google.common.io.ByteStreams;
import com.openosrs.injector.InjectUtil;
import com.openosrs.injector.TestInjection;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.rsapi.RSApi;
import javax.swing.plaf.TabbedPaneUI;
import net.runelite.asm.Annotation;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Named;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Annotated;
import net.runelite.deob.DeobAnnotations;
import net.runelite.deob.util.JarUtil;
import org.junit.Test;
import static net.runelite.deob.DeobAnnotations.OBFUSCATED_NAME;
import static net.runelite.deob.DeobAnnotations.OBFUSCATED_SIGNATURE;
public class DrawAfterWidgetsTest
{
@Test
public void testInjectDrawWidgetsRev160() throws Exception
{
// Rev 160 does not have the drawWidgets call inlined
ClassFile deobClient = JarUtil.loadClass(ByteStreams.toByteArray(getClass().getResourceAsStream("/drawafterwidgets/Client_deob160.class")));
ClassFile deobRasterizer = JarUtil.loadClass(ByteStreams.toByteArray(getClass().getResourceAsStream("/drawafterwidgets/Rasterizer2D_deob160.class")));
ClassGroup deob = new ClassGroup();
deob.addClass(deobClient);
deob.addClass(deobRasterizer);
ClassFile obClient = JarUtil.loadClass(ByteStreams.toByteArray(getClass().getResourceAsStream("/drawafterwidgets/Client_ob160.class")));
ClassFile obRasterizer = JarUtil.loadClass(ByteStreams.toByteArray(getClass().getResourceAsStream("/drawafterwidgets/Rasterizer2D_ob160.class")));
ClassGroup vanilla = new ClassGroup();
vanilla.addClass(obClient);
vanilla.addClass(obRasterizer);
InjectData inject = new TestInjection(vanilla, deob, new ClassGroup(), new RSApi());
addPhonyFields(deob, vanilla);
inject.initToVanilla();
new DrawAfterWidgets(inject).inject();
}
@Test
public void testInjectDrawWidgetsRev180() throws Exception
{
// Rev 180 has the drawWidgets call inlined
ClassFile deobClient = JarUtil.loadClass(ByteStreams.toByteArray(getClass().getResourceAsStream("/drawafterwidgets/Client_deob180.class")));
ClassFile deobRasterizer = JarUtil.loadClass(ByteStreams.toByteArray(getClass().getResourceAsStream("/drawafterwidgets/Rasterizer2D_deob180.class")));
ClassGroup deob = new ClassGroup();
deob.addClass(deobClient);
deob.addClass(deobRasterizer);
ClassFile obClient = JarUtil.loadClass(ByteStreams.toByteArray(getClass().getResourceAsStream("/drawafterwidgets/Client_ob180.class")));
ClassFile obRasterizer = JarUtil.loadClass(ByteStreams.toByteArray(getClass().getResourceAsStream("/drawafterwidgets/Rasterizer2D_ob180.class")));
ClassGroup vanilla = new ClassGroup();
vanilla.addClass(obClient);
vanilla.addClass(obRasterizer);
InjectData inject = new TestInjection(vanilla, deob, new ClassGroup(), new RSApi());
addPhonyFields(deob, vanilla);
inject.initToVanilla();
new DrawAfterWidgets(inject).inject();
}
private static void addPhonyFields(ClassGroup deob, ClassGroup vanilla)
{
final ClassFile d = deob.findClass("Client");
final ClassFile v = vanilla.findClass("client");
final Field clientD = new Field(d, "client", new Type("LClient;"));
clientD.findAnnotation(OBFUSCATED_NAME, true).setElement("obclient");
clientD.findAnnotation(OBFUSCATED_SIGNATURE, true).setElement("descriptor", "Lclient;");
clientD.setStatic();
d.addField(clientD);
final Field clientV = new Field(v, "obclient", new Type("Lclient;"));
clientV.setStatic();
v.addField(clientV);
final Field callbacks = new Field(v, "callbacks", new Type("LCallbacks;"));
v.addField(callbacks);
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) 2019, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.injectors.raw;
import com.google.common.io.ByteStreams;
import com.openosrs.injector.TestInjection;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.rsapi.RSApi;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.deob.util.JarUtil;
import org.junit.Test;
public class DrawMenuTest
{
@Test
public void test160() throws Exception
{
// 160 has both drawMenu and drawTopLeftText inlined
ClassFile deobClient = JarUtil.loadClass(ByteStreams.toByteArray(getClass().getResourceAsStream("/drawafterwidgets/Client_deob160.class")));
ClassFile obClient = JarUtil.loadClass(ByteStreams.toByteArray(getClass().getResourceAsStream("/drawafterwidgets/Client_ob160.class")));
ClassGroup van = new ClassGroup();
van.addClass(obClient);
ClassGroup deob = new ClassGroup();
deob.addClass(deobClient);
InjectData inject = new TestInjection(van, deob, new ClassGroup(), new RSApi());
inject.initToVanilla();
new DrawMenu(inject).inject();
}
@Test
public void test180() throws Exception
{
// 180 has only drawMenu inlined
ClassFile deobClient = JarUtil.loadClass(ByteStreams.toByteArray(getClass().getResourceAsStream("/drawafterwidgets/Client_deob180.class")));
ClassFile obClient = JarUtil.loadClass(ByteStreams.toByteArray(getClass().getResourceAsStream("/drawafterwidgets/Client_ob180.class")));
ClassGroup van = new ClassGroup();
van.addClass(obClient);
ClassGroup deob = new ClassGroup();
deob.addClass(deobClient);
InjectData inject = new TestInjection(van, deob, new ClassGroup(), new RSApi());
inject.initToVanilla();
new DrawMenu(inject).inject();
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2020, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.transformers;
import com.openosrs.injector.InjectException;
import com.openosrs.injector.injection.InjectData;
import com.openosrs.injector.injectors.Injector;
import com.openosrs.injector.transformers.srcchangeclasses.NewName;
import com.openosrs.injector.transformers.srcchangeclasses.OldName;
import java.lang.invoke.MethodHandles;
import java.util.function.BiConsumer;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.objectwebasm.NonloadingClassWriter;
import net.runelite.asm.visitors.ClassFileVisitor;
import org.junit.Ignore;
import org.junit.Test;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
public class SourceChangerTest
{
private static final String PACKAGE = "com.openosrs.injector.transformers.srcchangeclasses.";
@Test
@Ignore // Ignored because it's not really testing anything atm, but it works!
public void test() throws Exception
{
final ClassFileVisitor deob = new ClassFileVisitor(), vann = new ClassFileVisitor();
new ClassReader(PACKAGE + "NewName").accept(deob, ClassReader.SKIP_FRAMES);
new ClassReader(PACKAGE + "OldName").accept(vann, ClassReader.SKIP_FRAMES);
new SourceChanger(
new InjectData(new ClassGroup(), new ClassGroup(), null, null) {
public void runChildInjector(Injector injector) throws InjectException
{}
@Override
public void forEachPair(BiConsumer<ClassFile, ClassFile> consumer)
{
consumer.accept(deob.getClassFile(), vann.getClassFile());
}
}).transformImpl();
final ClassGroup group = new ClassGroup();
group.addClass(vann.getClassFile());
final ClassWriter cw = new NonloadingClassWriter(group, 0);
vann.getClassFile().accept(cw);
OldName obj = (OldName) MethodHandles.privateLookupIn(
NewName.class,
MethodHandles.lookup())
.defineClass(cw.toByteArray())
.getDeclaredConstructor()
.newInstance();
obj.obfMethodName();
}
}

View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) 2020, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.transformers.srcchangeclasses;
public class NewName
{
public void deobMethodName()
{
// do something interesting
}
}

View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) 2020, Lucas <https://github.com/Lucwousin>
* All rights reserved.
*
* This code is licensed under GPL3, see the complete license in
* the LICENSE file in the root directory of this source tree.
*/
package com.openosrs.injector.transformers.srcchangeclasses;
public class OldName
{
public void obfMethodName()
{
new RuntimeException().printStackTrace();
}
}