Port Injector to main layout
Adds deobfuscator Adds injected-client Adds injector-plugin Adds runescape-client Replaces RL's apis Small bug with sprites atm, will be resolved soon. tired af. Builds, probably
This commit is contained in:
225
deobfuscator/src/main/java/net/runelite/deob/Deob.java
Normal file
225
deobfuscator/src/main/java/net/runelite/deob/Deob.java
Normal file
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob;
|
||||
|
||||
import com.google.common.base.Stopwatch;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.Type;
|
||||
import net.runelite.asm.attributes.Annotations;
|
||||
import net.runelite.asm.execution.Execution;
|
||||
import net.runelite.deob.deobfuscators.transformers.GetPathTransformer;
|
||||
import net.runelite.deob.deobfuscators.CastNull;
|
||||
import net.runelite.deob.deobfuscators.constparam.ConstantParameter;
|
||||
import net.runelite.deob.deobfuscators.EnumDeobfuscator;
|
||||
import net.runelite.deob.deobfuscators.FieldInliner;
|
||||
import net.runelite.deob.deobfuscators.IllegalStateExceptions;
|
||||
import net.runelite.deob.deobfuscators.Lvt;
|
||||
import net.runelite.deob.deobfuscators.Order;
|
||||
import net.runelite.deob.deobfuscators.RenameUnique;
|
||||
import net.runelite.deob.deobfuscators.RuntimeExceptions;
|
||||
import net.runelite.deob.deobfuscators.UnreachedCode;
|
||||
import net.runelite.deob.deobfuscators.UnusedClass;
|
||||
import net.runelite.deob.deobfuscators.UnusedFields;
|
||||
import net.runelite.deob.deobfuscators.UnusedMethods;
|
||||
import net.runelite.deob.deobfuscators.UnusedParameters;
|
||||
import net.runelite.deob.deobfuscators.arithmetic.ModArith;
|
||||
import net.runelite.deob.deobfuscators.arithmetic.MultiplicationDeobfuscator;
|
||||
import net.runelite.deob.deobfuscators.arithmetic.MultiplyOneDeobfuscator;
|
||||
import net.runelite.deob.deobfuscators.arithmetic.MultiplyZeroDeobfuscator;
|
||||
import net.runelite.deob.deobfuscators.cfg.ControlFlowDeobfuscator;
|
||||
import net.runelite.deob.deobfuscators.exprargorder.ExprArgOrder;
|
||||
import net.runelite.deob.deobfuscators.menuaction.MenuActionDeobfuscator;
|
||||
import net.runelite.deob.deobfuscators.transformers.ClientErrorTransformer;
|
||||
import net.runelite.deob.deobfuscators.transformers.MaxMemoryTransformer;
|
||||
import net.runelite.deob.deobfuscators.transformers.OpcodesTransformer;
|
||||
import net.runelite.deob.deobfuscators.transformers.ReflectionTransformer;
|
||||
import net.runelite.deob.util.JarUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Deob
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(Deob.class);
|
||||
|
||||
public static final int OBFUSCATED_NAME_MAX_LEN = 2;
|
||||
private static final boolean CHECK_EXEC = false;
|
||||
|
||||
public static void main(String[] args) throws IOException
|
||||
{
|
||||
if (args == null || args.length < 2)
|
||||
{
|
||||
System.err.println("Syntax: input_jar output_jar");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
//logger.info("Deobfuscator revision {}", DeobProperties.getRevision());
|
||||
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
|
||||
ClassGroup group = JarUtil.loadJar(new File(args[0]));
|
||||
|
||||
for (ClassFile f : group.getClasses())
|
||||
{
|
||||
f.getAnnotations().clearAnnotations();
|
||||
for (Method m : f.getMethods())
|
||||
{
|
||||
Annotations an = m.getAnnotations();
|
||||
an.clearAnnotations();
|
||||
}
|
||||
for (Field fi : f.getFields())
|
||||
{
|
||||
Annotations an = fi.getAnnotations();
|
||||
if (an.find(new Type("Ljavax/inject/Inject;")) == null)
|
||||
{
|
||||
an.clearAnnotations();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.info("Class {}, field {} has inject", f.getClassName(), fi.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove except RuntimeException
|
||||
run(group, new RuntimeExceptions());
|
||||
|
||||
run(group, new ControlFlowDeobfuscator());
|
||||
|
||||
run(group, new RenameUnique());
|
||||
|
||||
// remove unused methods - this leaves Code with no instructions,
|
||||
// which is not valid, so unused methods is run after
|
||||
run(group, new UnreachedCode());
|
||||
run(group, new UnusedMethods());
|
||||
|
||||
// remove illegal state exceptions, frees up some parameters
|
||||
run(group, new IllegalStateExceptions());
|
||||
|
||||
// remove constant logically dead parameters
|
||||
run(group, new ConstantParameter());
|
||||
|
||||
// remove unhit blocks
|
||||
run(group, new UnreachedCode());
|
||||
run(group, new UnusedMethods());
|
||||
|
||||
// remove unused parameters
|
||||
run(group, new UnusedParameters());
|
||||
|
||||
// remove unused fields
|
||||
run(group, new UnusedFields());
|
||||
|
||||
run(group, new FieldInliner());
|
||||
|
||||
// order uses class name order for sorting fields/methods,
|
||||
// so run it before removing classes below
|
||||
run(group, new Order());
|
||||
|
||||
run(group, new UnusedClass());
|
||||
|
||||
runMath(group);
|
||||
|
||||
run(group, new ExprArgOrder());
|
||||
|
||||
run(group, new Lvt());
|
||||
|
||||
run(group, new CastNull());
|
||||
|
||||
run(group, new EnumDeobfuscator());
|
||||
|
||||
new OpcodesTransformer().transform(group);
|
||||
//run(group, new PacketHandlerOrder());
|
||||
//run(group, new PacketWriteDeobfuscator());
|
||||
|
||||
run(group, new MenuActionDeobfuscator());
|
||||
|
||||
new GetPathTransformer().transform(group);
|
||||
new ClientErrorTransformer().transform(group);
|
||||
new ReflectionTransformer().transform(group);
|
||||
new MaxMemoryTransformer().transform(group);
|
||||
//new RuneliteBufferTransformer().transform(group);
|
||||
|
||||
JarUtil.saveJar(group, new File(args[1]));
|
||||
|
||||
stopwatch.stop();
|
||||
logger.info("Done in {}", stopwatch);
|
||||
}
|
||||
|
||||
public static boolean isObfuscated(String name)
|
||||
{
|
||||
return name.length() <= OBFUSCATED_NAME_MAX_LEN || name.startsWith("method") || name.startsWith("vmethod") || name.startsWith("field") || name.startsWith("class");
|
||||
}
|
||||
|
||||
private static void runMath(ClassGroup group)
|
||||
{
|
||||
ModArith mod = new ModArith();
|
||||
mod.run(group);
|
||||
|
||||
int last = -1, cur;
|
||||
while ((cur = mod.runOnce()) > 0)
|
||||
{
|
||||
new MultiplicationDeobfuscator().run(group);
|
||||
|
||||
// do not remove 1 * field so that ModArith can detect
|
||||
// the change in guessDecreasesConstants()
|
||||
new MultiplyOneDeobfuscator(true).run(group);
|
||||
|
||||
new MultiplyZeroDeobfuscator().run(group);
|
||||
|
||||
if (last == cur)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
last = cur;
|
||||
}
|
||||
|
||||
// now that modarith is done, remove field * 1
|
||||
new MultiplyOneDeobfuscator(false).run(group);
|
||||
|
||||
mod.annotateEncryption();
|
||||
}
|
||||
|
||||
private static void run(ClassGroup group, Deobfuscator deob)
|
||||
{
|
||||
Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
deob.run(group);
|
||||
stopwatch.stop();
|
||||
|
||||
logger.info("{} took {}", deob.getClass().getSimpleName(), stopwatch);
|
||||
|
||||
// check code is still correct
|
||||
if (CHECK_EXEC)
|
||||
{
|
||||
Execution execution = new Execution(group);
|
||||
execution.populateInitialMethods();
|
||||
execution.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob;
|
||||
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.Type;
|
||||
import net.runelite.asm.attributes.Annotations;
|
||||
import net.runelite.asm.attributes.annotation.Annotation;
|
||||
import net.runelite.asm.attributes.annotation.Element;
|
||||
import net.runelite.asm.signature.Signature;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DeobAnnotations
|
||||
{
|
||||
public static final Type OBFUSCATED_NAME = new Type("Lnet/runelite/mapping/ObfuscatedName;");
|
||||
public static final Type EXPORT = new Type("Lnet/runelite/mapping/Export;");
|
||||
public static final Type IMPLEMENTS = new Type("Lnet/runelite/mapping/Implements;");
|
||||
public static final Type OBFUSCATED_GETTER = new Type("Lnet/runelite/mapping/ObfuscatedGetter;");
|
||||
public static final Type OBFUSCATED_SIGNATURE = new Type("Lnet/runelite/mapping/ObfuscatedSignature;");
|
||||
public static final Type HOOK = new Type("Lnet/runelite/mapping/Hook;");
|
||||
|
||||
public static Signature getObfuscatedSignature(Method m)
|
||||
{
|
||||
String str = getAnnotationValue(m.getAnnotations(), OBFUSCATED_SIGNATURE);
|
||||
|
||||
if (str == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Signature(str);
|
||||
}
|
||||
|
||||
public static Type getObfuscatedType(Field f)
|
||||
{
|
||||
String str = getAnnotationValue(f.getAnnotations(), OBFUSCATED_SIGNATURE);
|
||||
|
||||
if (str == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Type(str);
|
||||
}
|
||||
|
||||
public static String getObfuscatedName(Annotations an)
|
||||
{
|
||||
return getAnnotationValue(an, OBFUSCATED_NAME);
|
||||
}
|
||||
|
||||
public static String getExportedName(Annotations an)
|
||||
{
|
||||
return getAnnotationValue(an, EXPORT);
|
||||
}
|
||||
|
||||
public static String getImplements(ClassFile cf)
|
||||
{
|
||||
return getAnnotationValue(cf.getAnnotations(), IMPLEMENTS);
|
||||
}
|
||||
|
||||
public static String getHookName(Annotations an)
|
||||
{
|
||||
return getAnnotationValue(an, HOOK);
|
||||
}
|
||||
|
||||
public static Number getObfuscatedGetter(Field field)
|
||||
{
|
||||
if (field == null || field.getAnnotations() == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Annotation an = field.getAnnotations().find(OBFUSCATED_GETTER);
|
||||
if (an == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (Number) an.getElement().getValue();
|
||||
}
|
||||
|
||||
public static String getObfuscatedValue(Method method)
|
||||
{
|
||||
if (method == null || method.getAnnotations() == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Annotation an = method.getAnnotations().find(OBFUSCATED_SIGNATURE);
|
||||
if (an == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Element> elements = an.getElements();
|
||||
if (elements == null || elements.size() < 2)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (String) elements.get(1).getValue();
|
||||
}
|
||||
|
||||
private static String getAnnotationValue(Annotations an, Type type)
|
||||
{
|
||||
if (an == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Annotation a = an.find(type);
|
||||
if (a == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return a.getElement().getString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 net.runelite.deob;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
public class DeobProperties
|
||||
{
|
||||
public static String getRevision() throws IOException
|
||||
{
|
||||
Properties properties = new Properties();
|
||||
InputStream resourceAsStream = DeobProperties.class.getResourceAsStream("/deob.properties");
|
||||
properties.load(resourceAsStream);
|
||||
|
||||
return "420";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob;
|
||||
|
||||
import net.runelite.asm.ClassGroup;
|
||||
|
||||
public interface Deobfuscator
|
||||
{
|
||||
void run(ClassGroup group);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob;
|
||||
|
||||
import net.runelite.asm.ClassGroup;
|
||||
|
||||
public interface Transformer
|
||||
{
|
||||
void transform(ClassGroup group);
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.c2s;
|
||||
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.Type;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class IsaacCipherFinder
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(IsaacCipherFinder.class);
|
||||
|
||||
private static final int GOLDEN_RATIO = 0x9E3779B9; // 2^32 / phi
|
||||
private final ClassGroup group;
|
||||
|
||||
private ClassFile isaacCipher;
|
||||
private Method getNext;
|
||||
|
||||
public IsaacCipherFinder(ClassGroup group)
|
||||
{
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public ClassFile getIsaacCipher()
|
||||
{
|
||||
return isaacCipher;
|
||||
}
|
||||
|
||||
public Method getGetNext()
|
||||
{
|
||||
return getNext;
|
||||
}
|
||||
|
||||
public void find()
|
||||
{
|
||||
Method highest = null;
|
||||
int count = 0;
|
||||
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
Code code = m.getCode();
|
||||
|
||||
int i = find(m, code);
|
||||
if (i > count)
|
||||
{
|
||||
count = i;
|
||||
highest = m;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert highest != null;
|
||||
isaacCipher = highest.getClassFile();
|
||||
|
||||
// find nextInt
|
||||
for (Method method : isaacCipher.getMethods())
|
||||
{
|
||||
if (method.getDescriptor().size() == 0 && method.getDescriptor().getReturnValue().equals(Type.INT))
|
||||
{
|
||||
getNext = method;
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Found cipher {}, getNext {}", isaacCipher, getNext);
|
||||
}
|
||||
|
||||
private int find(Method method, Code code)
|
||||
{
|
||||
if (code == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gr = 0;
|
||||
|
||||
for (Instruction i : code.getInstructions().getInstructions())
|
||||
{
|
||||
if (i instanceof PushConstantInstruction)
|
||||
{
|
||||
PushConstantInstruction pci = (PushConstantInstruction) i;
|
||||
|
||||
if (pci.getConstant().equals(GOLDEN_RATIO))
|
||||
{
|
||||
++gr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return gr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.c2s;
|
||||
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class RWOpcodeFinder
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(RWOpcodeFinder.class);
|
||||
|
||||
private final ClassGroup group;
|
||||
|
||||
private Method readOpcode;
|
||||
private Method writeOpcode;
|
||||
|
||||
public RWOpcodeFinder(ClassGroup group)
|
||||
{
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public Method getReadOpcode()
|
||||
{
|
||||
return readOpcode;
|
||||
}
|
||||
|
||||
public Method getWriteOpcode()
|
||||
{
|
||||
return writeOpcode;
|
||||
}
|
||||
|
||||
public ClassFile getSecretBuffer()
|
||||
{
|
||||
assert writeOpcode.getClassFile() == readOpcode.getClassFile();
|
||||
return writeOpcode.getClassFile();
|
||||
}
|
||||
|
||||
public ClassFile getBuffer()
|
||||
{
|
||||
return getSecretBuffer().getParent();
|
||||
}
|
||||
|
||||
public void find()
|
||||
{
|
||||
IsaacCipherFinder ic = new IsaacCipherFinder(group);
|
||||
ic.find();
|
||||
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
Code code = m.getCode();
|
||||
|
||||
find(ic, m, code);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Found readOpcode {}, writeOpcode {}", readOpcode, writeOpcode);
|
||||
}
|
||||
|
||||
private void find(IsaacCipherFinder ic, Method method, Code code)
|
||||
{
|
||||
if (code == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (Instruction i : code.getInstructions().getInstructions())
|
||||
{
|
||||
if (i instanceof InvokeInstruction)
|
||||
{
|
||||
if (((InvokeInstruction) i).getMethods().contains(ic.getGetNext()))
|
||||
{
|
||||
if (method.getDescriptor().size() == 0)
|
||||
{
|
||||
// read opcode
|
||||
readOpcode = method;
|
||||
}
|
||||
else
|
||||
{
|
||||
// write opcode
|
||||
writeOpcode = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.clientver;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Enumeration;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
|
||||
public class ClientVersion
|
||||
{
|
||||
private final File jar;
|
||||
|
||||
public ClientVersion(File jar)
|
||||
{
|
||||
this.jar = jar;
|
||||
}
|
||||
|
||||
public int getVersion() throws IOException
|
||||
{
|
||||
try (JarFile jar = new JarFile(this.jar))
|
||||
{
|
||||
for (Enumeration<JarEntry> it = jar.entries(); it.hasMoreElements();)
|
||||
{
|
||||
JarEntry entry = it.nextElement();
|
||||
|
||||
if (!entry.getName().equals("client.class"))
|
||||
continue;
|
||||
|
||||
InputStream in = jar.getInputStream(entry);
|
||||
|
||||
ClassReader reader = new ClassReader(in);
|
||||
VersionClassVisitor v = new VersionClassVisitor();
|
||||
reader.accept(v, 0);
|
||||
return v.getVersion();
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.clientver;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class ClientVersionMain
|
||||
{
|
||||
public static void main(String[] args) throws IOException
|
||||
{
|
||||
File jar = new File(args[0]);
|
||||
ClientVersion cv = new ClientVersion(jar);
|
||||
System.out.println(cv.getVersion());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.clientver;
|
||||
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
public class VersionClassVisitor extends ClassVisitor
|
||||
{
|
||||
private VersionMethodVisitor vmv = new VersionMethodVisitor();
|
||||
|
||||
public VersionClassVisitor()
|
||||
{
|
||||
super(Opcodes.ASM5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access,
|
||||
String name,
|
||||
String desc,
|
||||
String signature,
|
||||
String[] exceptions)
|
||||
{
|
||||
if (!name.equals("init"))
|
||||
return null;
|
||||
|
||||
return vmv;
|
||||
}
|
||||
|
||||
public int getVersion()
|
||||
{
|
||||
return vmv.getVersion();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.clientver;
|
||||
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
public class VersionMethodVisitor extends MethodVisitor
|
||||
{
|
||||
private int state = 0;
|
||||
private int version = -1;
|
||||
|
||||
VersionMethodVisitor()
|
||||
{
|
||||
super(Opcodes.ASM5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitIntInsn(int opcode, int operand)
|
||||
{
|
||||
if (state == 2)
|
||||
{
|
||||
version = operand;
|
||||
++state;
|
||||
}
|
||||
|
||||
if (operand == 765 || operand == 503)
|
||||
{
|
||||
++state;
|
||||
}
|
||||
}
|
||||
|
||||
public int getVersion()
|
||||
{
|
||||
return version;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instructions.AConstNull;
|
||||
import net.runelite.asm.attributes.code.instructions.CheckCast;
|
||||
import net.runelite.asm.execution.Execution;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
import net.runelite.asm.execution.MethodContext;
|
||||
import net.runelite.asm.execution.StackContext;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class CastNull implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(CastNull.class);
|
||||
|
||||
private int removed;
|
||||
|
||||
private final List<Instruction> interesting = new ArrayList<>();
|
||||
private final List<Instruction> notInteresting = new ArrayList<>();
|
||||
|
||||
private void visit(InstructionContext ictx)
|
||||
{
|
||||
if (!(ictx.getInstruction() instanceof CheckCast))
|
||||
return;
|
||||
|
||||
if (notInteresting.contains(ictx.getInstruction()) || interesting.contains(ictx.getInstruction()))
|
||||
return;
|
||||
|
||||
StackContext sctx = ictx.getPops().get(0);
|
||||
if (sctx.getPushed().getInstruction() instanceof AConstNull)
|
||||
{
|
||||
interesting.add(ictx.getInstruction());
|
||||
}
|
||||
else
|
||||
{
|
||||
interesting.remove(ictx.getInstruction());
|
||||
notInteresting.add(ictx.getInstruction());
|
||||
}
|
||||
}
|
||||
|
||||
private void visit(MethodContext ctx)
|
||||
{
|
||||
Instructions ins = ctx.getMethod().getCode().getInstructions();
|
||||
interesting.forEach(ins::remove);
|
||||
removed += interesting.size();
|
||||
interesting.clear();
|
||||
notInteresting.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
Execution execution = new Execution(group);
|
||||
execution.addExecutionVisitor(i -> visit(i));
|
||||
execution.addMethodContextVisitor(i -> visit(i));
|
||||
execution.populateInitialMethods();
|
||||
execution.run();
|
||||
|
||||
logger.info("Removed {} casts on null", removed);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.Type;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.InstructionType;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
|
||||
import net.runelite.asm.attributes.code.instructions.ALoad;
|
||||
import net.runelite.asm.attributes.code.instructions.ILoad;
|
||||
import net.runelite.asm.attributes.code.instructions.InvokeSpecial;
|
||||
import net.runelite.asm.attributes.code.instructions.LDC;
|
||||
import net.runelite.asm.attributes.code.instructions.PutStatic;
|
||||
import net.runelite.asm.signature.Signature;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class EnumDeobfuscator implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(EnumDeobfuscator.class);
|
||||
|
||||
private static final net.runelite.asm.pool.Method ENUM_INIT = new net.runelite.asm.pool.Method(
|
||||
new net.runelite.asm.pool.Class("java/lang/Enum"),
|
||||
"<init>",
|
||||
new Signature("(Ljava/lang/String;I)V")
|
||||
);
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
if (!isEnum(cf) || cf.isEnum())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.info("Converting {} to an enum", cf.getName());
|
||||
makeEnum(cf);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEnum(ClassFile cf)
|
||||
{
|
||||
if (cf.getInterfaces().getMyInterfaces().size() != 1
|
||||
|| cf.getInterfaces().getInterfaces().size() != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!interfaceIsEnum(cf.getInterfaces().getMyInterfaces().get(0)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// number of non static methods should be 1
|
||||
long count = cf.getMethods()
|
||||
.stream()
|
||||
.filter(m -> !m.isStatic())
|
||||
.filter(m -> !m.getName().startsWith("<"))
|
||||
.count();
|
||||
if (count != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean interfaceIsEnum(ClassFile cf)
|
||||
{
|
||||
assert cf.isInterface();
|
||||
|
||||
if (cf.getMethods().size() != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Method method = cf.getMethods().get(0);
|
||||
return method.getDescriptor().toString().equals("()I"); // ordinal
|
||||
}
|
||||
|
||||
private void makeEnum(ClassFile cf)
|
||||
{
|
||||
cf.setEnum(); // make class an enum
|
||||
|
||||
// enums super class is java/lang/Enum
|
||||
assert cf.getParentClass().getName().equals("java/lang/Object");
|
||||
cf.setSuperName("java/lang/Enum");
|
||||
|
||||
// all static fields of the type of the class become enum members
|
||||
for (Field field : cf.getFields())
|
||||
{
|
||||
if (field.isStatic() && field.getType().equals(new Type("L" + cf.getName() + ";")))
|
||||
{
|
||||
field.setEnum();
|
||||
}
|
||||
}
|
||||
|
||||
for (Method method : cf.getMethods())
|
||||
{
|
||||
if (!method.getName().equals("<init>"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add string as first argument, which is the field name,
|
||||
// and ordinal as second argument
|
||||
Signature signature = new Signature.Builder()
|
||||
.setReturnType(method.getDescriptor().getReturnValue())
|
||||
.addArgument(Type.STRING)
|
||||
.addArgument(Type.INT)
|
||||
.addArguments(method.getDescriptor().getArguments())
|
||||
.build();
|
||||
|
||||
method.setDescriptor(signature);
|
||||
|
||||
// Remove instructions up to invokespecial
|
||||
Instructions ins = method.getCode().getInstructions();
|
||||
Instruction i;
|
||||
do
|
||||
{
|
||||
i = ins.getInstructions().get(0);
|
||||
ins.remove(i);
|
||||
}
|
||||
while (i.getType() != InstructionType.INVOKESPECIAL);
|
||||
|
||||
ins.addInstruction(0, new ALoad(ins, 0)); // load this
|
||||
ins.addInstruction(1, new ALoad(ins, 1)); // load constant name
|
||||
ins.addInstruction(2, new ILoad(ins, 2)); // ordinal
|
||||
ins.addInstruction(3, new InvokeSpecial(ins, ENUM_INIT)); // invoke enum constructor
|
||||
|
||||
// Shift all indexes after this up +2 because of the new String and int argument
|
||||
for (int j = 4; j < ins.getInstructions().size(); ++j)
|
||||
{
|
||||
i = ins.getInstructions().get(j);
|
||||
|
||||
if (i instanceof LVTInstruction)
|
||||
{
|
||||
LVTInstruction lvt = ((LVTInstruction) i);
|
||||
int idx = lvt.getVariableIndex();
|
||||
if (idx != 0)
|
||||
{
|
||||
lvt.setVariableIndex(idx + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Order of fields being set in clinit, which is the order
|
||||
// the enum fields are actually in
|
||||
List<Field> order = new ArrayList<>();
|
||||
|
||||
for (Method method : cf.getMethods())
|
||||
{
|
||||
if (!method.getName().equals("<clinit>"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Instructions ins = method.getCode().getInstructions();
|
||||
|
||||
int count = 0;
|
||||
// sometimes there is new new invokespecial invokespecial putfield
|
||||
// for eg enum member field30(1, 2, String.class, new class5());
|
||||
boolean seenDup = false;
|
||||
for (int j = 0; j < ins.getInstructions().size(); ++j)
|
||||
{
|
||||
Instruction i = ins.getInstructions().get(j);
|
||||
|
||||
if (i.getType() == InstructionType.DUP && !seenDup)
|
||||
{
|
||||
// XXX this should actually be the field name, but it seems to have no effect on fernflower
|
||||
ins.addInstruction(j + 1, new LDC(ins, "runelite"));
|
||||
ins.addInstruction(j + 2, new LDC(ins, count++));
|
||||
seenDup = true;
|
||||
}
|
||||
else if (i.getType() == InstructionType.INVOKESPECIAL)
|
||||
{
|
||||
Instruction next = ins.getInstructions().get(j + 1);
|
||||
|
||||
// check if this is the invokespecial on the enum, putstatic comes next
|
||||
if (next.getType() == InstructionType.PUTSTATIC)
|
||||
{
|
||||
InvokeSpecial is = (InvokeSpecial) i;
|
||||
PutStatic ps = (PutStatic) next;
|
||||
|
||||
net.runelite.asm.pool.Method pmethod = new net.runelite.asm.pool.Method(
|
||||
is.getMethod().getClazz(),
|
||||
is.getMethod().getName(),
|
||||
new Signature.Builder()
|
||||
.setReturnType(is.getMethod().getType().getReturnValue())
|
||||
.addArgument(Type.STRING)
|
||||
.addArgument(Type.INT)
|
||||
.addArguments(is.getMethod().getType().getArguments())
|
||||
.build()
|
||||
);
|
||||
|
||||
is.setMethod(pmethod);
|
||||
|
||||
Field field = ps.getMyField();
|
||||
assert field != null;
|
||||
order.add(field);
|
||||
|
||||
seenDup = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enum fields must be first. Also they are in order in clinit.
|
||||
// Sort fields
|
||||
Collections.sort(cf.getFields(), (f1, f2) ->
|
||||
{
|
||||
int idx1 = order.indexOf(f1);
|
||||
int idx2 = order.indexOf(f2);
|
||||
|
||||
if (idx1 == -1)
|
||||
{
|
||||
idx1 = Integer.MAX_VALUE;
|
||||
}
|
||||
if (idx2 == -1)
|
||||
{
|
||||
idx2 = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
return Integer.compare(idx1, idx2);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.Type;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instruction.types.FieldInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.GetFieldInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.SetFieldInstruction;
|
||||
import net.runelite.asm.attributes.code.instructions.LDC;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class FieldInliner implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(FieldInliner.class);
|
||||
|
||||
private ClassGroup group;
|
||||
private Multimap<Field, FieldInstruction> fieldInstructions = HashMultimap.create();
|
||||
private List<Field> fields = new ArrayList<>();
|
||||
|
||||
private void findFieldIns()
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
Code code = m.getCode();
|
||||
|
||||
if (code == null)
|
||||
continue;
|
||||
|
||||
for (Instruction i : code.getInstructions().getInstructions())
|
||||
{
|
||||
if (!(i instanceof FieldInstruction))
|
||||
continue;
|
||||
|
||||
FieldInstruction sf = (FieldInstruction) i;
|
||||
|
||||
if (sf.getMyField() == null)
|
||||
continue;
|
||||
|
||||
fieldInstructions.put(sf.getMyField(), sf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void makeConstantValues()
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Field f : cf.getFields())
|
||||
{
|
||||
if (!f.isStatic() || !f.getType().equals(Type.STRING))
|
||||
continue;
|
||||
|
||||
Object constantValue = f.getValue();
|
||||
if (constantValue != null)
|
||||
continue;
|
||||
|
||||
List<FieldInstruction> sfis = fieldInstructions.get(f).stream().filter(f2 -> f2 instanceof SetFieldInstruction).collect(Collectors.toList());
|
||||
if (sfis.size() != 1)
|
||||
continue;
|
||||
|
||||
SetFieldInstruction sfi = (SetFieldInstruction) sfis.get(0);
|
||||
Instruction ins = (Instruction) sfi;
|
||||
|
||||
Method mOfSet = ins.getInstructions().getCode().getMethod();
|
||||
if (!mOfSet.getName().equals("<clinit>"))
|
||||
continue;
|
||||
|
||||
// get prev instruction and change to a constant value
|
||||
Instructions instructions = mOfSet.getCode().getInstructions();
|
||||
int idx = instructions.getInstructions().indexOf(ins);
|
||||
assert idx != -1;
|
||||
|
||||
Instruction prev = instructions.getInstructions().get(idx - 1);
|
||||
if (!(prev instanceof PushConstantInstruction))
|
||||
continue;
|
||||
|
||||
PushConstantInstruction pci = (PushConstantInstruction) prev;
|
||||
|
||||
constantValue = pci.getConstant();
|
||||
f.setValue(constantValue);
|
||||
|
||||
fields.add(f);
|
||||
|
||||
boolean b = instructions.getInstructions().remove(prev);
|
||||
assert b;
|
||||
b = instructions.getInstructions().remove(ins);
|
||||
assert b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int inlineUse()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (Field f : fields)
|
||||
{
|
||||
// replace getfield with constant push
|
||||
List<FieldInstruction> fins = fieldInstructions.get(f).stream().filter(f2 -> f2 instanceof GetFieldInstruction).collect(Collectors.toList());
|
||||
Object value = f.getValue();
|
||||
|
||||
for (FieldInstruction fin : fins)
|
||||
{
|
||||
// remove fin, add push constant
|
||||
Instruction i = (Instruction) fin;
|
||||
|
||||
Instruction pushIns = new LDC(i.getInstructions(), value);
|
||||
|
||||
List<Instruction> instructions = i.getInstructions().getInstructions();
|
||||
|
||||
int idx = instructions.indexOf(i);
|
||||
assert idx != -1;
|
||||
|
||||
i.getInstructions().remove(i);
|
||||
instructions.add(idx, pushIns);
|
||||
|
||||
++count;
|
||||
}
|
||||
|
||||
f.getClassFile().removeField(f);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
this.group = group;
|
||||
findFieldIns();
|
||||
makeConstantValues();
|
||||
int count = inlineUse();
|
||||
|
||||
logger.info("Inlined " + count + " fields");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instruction.types.ComparisonInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.JumpingInstruction;
|
||||
import net.runelite.asm.attributes.code.instructions.AThrow;
|
||||
import net.runelite.asm.attributes.code.instructions.Goto;
|
||||
import net.runelite.asm.attributes.code.instructions.If;
|
||||
import net.runelite.asm.attributes.code.instructions.New;
|
||||
import net.runelite.asm.execution.Execution;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
import net.runelite.asm.execution.MethodContext;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class IllegalStateExceptions implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(IllegalStateExceptions.class);
|
||||
|
||||
private int count;
|
||||
private Set<Instruction> interesting = new HashSet<>();
|
||||
private List<InstructionContext> toRemove = new ArrayList<>();
|
||||
|
||||
/* find if, new, ..., athrow, replace with goto */
|
||||
private void findInteresting(ClassGroup group)
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
Code c = m.getCode();
|
||||
if (c == null)
|
||||
continue;
|
||||
|
||||
Instructions instructions = c.getInstructions();
|
||||
|
||||
List<Instruction> ilist = instructions.getInstructions();
|
||||
for (int i = 0; i < ilist.size(); ++i)
|
||||
{
|
||||
Instruction ins = ilist.get(i);
|
||||
|
||||
if (!(ins instanceof ComparisonInstruction)) // the if
|
||||
continue;
|
||||
|
||||
Instruction ins2 = ilist.get(i + 1);
|
||||
if (!(ins2 instanceof New))
|
||||
continue;
|
||||
|
||||
New new2 = (New) ins2;
|
||||
net.runelite.asm.pool.Class clazz = new2.getNewClass();
|
||||
if (!clazz.getName().contains("java/lang/IllegalStateException"))
|
||||
continue;
|
||||
|
||||
interesting.add(ins);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void visit(InstructionContext ic)
|
||||
{
|
||||
if (interesting.contains(ic.getInstruction()))
|
||||
{
|
||||
toRemove.add(ic);
|
||||
}
|
||||
}
|
||||
|
||||
private void visit(MethodContext ctx)
|
||||
{
|
||||
for (InstructionContext ictx : toRemove)
|
||||
processOne(ictx);
|
||||
toRemove.clear();
|
||||
}
|
||||
|
||||
private void processOne(InstructionContext ic)
|
||||
{
|
||||
Instruction ins = ic.getInstruction();
|
||||
Instructions instructions = ins.getInstructions();
|
||||
|
||||
if (instructions == null)
|
||||
return;
|
||||
|
||||
List<Instruction> ilist = instructions.getInstructions();
|
||||
|
||||
JumpingInstruction jumpIns = (JumpingInstruction) ins;
|
||||
assert jumpIns.getJumps().size() == 1;
|
||||
Instruction to = jumpIns.getJumps().get(0);
|
||||
|
||||
// remove stack of if.
|
||||
if (ins instanceof If)
|
||||
{
|
||||
ic.removeStack(1);
|
||||
}
|
||||
ic.removeStack(0);
|
||||
|
||||
int i = ilist.indexOf(ins);
|
||||
assert i != -1;
|
||||
|
||||
// remove up to athrow
|
||||
while (!(ins instanceof AThrow))
|
||||
{
|
||||
instructions.remove(ins);
|
||||
ins = ilist.get(i); // don't need to ++i because
|
||||
}
|
||||
|
||||
// remove athrow
|
||||
instructions.remove(ins);
|
||||
|
||||
// insert goto
|
||||
assert ilist.contains(to);
|
||||
Goto g = new Goto(instructions, instructions.createLabelFor(to));
|
||||
ilist.add(i, g);
|
||||
|
||||
++count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
findInteresting(group);
|
||||
|
||||
Execution execution = new Execution(group);
|
||||
execution.addExecutionVisitor(i -> visit(i));
|
||||
execution.addMethodContextVisitor(i -> visit(i));
|
||||
execution.populateInitialMethods();
|
||||
execution.run();
|
||||
|
||||
logger.info("Removed " + count + " illegal state exceptions");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators;
|
||||
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import net.runelite.deob.deobfuscators.lvt.Mappings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This deobfuscator is only required for fernflower which has a difficult time
|
||||
* when the same lvt index is used for variables of differing types (like object
|
||||
* and int), see IDEABKL-7230.
|
||||
*
|
||||
* @author Adam
|
||||
*/
|
||||
public class Lvt implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(Lvt.class);
|
||||
|
||||
private int count = 0;
|
||||
|
||||
private void process(Method method)
|
||||
{
|
||||
Code code = method.getCode();
|
||||
if (code == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Mappings mappings = new Mappings(code.getMaxLocals());
|
||||
|
||||
for (Instruction ins : code.getInstructions().getInstructions())
|
||||
{
|
||||
if (!(ins instanceof LVTInstruction))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
LVTInstruction lv = (LVTInstruction) ins;
|
||||
Integer newIdx = mappings.remap(lv.getVariableIndex(), lv.type());
|
||||
|
||||
if (newIdx == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
assert newIdx != lv.getVariableIndex();
|
||||
|
||||
Instruction newIns = lv.setVariableIndex(newIdx);
|
||||
assert ins == newIns;
|
||||
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
process(m);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Remapped {} lvt indexes", count);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Annotations;
|
||||
import net.runelite.asm.execution.Execution;
|
||||
import net.runelite.deob.DeobAnnotations;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Sort fields and methods based first on the name order of the classes, and
|
||||
* then based on the order the executor encounters them.
|
||||
*
|
||||
* @author Adam
|
||||
*/
|
||||
public class Order implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(Order.class);
|
||||
|
||||
private Execution execution;
|
||||
|
||||
private final Map<String, Integer> nameIndices = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
execution = new Execution(group);
|
||||
execution.staticStep = true;
|
||||
execution.populateInitialMethods();
|
||||
execution.run();
|
||||
|
||||
for (int i = 0; i < group.getClasses().size(); i++)
|
||||
{
|
||||
ClassFile cf = group.getClasses().get(i);
|
||||
String className = DeobAnnotations.getObfuscatedName(cf.getAnnotations());
|
||||
nameIndices.put(className, i);
|
||||
}
|
||||
|
||||
int sortedMethods = 0, sortedFields = 0;
|
||||
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
List<Method> m = cf.getMethods();
|
||||
Collections.sort(m, this::compareMethod);
|
||||
|
||||
sortedMethods += m.size();
|
||||
|
||||
// field order of enums is mostly handled in EnumDeobfuscator
|
||||
if (!cf.isEnum())
|
||||
{
|
||||
List<Field> f = cf.getFields();
|
||||
Collections.sort(f, this::compareFields);
|
||||
|
||||
sortedFields += f.size();
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Sorted {} methods and {} fields", sortedMethods, sortedFields);
|
||||
}
|
||||
|
||||
// static fields, member fields, clinit, init, methods, static methods
|
||||
private int compareMethod(Method m1, Method m2)
|
||||
{
|
||||
int i1 = getType(m1), i2 = getType(m2);
|
||||
|
||||
if (i1 != i2)
|
||||
{
|
||||
return Integer.compare(i1, i2);
|
||||
}
|
||||
|
||||
int nameIdx1 = getNameIdx(m1.getAnnotations());
|
||||
int nameIdx2 = getNameIdx(m2.getAnnotations());
|
||||
|
||||
if (nameIdx1 != nameIdx2)
|
||||
{
|
||||
return Integer.compare(nameIdx1, nameIdx2);
|
||||
}
|
||||
|
||||
return compareOrder(m1, m2);
|
||||
}
|
||||
|
||||
private int compareFields(Field f1, Field f2)
|
||||
{
|
||||
int i1 = getType(f1), i2 = getType(f2);
|
||||
|
||||
if (i1 != i2)
|
||||
{
|
||||
return Integer.compare(i1, i2);
|
||||
}
|
||||
|
||||
int nameIdx1 = getNameIdx(f1.getAnnotations());
|
||||
int nameIdx2 = getNameIdx(f2.getAnnotations());
|
||||
|
||||
if (nameIdx1 != nameIdx2)
|
||||
{
|
||||
return Integer.compare(nameIdx1, nameIdx2);
|
||||
}
|
||||
|
||||
return compareOrder(f1, f2);
|
||||
}
|
||||
|
||||
private int getNameIdx(Annotations annotations)
|
||||
{
|
||||
String name = DeobAnnotations.getObfuscatedName(annotations);
|
||||
|
||||
Integer nameIdx = nameIndices.get(name);
|
||||
|
||||
return nameIdx != null ? nameIdx : -1;
|
||||
}
|
||||
|
||||
private int getType(Method m)
|
||||
{
|
||||
if (m.getName().equals("<clinit>"))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if (m.getName().equals("<init>"))
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
if (!m.isStatic())
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
|
||||
private int getType(Field f)
|
||||
{
|
||||
if (f.isStatic())
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
private int compareOrder(Object o1, Object o2)
|
||||
{
|
||||
Integer i1, i2;
|
||||
|
||||
i1 = execution.getOrder(o1);
|
||||
i2 = execution.getOrder(o2);
|
||||
|
||||
if (i1 == null)
|
||||
{
|
||||
i1 = Integer.MAX_VALUE;
|
||||
}
|
||||
if (i2 == null)
|
||||
{
|
||||
i2 = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
if (!i1.equals(i2))
|
||||
{
|
||||
return Integer.compare(i1, i2);
|
||||
}
|
||||
|
||||
// Fall back to number of accesses
|
||||
i1 = execution.getAccesses(o1);
|
||||
i2 = execution.getAccesses(o2);
|
||||
|
||||
if (i1 == null)
|
||||
{
|
||||
i1 = Integer.MAX_VALUE;
|
||||
}
|
||||
if (i2 == null)
|
||||
{
|
||||
i2 = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
return Integer.compare(i1, i2);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,693 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.Type;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.InstructionType;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.Label;
|
||||
import net.runelite.asm.attributes.code.instruction.types.ComparisonInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.GetFieldInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.JumpingInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.MappableInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.SetFieldInstruction;
|
||||
import net.runelite.asm.attributes.code.instructions.GetStatic;
|
||||
import net.runelite.asm.attributes.code.instructions.Goto;
|
||||
import net.runelite.asm.attributes.code.instructions.If;
|
||||
import net.runelite.asm.attributes.code.instructions.IfEq;
|
||||
import net.runelite.asm.attributes.code.instructions.IfICmpEq;
|
||||
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
|
||||
import net.runelite.asm.attributes.code.instructions.LDC;
|
||||
import net.runelite.asm.attributes.code.instructions.PutStatic;
|
||||
import net.runelite.asm.execution.Execution;
|
||||
import net.runelite.asm.execution.Frame;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
import net.runelite.asm.signature.Signature;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import net.runelite.deob.deobfuscators.transformers.buffer.BufferFinder;
|
||||
import net.runelite.deob.deobfuscators.packethandler.PacketLengthFinder;
|
||||
import net.runelite.deob.deobfuscators.packethandler.PacketRead;
|
||||
import net.runelite.deob.deobfuscators.packethandler.PacketTypeFinder;
|
||||
import static net.runelite.deob.deobfuscators.transformers.OpcodesTransformer.RUNELITE_OPCODES;
|
||||
import net.runelite.deob.s2c.HandlerFinder;
|
||||
import net.runelite.deob.s2c.PacketHandler;
|
||||
import net.runelite.deob.s2c.PacketHandlers;
|
||||
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
|
||||
import static org.objectweb.asm.Opcodes.ACC_STATIC;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class PacketHandlerOrder implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(PacketHandlerOrder.class);
|
||||
|
||||
private static final String RUNELITE_PACKET = "RUNELITE_PACKET";
|
||||
|
||||
private static final MessageDigest sha256;
|
||||
|
||||
static
|
||||
{
|
||||
try
|
||||
{
|
||||
sha256 = MessageDigest.getInstance("SHA-256");
|
||||
}
|
||||
catch (NoSuchAlgorithmException ex)
|
||||
{
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
// This is run on the deobfuscated jar, so there are no symbols yet...
|
||||
// Find packetType and buffer classes
|
||||
PacketTypeFinder ptf = new PacketTypeFinder(group);
|
||||
ptf.find();
|
||||
|
||||
BufferFinder bf = new BufferFinder(group);
|
||||
bf.find();
|
||||
|
||||
HandlerFinder hf = new HandlerFinder(group, ptf.getPacketType());
|
||||
PacketHandlers handlers = hf.findHandlers();
|
||||
|
||||
logger.info("Found {} packet handlers", handlers.getHandlers().size());
|
||||
|
||||
for (PacketHandler handler : handlers.getHandlers())
|
||||
{
|
||||
Execution e = hf.getExecution();
|
||||
e.reset();
|
||||
|
||||
e.staticStep = true;
|
||||
e.step = false;
|
||||
e.noInvoke = true;
|
||||
// exception processing won't do non-local jumps, so
|
||||
// depending on whether methods are inlined or not
|
||||
// it may jump completely out of the handler into the
|
||||
// catch all for all packet handling
|
||||
// just disable exception execution
|
||||
e.noExceptions = true;
|
||||
|
||||
assert e.frames.isEmpty();
|
||||
|
||||
Frame f = handler.jumpFrame.dup();
|
||||
assert f.isExecuting();
|
||||
f.getMethodCtx().reset();
|
||||
|
||||
e.clearExecutionVisitor();
|
||||
e.addExecutionVisitor(ictx ->
|
||||
{
|
||||
if (ictx.getInstruction() instanceof MappableInstruction)
|
||||
{
|
||||
if (ictx.getInstruction().getType() != InstructionType.INVOKESTATIC)
|
||||
{
|
||||
if (!handler.mappable.contains(ictx.getInstruction()))
|
||||
{
|
||||
handler.mappable.add(ictx.getInstruction());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ictx.getInstruction().getType() == InstructionType.INVOKEVIRTUAL)
|
||||
{
|
||||
InvokeInstruction ii = (InvokeInstruction) ictx.getInstruction();
|
||||
|
||||
// check if the invoke is on buffer/packetbuffer classes
|
||||
boolean matches = ii.getMethods().stream()
|
||||
.filter(m -> m.getDescriptor().size() == 0)
|
||||
.map(method -> method.getClassFile())
|
||||
.anyMatch(cf -> cf == bf.getBuffer() || cf == bf.getPacketBuffer());
|
||||
if (matches)
|
||||
{
|
||||
Method method = ii.getMethods().get(0);
|
||||
Signature signature = method.getDescriptor();
|
||||
Type returnValue = signature.getReturnValue();
|
||||
|
||||
assert ictx.getPops().size() == 1; // buffer reference
|
||||
InstructionContext bufferCtx = ictx.getPops().get(0).getPushed();
|
||||
if (bufferCtx.getInstruction().getType() != InstructionType.GETSTATIC)
|
||||
{
|
||||
return; // sometimes buffer is passed to a function and then invoked.
|
||||
}
|
||||
PacketRead packetRead = new PacketRead(returnValue, bufferCtx.getInstruction(), ictx);
|
||||
|
||||
if (!handler.reads.contains(packetRead))
|
||||
{
|
||||
handler.reads.add(packetRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ictx.getInstruction().getType() == InstructionType.INVOKEVIRTUAL
|
||||
|| ictx.getInstruction().getType() == InstructionType.INVOKESPECIAL
|
||||
|| ictx.getInstruction().getType() == InstructionType.INVOKEINTERFACE)
|
||||
{
|
||||
InvokeInstruction ii = (InvokeInstruction) ictx.getInstruction();
|
||||
// read methods are scrambled so cant count them
|
||||
if (!handler.hasPacketRead(ictx.getInstruction()))
|
||||
{
|
||||
handler.methodInvokes.addAll(ii.getMethods());
|
||||
}
|
||||
}
|
||||
if (ictx.getInstruction() instanceof SetFieldInstruction)
|
||||
{
|
||||
SetFieldInstruction sfi = (SetFieldInstruction) ictx.getInstruction();
|
||||
Field field = sfi.getMyField();
|
||||
if (field != null)
|
||||
{
|
||||
handler.fieldWrite.add(field);
|
||||
}
|
||||
}
|
||||
if (ictx.getInstruction() instanceof GetFieldInstruction)
|
||||
{
|
||||
GetFieldInstruction gfi = (GetFieldInstruction) ictx.getInstruction();
|
||||
Field field = gfi.getMyField();
|
||||
if (field != null)
|
||||
{
|
||||
handler.fieldRead.add(field);
|
||||
}
|
||||
}
|
||||
if (ictx.getInstruction() instanceof LVTInstruction)
|
||||
{
|
||||
LVTInstruction lvt = (LVTInstruction) ictx.getInstruction();
|
||||
if (!lvt.store())
|
||||
{
|
||||
// get lvt access order
|
||||
Frame frame = ictx.getFrame();
|
||||
int order = frame.getNextOrder();
|
||||
if (!handler.lvtOrder.containsKey(lvt.getVariableIndex()))
|
||||
{
|
||||
handler.lvtOrder.put(lvt.getVariableIndex(), order);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ictx.getInstruction() instanceof PushConstantInstruction)
|
||||
{
|
||||
PushConstantInstruction pci = (PushConstantInstruction) ictx.getInstruction();
|
||||
handler.constants.add(pci.getConstant());
|
||||
}
|
||||
});
|
||||
|
||||
logger.debug("Beginning execution of opcode {}", handler.getOpcode());
|
||||
|
||||
e.run();
|
||||
|
||||
logger.info("Executed opcode {}: {} mappable instructions", handler.getOpcode(), handler.mappable.size());
|
||||
|
||||
handler.findReorderableReads();
|
||||
}
|
||||
|
||||
List<PacketHandler> unsortedHandlers = new ArrayList<>(handlers.getHandlers());
|
||||
List<PacketHandler> sortedHandlers = new ArrayList<>(handlers.getHandlers()).stream()
|
||||
.sorted((PacketHandler p1, PacketHandler p2) ->
|
||||
{
|
||||
int c = compareReads(p1.reads, p2.reads);
|
||||
if (c != 0)
|
||||
{
|
||||
return c;
|
||||
}
|
||||
if (p1.methodInvokes.size() != p2.methodInvokes.size())
|
||||
{
|
||||
return Integer.compare(p1.methodInvokes.size(), p2.methodInvokes.size());
|
||||
}
|
||||
if (p1.fieldRead.size() != p2.fieldRead.size())
|
||||
{
|
||||
return Integer.compare(p1.fieldRead.size(), p2.fieldRead.size());
|
||||
}
|
||||
if (p1.fieldWrite.size() != p2.fieldWrite.size())
|
||||
{
|
||||
return Integer.compare(p1.fieldWrite.size(), p2.fieldWrite.size());
|
||||
}
|
||||
int i = Integer.compare(p1.mappable.size(), p2.mappable.size());
|
||||
if (i != 0)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
|
||||
int s1 = hashConstants(p1.constants), s2 = hashConstants(p2.constants);
|
||||
if (s1 != s2)
|
||||
{
|
||||
return Integer.compare(s1, s2);
|
||||
}
|
||||
|
||||
logger.warn("Unable to differentiate {} from {}", p1, p2);
|
||||
return 0;
|
||||
})
|
||||
.map(s -> s.clone())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
assert sortedHandlers.size() == handlers.getHandlers().size();
|
||||
|
||||
for (PacketHandler handler : sortedHandlers)
|
||||
{
|
||||
handler.sortedReads = new ArrayList<>(handler.reads);
|
||||
Collections.sort(handler.sortedReads, (PacketRead p1, PacketRead p2) ->
|
||||
{
|
||||
LVTInstruction l1 = (LVTInstruction) p1.getStore();
|
||||
LVTInstruction l2 = (LVTInstruction) p2.getStore();
|
||||
|
||||
if (l1 == null && l2 == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (l1 == null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if (l2 == null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (l1.getVariableIndex() == l2.getVariableIndex())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
Integer i1 = handler.lvtOrder.get(l1.getVariableIndex());
|
||||
Integer i2 = handler.lvtOrder.get(l2.getVariableIndex());
|
||||
assert i1 != null;
|
||||
assert i2 != null;
|
||||
int i = Integer.compare(i1, i2);
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
logger.warn("Cannot differentiate {} from {}", p1, p2);
|
||||
}
|
||||
|
||||
return i;
|
||||
});
|
||||
Collections.reverse(handler.sortedReads);
|
||||
}
|
||||
|
||||
ClassFile runeliteOpcodes = group.findClass(RUNELITE_OPCODES);
|
||||
assert runeliteOpcodes != null : "Opcodes class must exist";
|
||||
|
||||
for (PacketHandler handler : sortedHandlers)
|
||||
{
|
||||
logger.info("Handler {} mappable {} reads {} invokes {} freads {} fwrites {}",
|
||||
handler.getOpcode(), handler.mappable.size(), handler.reads.size(), handler.methodInvokes.size(),
|
||||
handler.fieldRead.size(), handler.fieldWrite.size());
|
||||
|
||||
final String fieldName = "PACKET_SERVER_" + handler.getOpcode();
|
||||
|
||||
// Add opcode fields
|
||||
if (runeliteOpcodes.findField(fieldName) == null)
|
||||
{
|
||||
Field opField = new Field(runeliteOpcodes, fieldName, Type.INT);
|
||||
// ACC_FINAL causes javac to inline the fields, which prevents
|
||||
// the mapper from doing field mapping
|
||||
opField.setAccessFlags(ACC_PUBLIC | ACC_STATIC);
|
||||
// setting a non-final static field value
|
||||
// doesn't work with fernflower
|
||||
opField.setValue(handler.getOpcode());
|
||||
runeliteOpcodes.addField(opField);
|
||||
|
||||
// add initialization
|
||||
Method clinit = runeliteOpcodes.findMethod("<clinit>");
|
||||
assert clinit != null;
|
||||
Instructions instructions = clinit.getCode().getInstructions();
|
||||
instructions.addInstruction(0, new LDC(instructions, handler.getOpcode()));
|
||||
instructions.addInstruction(1, new PutStatic(instructions, opField));
|
||||
}
|
||||
}
|
||||
|
||||
// Find unique methods
|
||||
List<Method> methods = unsortedHandlers.stream()
|
||||
.map(ph -> ph.getMethod())
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Method m : methods)
|
||||
{
|
||||
List<PacketHandler> unsortedMethodHandlers = unsortedHandlers.stream()
|
||||
.filter(ph -> ph.getMethod() == m)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<PacketHandler> sortedMethodHandlers = sortedHandlers.stream()
|
||||
.filter(ph -> ph.getMethod() == m)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
assert unsortedMethodHandlers.size() == sortedMethodHandlers.size();
|
||||
|
||||
for (int i = 0; i < sortedMethodHandlers.size(); ++i)
|
||||
{
|
||||
PacketHandler unsorted = unsortedMethodHandlers.get(i);
|
||||
PacketHandler sortedh = sortedMethodHandlers.get(i);
|
||||
|
||||
// Set opcode/jump from sorted -> unsorted
|
||||
If jump = (If) unsorted.getJump();
|
||||
PushConstantInstruction pci = (PushConstantInstruction) unsorted.getPush();
|
||||
|
||||
assert unsorted.getOpcode() == ((Number) pci.getConstant()).intValue();
|
||||
|
||||
Instructions instructions = unsorted.getMethod().getCode().getInstructions();
|
||||
|
||||
final String fieldName = "PACKET_SERVER_" + sortedh.getOpcode();
|
||||
|
||||
net.runelite.asm.pool.Field field = new net.runelite.asm.pool.Field(
|
||||
new net.runelite.asm.pool.Class(RUNELITE_OPCODES),
|
||||
fieldName,
|
||||
Type.INT
|
||||
);
|
||||
instructions.replace(unsorted.getPush(), new GetStatic(instructions, field));
|
||||
|
||||
assert jump.getType() == InstructionType.IF_ICMPEQ || jump.getType() == InstructionType.IF_ICMPNE;
|
||||
|
||||
Label startLabel = instructions.createLabelFor(sortedh.getStart());
|
||||
|
||||
if (jump.getType() == InstructionType.IF_ICMPEQ)
|
||||
{
|
||||
instructions.replace(jump, new IfICmpEq(instructions, startLabel));
|
||||
}
|
||||
else if (jump.getType() == InstructionType.IF_ICMPNE)
|
||||
{
|
||||
// insert a jump after to go to sortedh start
|
||||
int idx = instructions.getInstructions().indexOf(jump);
|
||||
assert idx != -1;
|
||||
|
||||
instructions.addInstruction(idx + 1, new Goto(instructions, startLabel));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
insertSortedReads(group, sortedHandlers);
|
||||
insertPacketLength(group, ptf);
|
||||
}
|
||||
|
||||
private void insertSortedReads(ClassGroup group, List<PacketHandler> handlers)
|
||||
{
|
||||
outer:
|
||||
for (PacketHandler handler : handlers)
|
||||
{
|
||||
Method method = handler.getMethod();
|
||||
Instructions instructions = method.getCode().getInstructions();
|
||||
List<Instruction> ins = instructions.getInstructions();
|
||||
|
||||
Instruction afterRead = handler.getAfterRead();
|
||||
if (afterRead == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (PacketRead read : handler.reads)
|
||||
{
|
||||
if (read.getStore() == null)
|
||||
{
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
|
||||
List<Instruction> follow = follow(instructions, handler.getStart(), afterRead);
|
||||
if (follow == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Instruction i : follow)
|
||||
{
|
||||
if (i instanceof ComparisonInstruction)
|
||||
{
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
|
||||
Label afterReadLabel = instructions.createLabelFor(afterRead);
|
||||
|
||||
int idx = ins.indexOf(handler.getStart());
|
||||
assert idx != -1;
|
||||
|
||||
if (handler.getStart() instanceof Label)
|
||||
{
|
||||
++idx;
|
||||
}
|
||||
|
||||
net.runelite.asm.pool.Field field = new net.runelite.asm.pool.Field(
|
||||
new net.runelite.asm.pool.Class(findClient(group).getName()),
|
||||
RUNELITE_PACKET,
|
||||
Type.BOOLEAN
|
||||
);
|
||||
|
||||
instructions.addInstruction(idx, new GetStatic(instructions, field));
|
||||
++idx;
|
||||
|
||||
instructions.addInstruction(idx, new IfEq(instructions, instructions.createLabelFor(ins.get(idx))));
|
||||
++idx;
|
||||
|
||||
List<Instruction> toCopy = new ArrayList<>();
|
||||
for (Instruction i : follow)
|
||||
{
|
||||
assert !(i instanceof JumpingInstruction);
|
||||
if (i instanceof Label)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
toCopy.add(i);
|
||||
}
|
||||
|
||||
// Remove getstatic/invoke/store for packet reads
|
||||
for (PacketRead read : handler.reads)
|
||||
{
|
||||
boolean b = toCopy.remove(read.getGetBuffer());
|
||||
assert b;
|
||||
b = toCopy.remove(read.getInvoke());
|
||||
assert b;
|
||||
b = toCopy.remove(read.getStore());
|
||||
assert b;
|
||||
}
|
||||
|
||||
// Add sorted reads
|
||||
for (PacketRead read : handler.sortedReads)
|
||||
{
|
||||
toCopy.add(0, read.getGetBuffer());
|
||||
|
||||
// replace invoke instruction with typed read
|
||||
InvokeInstruction ii = (InvokeInstruction) read.getInvoke();
|
||||
net.runelite.asm.pool.Method invokeMethod;
|
||||
if (read.getType().equals(Type.BYTE))
|
||||
{
|
||||
invokeMethod = new net.runelite.asm.pool.Method(
|
||||
ii.getMethod().getClazz(),
|
||||
"runeliteReadByte",
|
||||
new Signature("()B")
|
||||
);
|
||||
}
|
||||
else if (read.getType().equals(Type.SHORT))
|
||||
{
|
||||
invokeMethod = new net.runelite.asm.pool.Method(
|
||||
ii.getMethod().getClazz(),
|
||||
"runeliteReadShort",
|
||||
new Signature("()S")
|
||||
);
|
||||
}
|
||||
else if (read.getType().equals(Type.INT))
|
||||
{
|
||||
invokeMethod = new net.runelite.asm.pool.Method(
|
||||
ii.getMethod().getClazz(),
|
||||
"runeliteReadInt",
|
||||
new Signature("()I")
|
||||
);
|
||||
}
|
||||
else if (read.getType().equals(Type.STRING))
|
||||
{
|
||||
invokeMethod = new net.runelite.asm.pool.Method(
|
||||
ii.getMethod().getClazz(),
|
||||
"runeliteReadString",
|
||||
new Signature("()Ljava/lang/String;")
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new UnsupportedOperationException("Unknown type " + read.getType());
|
||||
}
|
||||
toCopy.add(1, new InvokeVirtual(instructions, invokeMethod));
|
||||
|
||||
toCopy.add(2, read.getStore());
|
||||
}
|
||||
|
||||
for (Instruction i : toCopy)
|
||||
{
|
||||
instructions.addInstruction(idx++, i.clone());
|
||||
}
|
||||
|
||||
instructions.addInstruction(idx, new Goto(instructions, afterReadLabel));
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
|
||||
private int compareReads(List<PacketRead> r1, List<PacketRead> r2)
|
||||
{
|
||||
List<Type> t1 = r1.stream()
|
||||
.map(pr -> pr.getType())
|
||||
.sorted(this::compareType)
|
||||
.collect(Collectors.toList());
|
||||
List<Type> t2 = r2.stream()
|
||||
.map(pr -> pr.getType())
|
||||
.sorted(this::compareType)
|
||||
.collect(Collectors.toList());
|
||||
if (t1.size() != t2.size())
|
||||
{
|
||||
return Integer.compare(t1.size(), t2.size());
|
||||
}
|
||||
|
||||
for (int i = 0; i < t1.size(); ++i)
|
||||
{
|
||||
Type type1 = t1.get(i), type2 = t2.get(i);
|
||||
|
||||
int cmp = compareType(type1, type2);
|
||||
if (cmp != 0)
|
||||
{
|
||||
return cmp;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int compareType(Type t1, Type t2)
|
||||
{
|
||||
if (t1.getDimensions() != t2.getDimensions())
|
||||
{
|
||||
return Integer.compare(t1.getDimensions(), t2.getDimensions());
|
||||
}
|
||||
return t1.toString().compareTo(t2.toString());
|
||||
}
|
||||
|
||||
public static int hashConstants(List<Object> constants)
|
||||
{
|
||||
sha256.reset();
|
||||
for (Object o : constants)
|
||||
{
|
||||
if (o instanceof Number)
|
||||
{
|
||||
sha256.update(((Number) o).byteValue());
|
||||
}
|
||||
else if (o instanceof String)
|
||||
{
|
||||
String s = (String) o;
|
||||
sha256.update(s.getBytes());
|
||||
}
|
||||
}
|
||||
byte[] b = sha256.digest();
|
||||
return Ints.fromByteArray(b);
|
||||
}
|
||||
|
||||
private void insertPacketLength(ClassGroup group, PacketTypeFinder ptf)
|
||||
{
|
||||
PacketLengthFinder pfl = new PacketLengthFinder(group, ptf);
|
||||
pfl.find();
|
||||
|
||||
GetStatic getArray = pfl.getGetArray();
|
||||
PutStatic ps = pfl.getStore(); // instruction to store packet length
|
||||
|
||||
Instructions instructions = ps.getInstructions();
|
||||
List<Instruction> ins = instructions.getInstructions();
|
||||
|
||||
Label getArrayLabel = instructions.createLabelFor(getArray);
|
||||
Label storeLabel = instructions.createLabelFor(ps);
|
||||
|
||||
int idx = ins.indexOf(getArray);
|
||||
assert idx != -1;
|
||||
|
||||
--idx; // to go before label, which must exist
|
||||
|
||||
net.runelite.asm.pool.Field field = new net.runelite.asm.pool.Field(
|
||||
new net.runelite.asm.pool.Class(findClient(group).getName()),
|
||||
RUNELITE_PACKET,
|
||||
Type.BOOLEAN
|
||||
);
|
||||
|
||||
instructions.addInstruction(idx++, new GetStatic(instructions, field));
|
||||
instructions.addInstruction(idx++, new IfEq(instructions, getArrayLabel));
|
||||
instructions.addInstruction(idx++, new LDC(instructions, -2)); // 2 byte length
|
||||
instructions.addInstruction(idx++, new Goto(instructions, storeLabel));
|
||||
}
|
||||
|
||||
private ClassFile findClient(ClassGroup group)
|
||||
{
|
||||
// "client" in vainlla but "Client" in deob..
|
||||
ClassFile cf = group.findClass("client");
|
||||
return cf != null ? cf : group.findClass("Client");
|
||||
}
|
||||
|
||||
private List<Instruction> follow(Instructions instructions, Instruction start, Instruction end)
|
||||
{
|
||||
List<Instruction> list = new ArrayList<>();
|
||||
|
||||
int idx = instructions.getInstructions().indexOf(start);
|
||||
assert idx != -1;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
Instruction i = instructions.getInstructions().get(idx);
|
||||
|
||||
// end is the following instruction post read.. not included
|
||||
if (i == end)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (i instanceof Goto)
|
||||
{
|
||||
Goto g = (Goto) i;
|
||||
Label to = g.getTo();
|
||||
|
||||
idx = instructions.getInstructions().indexOf(to);
|
||||
assert idx != -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
list.add(i);
|
||||
|
||||
if (i instanceof ComparisonInstruction)
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
++idx;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators;
|
||||
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.signature.util.VirtualMethods;
|
||||
import net.runelite.deob.Deob;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import net.runelite.deob.util.NameMappings;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class RenameUnique implements Deobfuscator
|
||||
{
|
||||
private Renamer renamer;
|
||||
|
||||
private void generateClassNames(NameMappings map, ClassGroup group)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
if (cf.getName().length() > Deob.OBFUSCATED_NAME_MAX_LEN)
|
||||
continue;
|
||||
|
||||
map.map(cf.getPoolClass(), "class" + i++);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateFieldNames(NameMappings map, ClassGroup group)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for (ClassFile cf : group.getClasses())
|
||||
for (Field field : cf.getFields())
|
||||
{
|
||||
if (field.getName().length() > Deob.OBFUSCATED_NAME_MAX_LEN)
|
||||
continue;
|
||||
|
||||
map.map(field.getPoolField(), "field" + i++);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateMethodNames(NameMappings map, ClassGroup group)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for (ClassFile cf : group.getClasses())
|
||||
for (Method method : cf.getMethods())
|
||||
{
|
||||
if (method.getName().length() > Deob.OBFUSCATED_NAME_MAX_LEN)
|
||||
continue;
|
||||
|
||||
List<Method> virtualMethods = VirtualMethods.getVirtualMethods(method);
|
||||
assert !virtualMethods.isEmpty();
|
||||
|
||||
String name;
|
||||
if (virtualMethods.size() == 1)
|
||||
name = "method" + i++;
|
||||
else
|
||||
name = "vmethod" + i++;
|
||||
|
||||
for (Method m : virtualMethods)
|
||||
map.map(m.getPoolMethod(), name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
group.buildClassGraph();
|
||||
group.lookup();
|
||||
|
||||
NameMappings mappings = new NameMappings();
|
||||
|
||||
this.generateClassNames(mappings, group);
|
||||
this.generateFieldNames(mappings, group);
|
||||
this.generateMethodNames(mappings, group);
|
||||
|
||||
renamer = new Renamer(mappings);
|
||||
renamer.run(group);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import java.util.List;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Interfaces;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.Type;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.annotation.Annotation;
|
||||
import net.runelite.asm.attributes.code.Exceptions;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.LocalVariable;
|
||||
import net.runelite.asm.attributes.code.Parameter;
|
||||
import net.runelite.asm.signature.Signature;
|
||||
import net.runelite.asm.signature.util.VirtualMethods;
|
||||
import net.runelite.deob.DeobAnnotations;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import net.runelite.deob.util.NameMappings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Renamer implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(Renamer.class);
|
||||
|
||||
private final NameMappings mappings;
|
||||
|
||||
public Renamer(NameMappings mappings)
|
||||
{
|
||||
this.mappings = mappings;
|
||||
}
|
||||
|
||||
private void renameClass(ClassFile on, ClassFile old, String name)
|
||||
{
|
||||
if (on.getParentClass().getName().equals(old.getName()))
|
||||
{
|
||||
on.setParentClass(new net.runelite.asm.pool.Class(name));
|
||||
}
|
||||
|
||||
Interfaces interfaces = on.getInterfaces();
|
||||
List<net.runelite.asm.pool.Class> interfaceList = interfaces.getInterfaces();
|
||||
for (net.runelite.asm.pool.Class inter : interfaceList)
|
||||
{
|
||||
if (inter.getName().equals(old.getName()))
|
||||
{
|
||||
int idx = interfaceList.indexOf(inter);
|
||||
interfaceList.remove(idx);
|
||||
interfaceList.add(idx, new net.runelite.asm.pool.Class(name));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void renameClass(ClassGroup group, ClassFile cf, String name)
|
||||
{
|
||||
for (ClassFile c : group.getClasses())
|
||||
{
|
||||
// rename on child interfaces and classes
|
||||
renameClass(c, cf, name);
|
||||
|
||||
for (Method method : c.getMethods())
|
||||
{
|
||||
// rename on instructions. this includes method calls and field accesses.
|
||||
if (method.getCode() != null)
|
||||
{
|
||||
Code code = method.getCode();
|
||||
|
||||
// rename on instructions
|
||||
for (Instruction i : code.getInstructions().getInstructions())
|
||||
{
|
||||
i.renameClass(cf.getName(), name);
|
||||
}
|
||||
|
||||
// rename on exception handlers
|
||||
Exceptions exceptions = code.getExceptions();
|
||||
exceptions.renameClass(cf, name);
|
||||
}
|
||||
|
||||
// rename on parameters
|
||||
Signature.Builder builder = new Signature.Builder();
|
||||
Signature signature = method.getDescriptor();
|
||||
for (int i = 0; i < signature.size(); ++i)
|
||||
{
|
||||
Type type = signature.getTypeOfArg(i);
|
||||
|
||||
if (type.getInternalName().equals(cf.getName()))
|
||||
{
|
||||
builder.addArgument(Type.getType("L" + name + ";", type.getDimensions()));
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.addArgument(type);
|
||||
}
|
||||
}
|
||||
|
||||
// rename return type
|
||||
if (signature.getReturnValue().getInternalName().equals(cf.getName()))
|
||||
{
|
||||
builder.setReturnType(Type.getType("L" + name + ";", signature.getReturnValue().getDimensions()));
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.setReturnType(signature.getReturnValue());
|
||||
}
|
||||
|
||||
Signature newSignature = builder.build();
|
||||
|
||||
if (!method.getDescriptor().equals(newSignature))
|
||||
{
|
||||
//Signature was updated. Annotate it
|
||||
if (method.getAnnotations().find(DeobAnnotations.OBFUSCATED_SIGNATURE) == null)
|
||||
{
|
||||
//Signature was not previously renamed
|
||||
method.getAnnotations().addAnnotation(DeobAnnotations.OBFUSCATED_SIGNATURE, "signature", method.getDescriptor().toString());
|
||||
}
|
||||
}
|
||||
|
||||
method.setDescriptor(newSignature);
|
||||
|
||||
// rename on exceptions thrown
|
||||
if (method.getExceptions() != null)
|
||||
{
|
||||
method.getExceptions().renameClass(cf, name);
|
||||
}
|
||||
}
|
||||
|
||||
// rename on fields
|
||||
for (Field field : c.getFields())
|
||||
{
|
||||
if (field.getType().getInternalName().equals(cf.getName()))
|
||||
{
|
||||
if (field.getAnnotations().find(DeobAnnotations.OBFUSCATED_SIGNATURE) == null)
|
||||
{
|
||||
//Signature was updated. Annotate it
|
||||
field.getAnnotations().addAnnotation(DeobAnnotations.OBFUSCATED_SIGNATURE, "signature", field.getType().toString());
|
||||
}
|
||||
|
||||
field.setType(Type.getType("L" + name + ";", field.getType().getDimensions()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cf.getAnnotations().find(DeobAnnotations.OBFUSCATED_NAME) == null)
|
||||
{
|
||||
cf.getAnnotations().addAnnotation(DeobAnnotations.OBFUSCATED_NAME, "value", cf.getName());
|
||||
}
|
||||
|
||||
group.renameClass(cf, name);
|
||||
}
|
||||
|
||||
private void regeneratePool(ClassGroup group)
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
Code c = m.getCode();
|
||||
if (c == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
c.getInstructions().regeneratePool();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
group.buildClassGraph();
|
||||
group.lookup();
|
||||
|
||||
int classes = 0, fields = 0, methods = 0, parameters = 0;
|
||||
|
||||
// rename fields
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Field field : cf.getFields())
|
||||
{
|
||||
String newName = mappings.get(field.getPoolField());
|
||||
if (newName == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.getAnnotations().find(DeobAnnotations.OBFUSCATED_NAME) == null)
|
||||
{
|
||||
field.getAnnotations().addAnnotation(DeobAnnotations.OBFUSCATED_NAME, "value", field.getName());
|
||||
}
|
||||
|
||||
field.setName(newName);
|
||||
++fields;
|
||||
}
|
||||
}
|
||||
|
||||
// rename methods
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method method : cf.getMethods())
|
||||
{
|
||||
String newName = mappings.get(method.getPoolMethod());
|
||||
String[] newParams = mappings.getP(method.getPoolMethod());
|
||||
|
||||
// rename on obfuscated signature
|
||||
Annotation an = method.getAnnotations().find(DeobAnnotations.OBFUSCATED_SIGNATURE);
|
||||
if (an != null)
|
||||
{
|
||||
Signature obfuscatedSig = new Signature(an.getElement().getString());
|
||||
Signature updatedSig = renameSignature(obfuscatedSig);
|
||||
an.getElement().setValue(updatedSig.toString());
|
||||
}
|
||||
|
||||
if (newName == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
List<Method> virtualMethods = VirtualMethods.getVirtualMethods(method);
|
||||
assert !virtualMethods.isEmpty();
|
||||
|
||||
for (Method m : virtualMethods)
|
||||
{
|
||||
List<Parameter> oldParams = method.getParameters();
|
||||
for (Parameter p : oldParams)
|
||||
{
|
||||
int index = oldParams.indexOf(p);
|
||||
if (newParams == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (newParams.length < 1 || index > newParams.length - 1 || Strings.isNullOrEmpty(newParams[index]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
LocalVariable oldVar = p.getLocalVariable();
|
||||
LocalVariable newVar = new LocalVariable(
|
||||
newParams[index],
|
||||
oldVar.getDesc(),
|
||||
oldVar.getSignature(),
|
||||
oldVar.getStart(),
|
||||
oldVar.getEnd(),
|
||||
oldVar.getIndex()
|
||||
);
|
||||
|
||||
p.setLocalVariable(newVar);
|
||||
|
||||
++parameters;
|
||||
}
|
||||
|
||||
if (m.getAnnotations().find(DeobAnnotations.OBFUSCATED_NAME) == null)
|
||||
{
|
||||
m.getAnnotations().addAnnotation(DeobAnnotations.OBFUSCATED_NAME, "value", m.getName());
|
||||
}
|
||||
|
||||
m.setName(newName);
|
||||
}
|
||||
|
||||
methods += virtualMethods.size();
|
||||
}
|
||||
}
|
||||
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
String newName = mappings.get(cf.getPoolClass());
|
||||
if (newName == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
renameClass(group, cf, newName);
|
||||
++classes;
|
||||
}
|
||||
|
||||
this.regeneratePool(group);
|
||||
|
||||
logger.info("Renamed {} classes, {} fields, {} methods, and {} parameters", classes, fields, methods, parameters);
|
||||
}
|
||||
|
||||
private Type renameType(Type t)
|
||||
{
|
||||
if (t.isPrimitive())
|
||||
{
|
||||
return t;
|
||||
}
|
||||
|
||||
String className = t.getInternalName();
|
||||
String newName = mappings.get(new net.runelite.asm.pool.Class(className));
|
||||
if (newName == null)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
|
||||
Type type = Type.getType("L" + newName + ";", t.getDimensions());
|
||||
|
||||
logger.debug("Renamed {} -> {}", t, type);
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
private Signature renameSignature(Signature s)
|
||||
{
|
||||
Signature.Builder builder = new Signature.Builder()
|
||||
.setReturnType(renameType(s.getReturnValue()));
|
||||
for (Type t : s.getArguments())
|
||||
{
|
||||
builder.addArgument(renameType(t));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class RuntimeExceptions implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(RuntimeExceptions.class);
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
boolean foundInit = false;
|
||||
|
||||
int i = 0;
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
Code c = m.getCode();
|
||||
if (c == null)
|
||||
continue;
|
||||
|
||||
// Keep one handler in the client so the deobfuscator
|
||||
// keeps the client error handling related methods
|
||||
if (cf.getName().equals("client") && m.getName().equals("init"))
|
||||
{
|
||||
foundInit = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (net.runelite.asm.attributes.code.Exception e : new ArrayList<>(c.getExceptions().getExceptions()))
|
||||
{
|
||||
if (e.getCatchType() != null && e.getCatchType().getName().equals("java/lang/RuntimeException"))
|
||||
{
|
||||
c.getExceptions().remove(e);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundInit)
|
||||
{
|
||||
throw new IllegalStateException("client.init(...) method seems to be missing!");
|
||||
}
|
||||
|
||||
logger.info("Remove {} exception handlers", i);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.Label;
|
||||
import net.runelite.asm.execution.Execution;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class UnreachedCode implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(UnreachedCode.class);
|
||||
|
||||
private Execution execution;
|
||||
|
||||
private int removeUnused(Method m)
|
||||
{
|
||||
Instructions ins = m.getCode().getInstructions();
|
||||
|
||||
int count = 0;
|
||||
List<Instruction> insCopy = new ArrayList<>(ins.getInstructions());
|
||||
|
||||
for (int j = 0; j < insCopy.size(); ++j)
|
||||
{
|
||||
Instruction i = insCopy.get(j);
|
||||
|
||||
if (!execution.executed.contains(i))
|
||||
{
|
||||
// if this is an exception handler, the exception handler is never used...
|
||||
for (net.runelite.asm.attributes.code.Exception e : new ArrayList<>(m.getCode().getExceptions().getExceptions()))
|
||||
{
|
||||
if (e.getStart().next() == i)
|
||||
{
|
||||
e.setStart(ins.createLabelFor(insCopy.get(j + 1)));
|
||||
|
||||
if (e.getStart().next() == e.getEnd().next())
|
||||
{
|
||||
m.getCode().getExceptions().remove(e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (e.getHandler().next() == i)
|
||||
{
|
||||
m.getCode().getExceptions().remove(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (i instanceof Label)
|
||||
continue;
|
||||
|
||||
ins.remove(i);
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
group.buildClassGraph();
|
||||
|
||||
execution = new Execution(group);
|
||||
execution.populateInitialMethods();
|
||||
execution.run();
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
if (m.getCode() == null)
|
||||
continue;
|
||||
|
||||
count += removeUnused(m);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Removed {} unused instructions", count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class UnusedClass implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(UnusedClass.class);
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
int count = 0;
|
||||
for (ClassFile cf : new ArrayList<>(group.getClasses()))
|
||||
{
|
||||
if (!cf.getFields().isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cf.getMethods().isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isImplemented(group, cf))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
group.removeClass(cf);
|
||||
++count;
|
||||
}
|
||||
|
||||
logger.info("Removed {} classes", count);
|
||||
}
|
||||
|
||||
private boolean isImplemented(ClassGroup group, ClassFile iface)
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
if (cf.getInterfaces().getMyInterfaces().contains(iface))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.FieldInstruction;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class UnusedFields implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(UnusedFields.class);
|
||||
|
||||
private final Set<Field> used = new HashSet<>();
|
||||
|
||||
private void checkForFieldUsage(ClassGroup group)
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
Code code = m.getCode();
|
||||
if (code == null)
|
||||
continue;
|
||||
|
||||
for (Instruction ins : code.getInstructions().getInstructions())
|
||||
{
|
||||
if (ins instanceof FieldInstruction)
|
||||
{
|
||||
FieldInstruction fi = (FieldInstruction) ins;
|
||||
|
||||
used.add(fi.getMyField());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
checkForFieldUsage(group);
|
||||
|
||||
int count = 0;
|
||||
for (ClassFile cf : group.getClasses())
|
||||
for (Field f : new ArrayList<>(cf.getFields()))
|
||||
if (!used.contains(f))
|
||||
{
|
||||
cf.removeField(f);
|
||||
++count;
|
||||
}
|
||||
|
||||
logger.info("Removed " + count + " unused fields");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
|
||||
import net.runelite.deob.Deob;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class UnusedMethods implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(UnusedMethods.class);
|
||||
|
||||
private final Set<Method> methods = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method method : cf.getMethods())
|
||||
{
|
||||
run(method);
|
||||
}
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
boolean extendsApplet = extendsApplet(cf);
|
||||
|
||||
for (Method method : new ArrayList<>(cf.getMethods()))
|
||||
{
|
||||
// constructors can't be renamed, but are obfuscated
|
||||
if (!Deob.isObfuscated(method.getName()) && !method.getName().equals("<init>"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (extendsApplet && method.getName().equals("<init>"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!methods.contains(method))
|
||||
{
|
||||
logger.debug("Removing unused method {}", method);
|
||||
|
||||
cf.removeMethod(method);
|
||||
++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Removed {} methods", count);
|
||||
}
|
||||
|
||||
private void run(Method method)
|
||||
{
|
||||
Code code = method.getCode();
|
||||
|
||||
if (code == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (Instruction i : code.getInstructions().getInstructions())
|
||||
{
|
||||
if (!(i instanceof InvokeInstruction))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
InvokeInstruction ii = (InvokeInstruction) i;
|
||||
|
||||
methods.addAll(ii.getMethods());
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean extendsApplet(ClassFile cf)
|
||||
{
|
||||
if (cf.getParent() != null)
|
||||
{
|
||||
return extendsApplet(cf.getParent());
|
||||
}
|
||||
return cf.getSuperName().equals("java/applet/Applet");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Sets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
|
||||
import net.runelite.asm.execution.Execution;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
import net.runelite.asm.execution.StackContext;
|
||||
import net.runelite.asm.signature.Signature;
|
||||
import net.runelite.asm.signature.util.VirtualMethods;
|
||||
import net.runelite.deob.Deob;
|
||||
import net.runelite.deob.DeobAnnotations;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class UnusedParameters implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(UnusedParameters.class);
|
||||
|
||||
private final Map<List<Method>, Collection<Integer>> unused = new HashMap<>();
|
||||
private final Multimap<Instruction, InstructionContext> invokes = HashMultimap.create();
|
||||
|
||||
private void visit(InstructionContext ictx)
|
||||
{
|
||||
Instruction i = ictx.getInstruction();
|
||||
|
||||
if (!(i instanceof InvokeInstruction))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
invokes.put(i, ictx);
|
||||
}
|
||||
|
||||
private void buildUnused(ClassGroup group)
|
||||
{
|
||||
unused.clear();
|
||||
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
if (!Deob.isObfuscated(m.getName()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
List<Method> ms = VirtualMethods.getVirtualMethods(m);
|
||||
Collection<Integer> u = this.findUnusedParameters(ms);
|
||||
if (!u.isEmpty())
|
||||
{
|
||||
unused.put(ms, u);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldRemove(Method m, int parameter)
|
||||
{
|
||||
Signature obSig = DeobAnnotations.getObfuscatedSignature(m);
|
||||
if (obSig == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return parameter + 1 == obSig.size();
|
||||
}
|
||||
|
||||
private int processUnused(Execution execution, ClassGroup group)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (List<Method> m : unused.keySet())
|
||||
{
|
||||
Collection<Integer> u = unused.get(m);
|
||||
|
||||
int offset = m.size() == 1 && m.get(0).isStatic() ? 0 : 1;
|
||||
|
||||
for (int unusedParameter : u)
|
||||
{
|
||||
if (!shouldRemove(m.get(0), unusedParameter))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Signature signature = m.get(0).getDescriptor();
|
||||
int lvtIndex = this.getLvtIndex(signature, offset, unusedParameter);
|
||||
/* removing the parameter can't cause collisions on other (overloaded) methods because prior to this we rename
|
||||
* all classes/fields/methods to have unique names.
|
||||
*/
|
||||
logger.debug("Removing parameter {} at index {} from {}", unusedParameter, lvtIndex, m);
|
||||
removeParameter(group, m, signature, execution, unusedParameter, lvtIndex);
|
||||
break;
|
||||
}
|
||||
|
||||
++count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private Set<Integer> findUnusedParameters(Method method)
|
||||
{
|
||||
int offset = method.isStatic() ? 0 : 1;
|
||||
Signature signature = method.getDescriptor();
|
||||
List<Integer> unusedParams = new ArrayList<>();
|
||||
|
||||
for (int variableIndex = 0, lvtIndex = offset;
|
||||
variableIndex < signature.size();
|
||||
lvtIndex += signature.getTypeOfArg(variableIndex++).getSize())
|
||||
{
|
||||
List<? extends Instruction> lv = method.findLVTInstructionsForVariable(lvtIndex);
|
||||
if (lv == null || lv.isEmpty())
|
||||
{
|
||||
unusedParams.add(variableIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return ImmutableSet.copyOf(unusedParams);
|
||||
}
|
||||
|
||||
@SuppressWarnings("empty-statement")
|
||||
private int getLvtIndex(Signature signature, int offset, int parameter)
|
||||
{
|
||||
// get lvt index for parameter
|
||||
int lvtIndex = offset;
|
||||
for (int variableIndex = 0;
|
||||
variableIndex < parameter;
|
||||
lvtIndex += signature.getTypeOfArg(variableIndex++).getSize());
|
||||
return lvtIndex;
|
||||
}
|
||||
|
||||
public Collection<Integer> findUnusedParameters(Collection<Method> methods)
|
||||
{
|
||||
Set<Integer> list = null;
|
||||
|
||||
for (Method m : methods)
|
||||
{
|
||||
Set<Integer> p = findUnusedParameters(m);
|
||||
|
||||
if (list == null)
|
||||
{
|
||||
list = p;
|
||||
}
|
||||
else
|
||||
{
|
||||
list = Sets.intersection(list, p);
|
||||
}
|
||||
}
|
||||
|
||||
List<Integer> l = new ArrayList<>(list);
|
||||
Collections.sort(l);
|
||||
Collections.reverse(l);
|
||||
return l;
|
||||
}
|
||||
|
||||
public void removeParameter(ClassGroup group, List<Method> methods, Signature signature, Execution execution, int paramIndex, int lvtIndex)
|
||||
{
|
||||
int slots = signature.getTypeOfArg(paramIndex).getSize();
|
||||
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
Code c = m.getCode();
|
||||
|
||||
if (c == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Instruction i : new ArrayList<>(c.getInstructions().getInstructions()))
|
||||
{
|
||||
if (!(i instanceof InvokeInstruction))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
InvokeInstruction ii = (InvokeInstruction) i;
|
||||
|
||||
if (!ii.getMethods().stream().anyMatch(me -> methods.contains(me)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ii.removeParameter(paramIndex); // remove parameter from instruction
|
||||
|
||||
Collection<InstructionContext> ics = invokes.get(i);
|
||||
assert ics != null;
|
||||
if (ics != null)
|
||||
{
|
||||
for (InstructionContext ins : ics)
|
||||
{
|
||||
int pops = signature.size() - paramIndex - 1; // index from top of stack of parameter. 0 is the last parameter
|
||||
|
||||
StackContext sctx = ins.getPops().get(pops);
|
||||
if (sctx.getPushed().getInstruction().getInstructions() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ins.removeStack(pops); // remove parameter from stack
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Method method : methods)
|
||||
{
|
||||
if (method.getCode() != null)
|
||||
// adjust lvt indexes to get rid of idx in the method
|
||||
{
|
||||
for (Instruction ins : method.getCode().getInstructions().getInstructions())
|
||||
{
|
||||
if (ins instanceof LVTInstruction)
|
||||
{
|
||||
LVTInstruction lins = (LVTInstruction) ins;
|
||||
|
||||
int i = lins.getVariableIndex();
|
||||
assert i != lvtIndex; // current unused variable detection just looks for no accesses
|
||||
|
||||
// reassign
|
||||
if (i > lvtIndex)
|
||||
{
|
||||
assert i > 0;
|
||||
assert i >= lvtIndex + slots;
|
||||
|
||||
Instruction newIns = lins.setVariableIndex(i - slots);
|
||||
assert ins == newIns;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Method method : methods)
|
||||
{
|
||||
method.getDescriptor().remove(paramIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private int count;
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
int i;
|
||||
int pnum = 1;
|
||||
do
|
||||
{
|
||||
group.buildClassGraph();
|
||||
|
||||
invokes.clear();
|
||||
this.buildUnused(group);
|
||||
|
||||
Execution execution = new Execution(group);
|
||||
execution.addExecutionVisitor(ictx -> visit(ictx));
|
||||
execution.populateInitialMethods();
|
||||
execution.run();
|
||||
|
||||
i = this.processUnused(execution, group);
|
||||
|
||||
count += i;
|
||||
break;
|
||||
}
|
||||
while (i > 0);
|
||||
|
||||
logger.info("Removed {} unused parameters", count);
|
||||
}
|
||||
|
||||
public int getCount()
|
||||
{
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.arithmetic;
|
||||
|
||||
// a constant associated to a field
|
||||
class AssociatedConstant
|
||||
{
|
||||
Number value;
|
||||
boolean other; // whether or not another field is involved
|
||||
boolean constant; // whether or not the constant is a constant field assignment
|
||||
boolean getter; // whether or not this is associated as a getter
|
||||
boolean setter; // whether or not this is associated as a setter
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.arithmetic;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
public class DMath
|
||||
{
|
||||
public static BigInteger modInverse(BigInteger val, int bits)
|
||||
{
|
||||
try
|
||||
{
|
||||
BigInteger shift = BigInteger.ONE.shiftLeft(bits);
|
||||
return val.modInverse(shift);
|
||||
}
|
||||
catch (ArithmeticException e)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
public static int modInverse(int val)
|
||||
{
|
||||
return modInverse(BigInteger.valueOf(val), 32).intValue();
|
||||
}
|
||||
|
||||
public static long modInverse(long val)
|
||||
{
|
||||
return modInverse(BigInteger.valueOf(val), 64).longValue();
|
||||
}
|
||||
|
||||
public static Number modInverse(Number value)
|
||||
{
|
||||
if (value instanceof Integer)
|
||||
{
|
||||
return modInverse((int) value);
|
||||
}
|
||||
else if (value instanceof Long)
|
||||
{
|
||||
return modInverse((long) value);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isInversable(int val)
|
||||
{
|
||||
try
|
||||
{
|
||||
modInverse(val);
|
||||
return true;
|
||||
}
|
||||
catch (ArithmeticException ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isInversable(long val)
|
||||
{
|
||||
try
|
||||
{
|
||||
modInverse(val);
|
||||
return true;
|
||||
}
|
||||
catch (ArithmeticException ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isInversable(Number value)
|
||||
{
|
||||
if (value instanceof Integer)
|
||||
{
|
||||
return isInversable((int) value);
|
||||
}
|
||||
else if (value instanceof Long)
|
||||
{
|
||||
return isInversable((long) value);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
public static Number multiply(Number one, Number two)
|
||||
{
|
||||
assert one.getClass() == two.getClass();
|
||||
|
||||
if (one instanceof Integer)
|
||||
{
|
||||
return (int) one * (int) two;
|
||||
}
|
||||
else if (one instanceof Long)
|
||||
{
|
||||
return (long) one * (long) two;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean equals(Number one, int two)
|
||||
{
|
||||
if (one instanceof Long)
|
||||
{
|
||||
return equals(one, ((long) two) & 0xffffffff);
|
||||
}
|
||||
return one.intValue() == two;
|
||||
}
|
||||
|
||||
public static boolean equals(Number one, long two)
|
||||
{
|
||||
if (one instanceof Integer)
|
||||
{
|
||||
return equals(one, (int) two);
|
||||
}
|
||||
return one.longValue() == two;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.arithmetic;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import java.util.List;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instruction.types.DupInstruction;
|
||||
import net.runelite.asm.attributes.code.instructions.Dup;
|
||||
import net.runelite.asm.attributes.code.instructions.Dup2_X1;
|
||||
import net.runelite.asm.attributes.code.instructions.Dup_X1;
|
||||
import net.runelite.asm.attributes.code.instructions.IMul;
|
||||
import net.runelite.asm.attributes.code.instructions.LMul;
|
||||
import net.runelite.asm.attributes.code.instructions.Pop;
|
||||
import net.runelite.asm.attributes.code.instructions.Pop2;
|
||||
import net.runelite.asm.attributes.code.instructions.Swap;
|
||||
import net.runelite.asm.execution.Execution;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
import net.runelite.asm.execution.MethodContext;
|
||||
import net.runelite.asm.execution.StackContext;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Removes the duplication of multiplication instructions to make
|
||||
* MultiplicationDeobfuscator easier
|
||||
*
|
||||
* @author Adam
|
||||
*/
|
||||
public class DupDeobfuscator implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(DupDeobfuscator.class);
|
||||
|
||||
private int count;
|
||||
|
||||
private void visit(InstructionContext i)
|
||||
{
|
||||
if (!(i.getInstruction() instanceof DupInstruction))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DupInstruction di = (DupInstruction) i.getInstruction();
|
||||
|
||||
List<StackContext> sctxs = di.getDuplicated(i); // stack values being duplicated
|
||||
|
||||
for (StackContext sctx : sctxs)
|
||||
{
|
||||
InstructionContext ic = sctx.getPushed();
|
||||
|
||||
if (ic.getInstruction() instanceof IMul)
|
||||
{
|
||||
if (i.getInstruction() instanceof Dup)
|
||||
{
|
||||
logger.debug("Dup instruction {} duplicates multiplication result {}", i, ic);
|
||||
|
||||
undup(i);
|
||||
++count;
|
||||
return;
|
||||
}
|
||||
if (i.getInstruction() instanceof Dup_X1)
|
||||
{
|
||||
logger.debug("Dup_X1 instruction {} duplicates multiplication result {}", i, ic);
|
||||
|
||||
undup_x1(i);
|
||||
++count;
|
||||
return;
|
||||
}
|
||||
|
||||
logger.warn("Dup instruction {} pops imul", i);
|
||||
}
|
||||
else if (ic.getInstruction() instanceof LMul)
|
||||
{
|
||||
if (i.getInstruction() instanceof Dup2_X1)
|
||||
{
|
||||
logger.debug("Dup_X2 instruction {} duplicates multiplication result {}", i, ic);
|
||||
|
||||
undup2_x1(i);
|
||||
++count;
|
||||
return;
|
||||
}
|
||||
|
||||
logger.warn("Dup instruction {} pops lmul", i);
|
||||
}
|
||||
}
|
||||
|
||||
// find if mul pops anything duplicated
|
||||
sctxs = di.getCopies(i);
|
||||
|
||||
for (StackContext sctx : sctxs)
|
||||
{
|
||||
for (InstructionContext ic : sctx.getPopped())
|
||||
{
|
||||
if (ic.getInstruction() instanceof IMul)
|
||||
{
|
||||
if (i.getInstruction() instanceof Dup)
|
||||
{
|
||||
logger.debug("imul {} pops dup instruction {}", ic, i);
|
||||
|
||||
undup(i);
|
||||
++count;
|
||||
return;
|
||||
}
|
||||
if (i.getInstruction() instanceof Dup_X1)
|
||||
{
|
||||
logger.debug("imul {} pops dup x1 instruction {}", ic, i);
|
||||
|
||||
undup_x1(i);
|
||||
++count;
|
||||
return;
|
||||
}
|
||||
|
||||
logger.warn("imul pops dup instruction {}", i);
|
||||
}
|
||||
else if (ic.getInstruction() instanceof LMul)
|
||||
{
|
||||
if (i.getInstruction() instanceof Dup2_X1)
|
||||
{
|
||||
logger.debug("imul {} pops dup2 x1 instruction {}", ic, i);
|
||||
|
||||
undup2_x1(i);
|
||||
++count;
|
||||
return;
|
||||
}
|
||||
|
||||
logger.warn("lmul pops dup instruction {}", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void undup(InstructionContext ictx)
|
||||
{
|
||||
assert ictx.getInstruction() instanceof Dup;
|
||||
|
||||
Instructions instructions = ictx.getInstruction().getInstructions();
|
||||
|
||||
StackContext duplicated = ictx.getPops().get(0);
|
||||
|
||||
int idx = instructions.getInstructions().indexOf(ictx.getInstruction());
|
||||
assert idx != -1;
|
||||
|
||||
// replace dup with duplicated instructions
|
||||
instructions.remove(ictx.getInstruction());
|
||||
|
||||
// insert copy
|
||||
copy(duplicated, instructions, idx);
|
||||
}
|
||||
|
||||
private void undup_x1(InstructionContext ictx)
|
||||
{
|
||||
assert ictx.getInstruction() instanceof Dup_X1;
|
||||
|
||||
Instructions instructions = ictx.getInstruction().getInstructions();
|
||||
|
||||
StackContext duplicated = ictx.getPops().get(0);
|
||||
|
||||
// replace dup_x1 with swap
|
||||
int idx = instructions.replace(ictx.getInstruction(), new Swap(instructions));
|
||||
|
||||
// copy imul and insert after idx
|
||||
copy(duplicated, instructions, idx + 1);
|
||||
}
|
||||
|
||||
private void undup2_x1(InstructionContext ictx)
|
||||
{
|
||||
assert ictx.getInstruction() instanceof Dup2_X1;
|
||||
assert ictx.getPops().size() == 2; // only support this form
|
||||
|
||||
// I L -> L I L
|
||||
|
||||
Instructions instructions = ictx.getInstruction().getInstructions();
|
||||
|
||||
// can't swap a long on the stack, so
|
||||
|
||||
int idx = instructions.getInstructions().indexOf(ictx.getInstruction());
|
||||
assert idx != -1;
|
||||
|
||||
instructions.remove(ictx.getInstruction()); // remove dup2_x1
|
||||
instructions.addInstruction(idx++, new Pop2(instructions)); // pop long
|
||||
instructions.addInstruction(idx++, new Pop(instructions)); // pop int
|
||||
|
||||
// insert copy of long
|
||||
idx = copy(ictx.getPops().get(0), instructions, idx);
|
||||
// insert copy of int
|
||||
idx = copy(ictx.getPops().get(1), instructions, idx);
|
||||
// insert copy of long
|
||||
/* idx = */ copy(ictx.getPops().get(0), instructions, idx);
|
||||
}
|
||||
|
||||
/**
|
||||
* copy the instruction which pushed sctx and insert into instructions
|
||||
* starting at index idx
|
||||
*/
|
||||
private int copy(StackContext sctx, Instructions instructions, int idx)
|
||||
{
|
||||
InstructionContext ictx = sctx.getPushed();
|
||||
|
||||
if (ictx.getInstruction() instanceof DupInstruction)
|
||||
{
|
||||
// we only care about one path
|
||||
DupInstruction di = (DupInstruction) ictx.getInstruction();
|
||||
sctx = di.getOriginal(sctx);
|
||||
ictx = sctx.getPushed();
|
||||
}
|
||||
|
||||
// copy required instructions
|
||||
// the first thing popped was pushed last, so reverse
|
||||
for (StackContext s : Lists.reverse(ictx.getPops()))
|
||||
{
|
||||
idx = copy(s, instructions, idx);
|
||||
}
|
||||
|
||||
// copy instruction
|
||||
Instruction i = ictx.getInstruction();
|
||||
|
||||
logger.debug("Duplicating instruction {} at index {}", i, idx);
|
||||
|
||||
i = i.clone();
|
||||
|
||||
instructions.addInstruction(idx, i);
|
||||
return idx + 1; // move on to next instruction
|
||||
}
|
||||
|
||||
private void visit(MethodContext mctx)
|
||||
{
|
||||
for (InstructionContext ictx : mctx.getInstructionContexts())
|
||||
{
|
||||
if (ictx.getInstruction().getInstructions() == null)
|
||||
{
|
||||
// already removed?
|
||||
continue;
|
||||
}
|
||||
|
||||
visit(ictx);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
Execution e = new Execution(group);
|
||||
e.addMethodContextVisitor(m -> visit(m));
|
||||
e.populateInitialMethods();
|
||||
e.run();
|
||||
|
||||
logger.info("Replaced {} dup instructions", count);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.arithmetic;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import net.runelite.asm.pool.Field;
|
||||
|
||||
class Encryption
|
||||
{
|
||||
private final Map<Field, Pair> fields = new HashMap<>();
|
||||
|
||||
void addPair(Pair pair)
|
||||
{
|
||||
assert pair.field != null;
|
||||
|
||||
Pair existing = fields.get(pair.field);
|
||||
if (existing != null)
|
||||
{
|
||||
// if this is a subsequent guess, then the new guess
|
||||
// is multiplied into the old guess
|
||||
fields.put(pair.field, new Pair(pair, existing));
|
||||
}
|
||||
else
|
||||
{
|
||||
fields.put(pair.field, pair);
|
||||
}
|
||||
}
|
||||
|
||||
Pair getField(Field field)
|
||||
{
|
||||
return fields.get(field);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.arithmetic;
|
||||
|
||||
import static java.lang.Math.abs;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import static net.runelite.deob.deobfuscators.arithmetic.DMath.multiply;
|
||||
|
||||
class FieldInfo
|
||||
{
|
||||
public final Set<Number> getters = new HashSet<>();
|
||||
public final Set<Number> setters = new HashSet<>();
|
||||
public final Set<AssociatedConstant> constants = new HashSet<>();
|
||||
|
||||
boolean guessDecreasesConstants(Pair guess)
|
||||
{
|
||||
if (getters.isEmpty() && setters.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (guess.getter.longValue() == -1 && guess.setter.longValue() == -1)
|
||||
{
|
||||
// special case - when guess is -1/-1, checking the abs(total)
|
||||
// is not helpful. instead look for both:
|
||||
// associated non-constant of value -1
|
||||
// no non-constant non-other values (indicative of field being
|
||||
// obfuscated still since if it used elsewhere it would have
|
||||
// an associated constant)
|
||||
//
|
||||
// this is really only her to fight:
|
||||
// client.java: field975 = field974;
|
||||
// client.java: field974 = field971;
|
||||
// when field974 is only used in combination with field975
|
||||
// and is initialized to -1. This causes causes guess()
|
||||
// to guess a negated getter which then causes
|
||||
// client.java: field975 = field974 * -1;
|
||||
// client.java: field974 = field971 * -1;
|
||||
// And it becomes difficult to prevent both fields being
|
||||
// multiplied by -1 on each round. field975 has some other
|
||||
// uses.
|
||||
|
||||
// only change if there is a -1 in the non constant
|
||||
// and also there is a non constant non other too
|
||||
List<Number> value2 = constants.stream()
|
||||
.filter(i -> !i.constant)
|
||||
.map(i -> i.value)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Number> value = constants.stream()
|
||||
.filter(i -> !i.other)
|
||||
.filter(i -> !i.constant)
|
||||
.map(i -> i.value)
|
||||
.collect(Collectors.toList());
|
||||
return value.isEmpty() && value2.contains(-1);
|
||||
}
|
||||
|
||||
// remove possibe getters that are:
|
||||
// - a constant - they don't necessarilly decrease when deobfuscated
|
||||
// - are multiplied into another field
|
||||
// - are both a getter and setter
|
||||
Collection<Number> gettersFiltered = getters.stream()
|
||||
.filter(number -> isOkay(number))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
Collection<Number> settersFiltered = setters.stream()
|
||||
.filter(number -> isOkay(number))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (!gettersFiltered.isEmpty())
|
||||
{
|
||||
long before = gettersFiltered.stream()
|
||||
.mapToLong(number -> abs(downsample(number).longValue()))
|
||||
.sum();
|
||||
long after = gettersFiltered.stream()
|
||||
.mapToLong(number -> abs(downsample(multiply(number, guess.setter)).longValue()))
|
||||
.sum();
|
||||
|
||||
assert before >= 0;
|
||||
assert after >= 0;
|
||||
|
||||
// If the total value of the getters is more, assume
|
||||
// the guess is bad. Ideally the values go to 1, so
|
||||
// 'after' will be small.
|
||||
if (after > before)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!settersFiltered.isEmpty())
|
||||
{
|
||||
long beforeSetter = settersFiltered.stream()
|
||||
.mapToLong(number -> abs(downsample(number).longValue()))
|
||||
.sum();
|
||||
|
||||
long afterSetter = settersFiltered.stream()
|
||||
.mapToLong(number -> abs(downsample(multiply(number, guess.getter)).longValue()))
|
||||
.sum();
|
||||
|
||||
assert beforeSetter >= 0;
|
||||
assert afterSetter >= 0;
|
||||
|
||||
if (afterSetter > beforeSetter)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isOkay(Number number)
|
||||
{
|
||||
for (AssociatedConstant c : constants)
|
||||
{
|
||||
if (c.value.equals(number)
|
||||
&& !c.constant
|
||||
&& !c.other
|
||||
&& !(c.setter && c.getter))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// summation of longs probably overflows,
|
||||
// so only use the most significant 32 bits
|
||||
private Number downsample(Number number)
|
||||
{
|
||||
if (number instanceof Long)
|
||||
{
|
||||
long l = (Long) number;
|
||||
return l >>> 32;
|
||||
}
|
||||
return number;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,795 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.arithmetic;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.Type;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instruction.types.ArrayLoad;
|
||||
import net.runelite.asm.attributes.code.instruction.types.ArrayStoreInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.DivisionInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.FieldInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.GetFieldInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.SetFieldInstruction;
|
||||
import net.runelite.asm.attributes.code.instructions.IAdd;
|
||||
import net.runelite.asm.attributes.code.instructions.IMul;
|
||||
import net.runelite.asm.attributes.code.instructions.IShR;
|
||||
import net.runelite.asm.attributes.code.instructions.ISub;
|
||||
import net.runelite.asm.attributes.code.instructions.If;
|
||||
import net.runelite.asm.attributes.code.instructions.If0;
|
||||
import net.runelite.asm.attributes.code.instructions.LAdd;
|
||||
import net.runelite.asm.attributes.code.instructions.LCmp;
|
||||
import net.runelite.asm.attributes.code.instructions.LDC;
|
||||
import net.runelite.asm.attributes.code.instructions.LMul;
|
||||
import net.runelite.asm.attributes.code.instructions.LSub;
|
||||
import net.runelite.asm.execution.Execution;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
import net.runelite.asm.execution.MethodContext;
|
||||
import net.runelite.asm.execution.StackContext;
|
||||
import net.runelite.deob.DeobAnnotations;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import static net.runelite.deob.deobfuscators.arithmetic.DMath.modInverse;
|
||||
import static net.runelite.deob.deobfuscators.arithmetic.DMath.multiply;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ModArith implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(ModArith.class);
|
||||
|
||||
private ClassGroup group;
|
||||
private Execution execution;
|
||||
private final Map<Field, FieldInfo> fieldInfo = new HashMap<>();
|
||||
private List<Pair> pairs = new ArrayList<>();
|
||||
private Encryption encryption = new Encryption();
|
||||
|
||||
private FieldInfo getFieldInfo(Field field)
|
||||
{
|
||||
FieldInfo f = fieldInfo.get(field);
|
||||
if (f == null)
|
||||
{
|
||||
f = new FieldInfo();
|
||||
fieldInfo.put(field, f);
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
private static List<InstructionContext> getInsInExpr(InstructionContext ctx, Set<Instruction> set, boolean imul)
|
||||
{
|
||||
List<InstructionContext> l = new ArrayList<>();
|
||||
|
||||
if (ctx == null || set.contains(ctx.getInstruction()))
|
||||
{
|
||||
return l;
|
||||
}
|
||||
|
||||
set.add(ctx.getInstruction());
|
||||
|
||||
if (imul)
|
||||
{
|
||||
if (!(ctx.getInstruction() instanceof IMul) & !(ctx.getInstruction() instanceof LMul))
|
||||
{
|
||||
l.add(ctx);
|
||||
return l;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// invoke and array store pops are unrelated to each other
|
||||
if (ctx.getInstruction() instanceof InvokeInstruction
|
||||
|| ctx.getInstruction() instanceof ArrayStoreInstruction
|
||||
|| ctx.getInstruction() instanceof ArrayLoad
|
||||
|| ctx.getInstruction() instanceof If
|
||||
|| ctx.getInstruction() instanceof If0
|
||||
|| ctx.getInstruction() instanceof LCmp
|
||||
|| ctx.getInstruction() instanceof DivisionInstruction
|
||||
|| ctx.getInstruction() instanceof IShR)
|
||||
{
|
||||
return l;
|
||||
}
|
||||
|
||||
l.add(ctx);
|
||||
}
|
||||
|
||||
for (StackContext s : ctx.getPops())
|
||||
{
|
||||
l.addAll(getInsInExpr(s.getPushed(), set, imul));
|
||||
}
|
||||
for (StackContext s : ctx.getPushes())
|
||||
{
|
||||
for (InstructionContext i : s.getPopped())
|
||||
{
|
||||
l.addAll(getInsInExpr(i, set, imul));
|
||||
}
|
||||
}
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
// find associated constants with each field
|
||||
private void findConstants(MethodContext mctx)
|
||||
{
|
||||
for (InstructionContext ctx : mctx.getInstructionContexts())
|
||||
{
|
||||
if (ctx.getInstruction() instanceof FieldInstruction)
|
||||
{
|
||||
FieldInstruction fi = (FieldInstruction) ctx.getInstruction();
|
||||
|
||||
if (fi.getMyField() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((!fi.getField().getType().equals(Type.INT)
|
||||
&& !fi.getField().getType().equals(Type.LONG))
|
||||
|| fi.getField().getType().isArray())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FieldInfo fieldInfo = getFieldInfo(fi.getMyField());
|
||||
|
||||
List<InstructionContext> l = getInsInExpr(ctx, new HashSet(), false);
|
||||
boolean other = false; // check if this contains another field
|
||||
boolean getter = false, setter = false;
|
||||
for (InstructionContext i : l)
|
||||
{
|
||||
if (i.getInstruction() instanceof FieldInstruction)
|
||||
{
|
||||
FieldInstruction fi2 = (FieldInstruction) i.getInstruction();
|
||||
Field myField = fi2.getMyField();
|
||||
|
||||
if (myField != null && myField != fi.getMyField())
|
||||
{
|
||||
Type t = myField.getType();
|
||||
if (t.equals(fi.getMyField().getType()))
|
||||
{
|
||||
other = true;
|
||||
}
|
||||
}
|
||||
|
||||
else if (myField != null && myField == fi.getMyField())
|
||||
{
|
||||
if (fi2 instanceof SetFieldInstruction)
|
||||
{
|
||||
setter = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
getter = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if this is a constant assignment
|
||||
boolean constant = false;
|
||||
if (fi instanceof SetFieldInstruction)
|
||||
{
|
||||
InstructionContext pushedsfi = ctx.getPops().get(0).getPushed(); // value being set
|
||||
pushedsfi = pushedsfi.resolve(ctx.getPops().get(0));
|
||||
|
||||
if (pushedsfi.getInstruction() instanceof LDC)
|
||||
{
|
||||
constant = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (InstructionContext i : l)
|
||||
{
|
||||
if (i.getInstruction() instanceof LDC)
|
||||
{
|
||||
PushConstantInstruction w = (PushConstantInstruction) i.getInstruction();
|
||||
if (w.getConstant() instanceof Integer || w.getConstant() instanceof Long)
|
||||
{
|
||||
AssociatedConstant n = new AssociatedConstant();
|
||||
n.value = (Number) w.getConstant();
|
||||
n.other = other;
|
||||
n.constant = constant;
|
||||
n.getter = getter;
|
||||
n.setter = setter;
|
||||
fieldInfo.constants.add(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find potential getters/setters for each field
|
||||
private void findUses(MethodContext mctx)
|
||||
{
|
||||
for (InstructionContext ctx : mctx.getInstructionContexts())
|
||||
{
|
||||
if (ctx.getInstruction() instanceof IMul || ctx.getInstruction() instanceof LMul)
|
||||
{
|
||||
Instruction one = ctx.getPops().get(0).getPushed().getInstruction();
|
||||
Instruction two = ctx.getPops().get(1).getPushed().getInstruction();
|
||||
|
||||
PushConstantInstruction pc = null;
|
||||
GetFieldInstruction gf = null;
|
||||
if (one instanceof PushConstantInstruction && two instanceof GetFieldInstruction)
|
||||
{
|
||||
pc = (PushConstantInstruction) one;
|
||||
gf = (GetFieldInstruction) two;
|
||||
}
|
||||
else if (two instanceof PushConstantInstruction && one instanceof GetFieldInstruction)
|
||||
{
|
||||
pc = (PushConstantInstruction) two;
|
||||
gf = (GetFieldInstruction) one;
|
||||
}
|
||||
|
||||
if (pc == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Field field = gf.getMyField();
|
||||
if (field == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FieldInfo fieldInfo = getFieldInfo(field);
|
||||
|
||||
// parse the full multiplication expression to
|
||||
// get all associated constants
|
||||
List<InstructionContext> insInExpr = getInsInExpr(ctx, new HashSet(), true);
|
||||
|
||||
for (InstructionContext ctx2 : insInExpr)
|
||||
{
|
||||
if (!(ctx2.getInstruction() instanceof PushConstantInstruction))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
PushConstantInstruction pci3 = (PushConstantInstruction) ctx2.getInstruction();
|
||||
Number value = (Number) pci3.getConstant();
|
||||
|
||||
// field * constant
|
||||
if (value instanceof Integer || value instanceof Long)
|
||||
{
|
||||
fieldInfo.getters.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ctx.getInstruction() instanceof SetFieldInstruction)
|
||||
{
|
||||
SetFieldInstruction sf = (SetFieldInstruction) ctx.getInstruction();
|
||||
|
||||
Field field = sf.getMyField();
|
||||
if (field == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FieldInfo fieldInfo = getFieldInfo(field);
|
||||
|
||||
InstructionContext pushedsfi = ctx.getPops().get(0).getPushed(); // value being set
|
||||
pushedsfi = pushedsfi.resolve(ctx.getPops().get(0));
|
||||
|
||||
if (!(pushedsfi.getInstruction() instanceof IMul) && !(pushedsfi.getInstruction() instanceof LMul)
|
||||
&& !(pushedsfi.getInstruction() instanceof IAdd) && !(pushedsfi.getInstruction() instanceof LAdd)
|
||||
&& !(pushedsfi.getInstruction() instanceof ISub) && !(pushedsfi.getInstruction() instanceof LSub))
|
||||
{
|
||||
if (pushedsfi.getInstruction() instanceof LDC)
|
||||
{
|
||||
PushConstantInstruction ldc = (PushConstantInstruction) pushedsfi.getInstruction();
|
||||
|
||||
if (ldc.getConstant() instanceof Integer || ldc.getConstant() instanceof Long)
|
||||
{
|
||||
Number i = (Number) ldc.getConstant();
|
||||
|
||||
// field = constant
|
||||
fieldInfo.setters.add(i);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Instruction one = pushedsfi.getPops().get(0).getPushed().getInstruction();
|
||||
Instruction two = pushedsfi.getPops().get(1).getPushed().getInstruction();
|
||||
|
||||
// field = field + imul
|
||||
if (pushedsfi.getInstruction() instanceof IAdd)
|
||||
{
|
||||
if (one instanceof IMul && two instanceof GetFieldInstruction)
|
||||
{
|
||||
one = pushedsfi.getPops().get(0).getPushed().getPops().get(0).getPushed().getInstruction();
|
||||
two = pushedsfi.getPops().get(0).getPushed().getPops().get(1).getPushed().getInstruction();
|
||||
}
|
||||
}
|
||||
|
||||
// if both one and two are constants then one of them must not be a setter
|
||||
PushConstantInstruction pc = null;
|
||||
if (one instanceof PushConstantInstruction
|
||||
&& !(two instanceof PushConstantInstruction))
|
||||
{
|
||||
pc = (PushConstantInstruction) one;
|
||||
}
|
||||
else if (two instanceof PushConstantInstruction
|
||||
&& !(one instanceof PushConstantInstruction))
|
||||
{
|
||||
pc = (PushConstantInstruction) two;
|
||||
}
|
||||
|
||||
if (pc == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Number value2 = (Number) pc.getConstant();
|
||||
|
||||
// field = something * constant
|
||||
if (value2 instanceof Integer || value2 instanceof Long)
|
||||
{
|
||||
fieldInfo.setters.add(value2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Pair guess(Field field, Collection<Number> constants)
|
||||
{
|
||||
if (constants.isEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// multiply each by each,
|
||||
// lowest number wins
|
||||
Number s1 = 0, s2 = 0;
|
||||
Number smallest = 0;
|
||||
for (Number i : constants)
|
||||
{
|
||||
for (Number i2 : constants)
|
||||
{
|
||||
if (DMath.equals(i, 0) || DMath.equals(i2, 0)
|
||||
|| DMath.equals(i, 1) || DMath.equals(i2, 1))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i.equals(i2))
|
||||
{
|
||||
// it is never i*i. if there is only one constant,
|
||||
// prefer i*inverse(i) if possible by setting i2
|
||||
// to 1
|
||||
if (i2 instanceof Integer)
|
||||
{
|
||||
i2 = 1;
|
||||
}
|
||||
else if (i2 instanceof Long)
|
||||
{
|
||||
i2 = 1L;
|
||||
}
|
||||
}
|
||||
|
||||
Number result = multiply(i, i2);
|
||||
|
||||
if (DMath.equals(result, 0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean smaller;
|
||||
if (smallest.longValue() == 0L
|
||||
// abs(MIN_VALUE) is MIN_VALUE - it is not the smallest
|
||||
|| (result instanceof Long && Long.MIN_VALUE == result.longValue())
|
||||
|| (result instanceof Integer && Integer.MIN_VALUE == result.intValue()))
|
||||
{
|
||||
smaller = false;
|
||||
}
|
||||
else if (i instanceof Long)
|
||||
{
|
||||
smaller = Math.abs((long) result) < Math.abs((long) smallest);
|
||||
}
|
||||
else
|
||||
{
|
||||
smaller = Math.abs((int) result) < Math.abs((int) smallest);
|
||||
}
|
||||
|
||||
if (DMath.equals(smallest, 0) || DMath.equals(result, 1) || smaller)
|
||||
{
|
||||
s1 = i;
|
||||
s2 = i2;
|
||||
smallest = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!DMath.equals(smallest, 1))
|
||||
{
|
||||
// smallest s1*s2 is not 1
|
||||
if (DMath.isInversable(smallest))
|
||||
{
|
||||
// s1*s2=smallest
|
||||
// divide both sides by inverse(smallest)
|
||||
// to make the right hand side 1
|
||||
|
||||
// pick s1/s2 depending on which one isn't inversible
|
||||
if (DMath.isInversable(s1))
|
||||
{
|
||||
s2 = multiply(s2, modInverse(smallest));
|
||||
smallest = 1; // rhs goes to one
|
||||
assert multiply(s1, s2).intValue() == 1;
|
||||
}
|
||||
else if (DMath.isInversable(s2))
|
||||
{
|
||||
s1 = multiply(s1, modInverse(smallest));
|
||||
smallest = 1;
|
||||
assert multiply(s1, s2).intValue() == 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// s1*s2 is not 1 and smallest is not inversible
|
||||
// calculate a new s1 or s2 if one is inversible
|
||||
if (DMath.isInversable(s1))
|
||||
{
|
||||
// assume this is s1 * (s2 * constant) = 1 * constant
|
||||
Number newTwo = modInverse(s1); // we guess s2 is actually this
|
||||
// check if s2 * constant = old value of s2
|
||||
if (multiply(newTwo, smallest).equals(s2))
|
||||
{
|
||||
s2 = newTwo; // s2 changes to new value
|
||||
smallest = 1; // rhs goes to 1
|
||||
assert multiply(s1, s2).intValue() == 1;
|
||||
}
|
||||
}
|
||||
else if (DMath.isInversable(s2))
|
||||
{
|
||||
Number newOne = modInverse(s2);
|
||||
if (multiply(newOne, smallest).equals(s1))
|
||||
{
|
||||
s1 = newOne;
|
||||
smallest = 1;
|
||||
assert multiply(s1, s2).intValue() == 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we have two constants, figure out which one is
|
||||
// the getter and which is the setter
|
||||
boolean g = isGetterOrSetter(field, true, s1),
|
||||
g2 = isGetterOrSetter(field, true, s2);
|
||||
|
||||
boolean inverse = false;
|
||||
if (g == g2)
|
||||
{
|
||||
// both are getters
|
||||
inverse = true;
|
||||
g = isGetterOrSetter(field, false, s1);
|
||||
g2 = isGetterOrSetter(field, false, s2);
|
||||
}
|
||||
|
||||
if (g == g2)
|
||||
{
|
||||
// special case, when the guessted value is correct but
|
||||
// negated, setters and getters will both contain -1,
|
||||
// and the guess will be -1/-1.
|
||||
if (s1.longValue() == -1L && s2.longValue() == -1L)
|
||||
{
|
||||
Pair p = new Pair();
|
||||
p.field = field.getPoolField();
|
||||
p.getter = s1;
|
||||
p.setter = s2;
|
||||
return p;
|
||||
}
|
||||
// both are also setters
|
||||
}
|
||||
else
|
||||
{
|
||||
Pair p = new Pair();
|
||||
p.field = field.getPoolField();
|
||||
if (g != inverse)
|
||||
{
|
||||
p.getter = s1;
|
||||
p.setter = s2;
|
||||
}
|
||||
else
|
||||
{
|
||||
p.getter = s2;
|
||||
p.setter = s1;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// figure out if value is a getter or setter
|
||||
private boolean isGetterOrSetter(Field field, boolean getter, Number value)
|
||||
{
|
||||
FieldInfo fieldInfo = getFieldInfo(field);
|
||||
|
||||
Collection<Number> c;
|
||||
if (getter)
|
||||
{
|
||||
c = fieldInfo.getters;
|
||||
}
|
||||
else
|
||||
{
|
||||
c = fieldInfo.setters;
|
||||
}
|
||||
if (c == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c.contains(value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void guess()
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Field f : cf.getFields())
|
||||
{
|
||||
FieldInfo fieldInfo = getFieldInfo(f);
|
||||
|
||||
Collection<AssociatedConstant> col = fieldInfo.constants; // all constants in instructions associated with the field
|
||||
if (col.isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Type type = f.getType();
|
||||
assert type.equals(Type.INT) || type.equals(Type.LONG);
|
||||
|
||||
Class typeOfField = type.equals(Type.INT) ? Integer.class : Long.class;
|
||||
|
||||
// filter collect constants of the correct type
|
||||
Collection<AssociatedConstant> col2 = col.stream()
|
||||
.filter(i -> i.value.getClass() == typeOfField)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// filer out ones that have another field in the expression
|
||||
List<Number> noOther = col2.stream()
|
||||
.filter(i -> !i.other && !i.constant)
|
||||
.map(i -> i.value)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
List<Number> other = col2.stream()
|
||||
.filter(i -> i.other || i.constant)
|
||||
.map(i -> i.value)
|
||||
.collect(Collectors.toList());
|
||||
other.addAll(noOther);
|
||||
other = ImmutableSet.copyOf(other).asList();
|
||||
|
||||
// guess with constants not associated with other fields
|
||||
Pair p = this.guess(f, noOther);
|
||||
if (p == null)
|
||||
{
|
||||
p = this.guess(f, other); // fall back to all constants
|
||||
}
|
||||
|
||||
// check that this guess doesn't increase constants
|
||||
if (p != null && !fieldInfo.guessDecreasesConstants(p))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (p != null)
|
||||
{
|
||||
pairs.add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
private void insertGetterSetterMuls(Encryption encr)
|
||||
{
|
||||
// after getfield insert imul * setter
|
||||
// before setfield insert imul * getter
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
Code code = m.getCode();
|
||||
if (code == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Instructions ins = code.getInstructions();
|
||||
List<Instruction> ilist = ins.getInstructions();
|
||||
|
||||
for (int i = 0; i < ilist.size(); ++i)
|
||||
{
|
||||
Instruction in = ilist.get(i);
|
||||
|
||||
if (in instanceof SetFieldInstruction)
|
||||
{
|
||||
SetFieldInstruction sfi = (SetFieldInstruction) in;
|
||||
Field f = sfi.getMyField();
|
||||
|
||||
if (f == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Pair p = encr.getField(f.getPoolField());
|
||||
if (p == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// insert push getter
|
||||
// insert imul
|
||||
if (p.getType() == Integer.class)
|
||||
{
|
||||
ilist.add(i++, new LDC(ins, (int) p.getter));
|
||||
ilist.add(i++, new IMul(ins));
|
||||
}
|
||||
else if (p.getType() == Long.class)
|
||||
{
|
||||
ilist.add(i++, new LDC(ins, (long) p.getter));
|
||||
ilist.add(i++, new LMul(ins));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
else if (in instanceof GetFieldInstruction)
|
||||
{
|
||||
GetFieldInstruction sfi = (GetFieldInstruction) in;
|
||||
Field f = sfi.getMyField();
|
||||
|
||||
if (f == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Pair p = encr.getField(f.getPoolField());
|
||||
if (p == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// add after:
|
||||
// push setter
|
||||
// imul
|
||||
if (p.getType() == Integer.class)
|
||||
{
|
||||
ilist.add(++i, new LDC(ins, (int) p.setter));
|
||||
ilist.add(++i, new IMul(ins));
|
||||
}
|
||||
else if (p.getType() == Long.class)
|
||||
{
|
||||
ilist.add(++i, new LDC(ins, (long) p.setter));
|
||||
ilist.add(++i, new LMul(ins));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int runOnce()
|
||||
{
|
||||
group.buildClassGraph();
|
||||
|
||||
pairs.clear();
|
||||
fieldInfo.clear();
|
||||
|
||||
execution = new Execution(group);
|
||||
execution.addMethodContextVisitor(i -> findUses(i));
|
||||
execution.addMethodContextVisitor(i -> findConstants(i));
|
||||
execution.populateInitialMethods();
|
||||
execution.run();
|
||||
|
||||
guess();
|
||||
|
||||
int i = 0;
|
||||
Encryption encr = new Encryption();
|
||||
for (Pair pair : pairs)
|
||||
{
|
||||
logger.debug("Processing {} getter {} setter {}", pair.field.getName(), pair.getter, pair.setter);
|
||||
|
||||
encr.addPair(pair);
|
||||
encryption.addPair(pair); // sum total
|
||||
++i;
|
||||
}
|
||||
|
||||
logger.info("Done processing {}", i);
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
insertGetterSetterMuls(encr);
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
public void annotateEncryption()
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Field f : cf.getFields())
|
||||
{
|
||||
Pair pair = encryption.getField(f.getPoolField());
|
||||
if (pair == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pair.getter.longValue() == 1L)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
String ename = pair.getType() == Long.class
|
||||
? "longValue"
|
||||
: "intValue";
|
||||
f.getAnnotations().addAnnotation(DeobAnnotations.OBFUSCATED_GETTER, ename, pair.getter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Encryption getEncryption()
|
||||
{
|
||||
return encryption;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.arithmetic;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.DupInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.GetFieldInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
|
||||
import net.runelite.asm.attributes.code.instructions.BiPush;
|
||||
import net.runelite.asm.attributes.code.instructions.IAdd;
|
||||
import net.runelite.asm.attributes.code.instructions.IInc;
|
||||
import net.runelite.asm.attributes.code.instructions.IMul;
|
||||
import net.runelite.asm.attributes.code.instructions.ISub;
|
||||
import net.runelite.asm.attributes.code.instructions.LAdd;
|
||||
import net.runelite.asm.attributes.code.instructions.LDC;
|
||||
import net.runelite.asm.attributes.code.instructions.LMul;
|
||||
import net.runelite.asm.attributes.code.instructions.LSub;
|
||||
import net.runelite.asm.attributes.code.instructions.SiPush;
|
||||
import net.runelite.asm.attributes.code.instructions.Swap;
|
||||
import net.runelite.asm.execution.Execution;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
import net.runelite.asm.execution.MethodContext;
|
||||
import net.runelite.asm.execution.StackContext;
|
||||
import net.runelite.asm.execution.VariableContext;
|
||||
import net.runelite.asm.execution.Variables;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MultiplicationDeobfuscator implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(MultiplicationDeobfuscator.class);
|
||||
|
||||
private ClassGroup group;
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
this.group = group;
|
||||
|
||||
int i;
|
||||
int count = 0;
|
||||
while ((i = runOnce()) > 0)
|
||||
{
|
||||
logger.info("Replaced " + i + " constants");
|
||||
count += i;
|
||||
}
|
||||
logger.info("Total changed " + count);
|
||||
}
|
||||
|
||||
public static MultiplicationExpression parseExpression(InstructionContext ctx, Class want)
|
||||
{
|
||||
MultiplicationExpression me = new MultiplicationExpression();
|
||||
|
||||
if (ctx.getInstruction() instanceof LVTInstruction)
|
||||
{
|
||||
LVTInstruction lvt = (LVTInstruction) ctx.getInstruction();
|
||||
|
||||
// loading a variable
|
||||
if (!lvt.store())
|
||||
{
|
||||
int idx = lvt.getVariableIndex(); // var index
|
||||
Variables vars = ctx.getVariables(); // variables at time of execution
|
||||
|
||||
VariableContext vctx = vars.get(idx); // get the variable
|
||||
|
||||
if (vctx.getRead().size() == 1) // ?
|
||||
{
|
||||
InstructionContext storeCtx = vctx.getInstructionWhichStored(); // this is an istore
|
||||
if (storeCtx.getInstruction() instanceof LVTInstruction)
|
||||
{
|
||||
// invoking funcs can put stuff in lvt
|
||||
|
||||
LVTInstruction storelvt = (LVTInstruction) storeCtx.getInstruction();
|
||||
|
||||
if (storelvt instanceof IInc)
|
||||
throw new IllegalStateException();
|
||||
|
||||
assert storelvt.store();
|
||||
|
||||
InstructionContext pushed = storeCtx.getPops().get(0).getPushed();
|
||||
return parseExpression(pushed, want);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.getInstruction() instanceof PushConstantInstruction)
|
||||
{
|
||||
if (ctx.getInstruction() instanceof BiPush || ctx.getInstruction() instanceof SiPush)
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
me.instructions.add(ctx);
|
||||
return me;
|
||||
}
|
||||
|
||||
for (StackContext sctx : ctx.getPops())
|
||||
{
|
||||
if (ctx.getInstruction().getClass() == want)
|
||||
{
|
||||
if (!isOnlyPath(ctx, sctx))
|
||||
continue;
|
||||
}
|
||||
|
||||
InstructionContext i = sctx.getPushed();
|
||||
|
||||
// if this instruction is imul, look at pops
|
||||
if (ctx.getInstruction().getClass() == want)
|
||||
{
|
||||
if (i.getInstruction() instanceof Swap)
|
||||
{
|
||||
logger.debug("Resolving swap");
|
||||
|
||||
Swap swap = (Swap) i.getInstruction();
|
||||
sctx = swap.getOriginal(sctx);
|
||||
i = sctx.getPushed();
|
||||
}
|
||||
|
||||
if (i.getInstruction() instanceof PushConstantInstruction)
|
||||
{
|
||||
// bipush/sipush are always not obfuscated
|
||||
if (i.getInstruction() instanceof BiPush || i.getInstruction() instanceof SiPush)
|
||||
continue;
|
||||
|
||||
// a constant of imul
|
||||
me.instructions.add(i);
|
||||
}
|
||||
else if (i.getInstruction().getClass() == want)
|
||||
{
|
||||
// chained imul, append to me
|
||||
try
|
||||
{
|
||||
MultiplicationExpression other = parseExpression(i, want);
|
||||
if (other.dupmagic != null)
|
||||
{
|
||||
assert me.dupmagic == null;
|
||||
me.dupmagic = other.dupmagic;
|
||||
}
|
||||
|
||||
me.instructions.addAll(other.instructions);
|
||||
me.dupedInstructions.addAll(other.dupedInstructions);
|
||||
me.subexpressions.addAll(other.subexpressions);
|
||||
}
|
||||
catch (IllegalStateException ex)
|
||||
{
|
||||
// this is ok? just don't include it?
|
||||
}
|
||||
}
|
||||
else if (i.getInstruction() instanceof IAdd || i.getInstruction() instanceof ISub
|
||||
|| i.getInstruction() instanceof LAdd || i.getInstruction() instanceof LSub)
|
||||
{
|
||||
// imul using result of iadd or isub. evaluate expression
|
||||
try
|
||||
{
|
||||
MultiplicationExpression other = parseExpression(i, want);
|
||||
assert other.dupmagic == null;
|
||||
|
||||
// subexpr
|
||||
me.subexpressions.add(other);
|
||||
}
|
||||
catch (IllegalStateException ex)
|
||||
{
|
||||
assert me.subexpressions.isEmpty();
|
||||
// subexpression is too complex. we can still simplify the top level though
|
||||
}
|
||||
}
|
||||
else if (i.getInstruction() instanceof DupInstruction)
|
||||
{
|
||||
DupInstruction dup = (DupInstruction) i.getInstruction();
|
||||
|
||||
// find other branch of the dup instruction
|
||||
// sctx = what dup pushed, find other
|
||||
StackContext otherCtx = dup.getOtherBranch(sctx); // other side of dup
|
||||
InstructionContext otherCtxI = otherCtx.getPopped().get(0); // what popped other side of dup. is this right?
|
||||
|
||||
if (otherCtxI.getInstruction().getClass() == want)
|
||||
{
|
||||
//assert otherCtxI.getInstruction() instanceof IMul;
|
||||
|
||||
InstructionContext pushConstant = otherCtxI.getPops().get(0).getPushed(); // other side of that imul
|
||||
assert pushConstant.getInstruction() instanceof LDC;
|
||||
|
||||
me.dupmagic = pushConstant;
|
||||
|
||||
StackContext orig = dup.getOriginal(sctx); // original
|
||||
try
|
||||
{
|
||||
MultiplicationExpression other = parseExpression(orig.getPushed(), want);
|
||||
// this expression is used elsewhere like 'pushConstant' so any changes
|
||||
// done to it affect that, too. so multiply it by existing values?
|
||||
if (orig.getPushed().getInstruction() instanceof IAdd || orig.getPushed().getInstruction() instanceof ISub
|
||||
|| orig.getPushed().getInstruction() instanceof LAdd || orig.getPushed().getInstruction() instanceof LSub)
|
||||
{
|
||||
me.subexpressions.add(other);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert other.dupmagic == null;
|
||||
me.instructions.addAll(other.instructions);
|
||||
me.dupedInstructions.addAll(other.instructions);
|
||||
me.subexpressions.addAll(other.subexpressions);
|
||||
}
|
||||
}
|
||||
catch (IllegalStateException ex)
|
||||
{
|
||||
assert me.subexpressions.isEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i.getInstruction() instanceof GetFieldInstruction)
|
||||
{
|
||||
me.fieldInstructions.add(i);
|
||||
// non constant, ignore
|
||||
}
|
||||
else
|
||||
{
|
||||
//System.out.println("imul pops something I don't know " + i.getInstruction());
|
||||
}
|
||||
}
|
||||
// this is an iadd/sub
|
||||
else if (ctx.getInstruction() instanceof IAdd || ctx.getInstruction() instanceof ISub
|
||||
|| ctx.getInstruction() instanceof LAdd || ctx.getInstruction() instanceof LSub)
|
||||
{
|
||||
MultiplicationExpression other = parseExpression(i, want); // parse this side of the add/sub
|
||||
|
||||
me.subexpressions.add(other);
|
||||
}
|
||||
else
|
||||
{
|
||||
//System.out.println(ctx.getInstruction() + " pops something I dont know " + i.getInstruction());
|
||||
}
|
||||
}
|
||||
|
||||
if (me.instructions.isEmpty() && me.subexpressions.isEmpty())
|
||||
throw new IllegalStateException();
|
||||
|
||||
return me;
|
||||
}
|
||||
|
||||
// one = imul, two = other executeion of instruction of one, sctx = popped by imul
|
||||
private static boolean ictxEqualsDir(InstructionContext one, InstructionContext two, StackContext sctx)
|
||||
{
|
||||
assert one.getInstruction() == two.getInstruction();
|
||||
|
||||
if (one.getInstruction() != two.getInstruction())
|
||||
return false;
|
||||
|
||||
assert one.getPops().contains(sctx);
|
||||
int i = one.getPops().indexOf(sctx);
|
||||
|
||||
StackContext theirsctx = two.getPops().get(i);
|
||||
|
||||
if (sctx.getPushed().getInstruction() != theirsctx.getPushed().getInstruction())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ctx = imul, sctx = popped by imul
|
||||
public static boolean isOnlyPath(InstructionContext ctx, StackContext sctx)
|
||||
{
|
||||
assert ctx.getInstruction() instanceof IMul || ctx.getInstruction() instanceof LMul;
|
||||
|
||||
Collection<InstructionContext> ins = ctx.getFrame().getMethodCtx().getInstructonContexts(ctx.getInstruction());
|
||||
for (InstructionContext i : ins)
|
||||
{
|
||||
if (sctx == null)
|
||||
{
|
||||
if (!i.equals(ctx))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (StackContext sctx2 : i.getPushes())
|
||||
{
|
||||
if (sctx2.getPopped().size() > 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// everything which pushed the parameters must be the same
|
||||
if (!ictxEqualsDir(ctx, i, sctx))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// everything which pops the result must be the same
|
||||
Instruction poppedIns = null;
|
||||
for (StackContext s : i.getPushes())
|
||||
for (InstructionContext i2 : s.getPopped())
|
||||
{
|
||||
if (poppedIns == null)
|
||||
poppedIns = i2.getInstruction();
|
||||
else if (poppedIns != i2.getInstruction())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private Set<Instruction> done = new HashSet<>();
|
||||
|
||||
private void visit(MethodContext ctx)
|
||||
{
|
||||
for (InstructionContext ictx : ctx.getInstructionContexts())
|
||||
{
|
||||
Instruction instruction = ictx.getInstruction();
|
||||
|
||||
if (!(instruction instanceof IMul) && !(instruction instanceof LMul))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
MultiplicationExpression expression;
|
||||
try
|
||||
{
|
||||
expression = parseExpression(ictx, instruction.getClass());
|
||||
}
|
||||
catch (IllegalStateException ex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (expression == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (done.contains(instruction))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
done.add(instruction);
|
||||
|
||||
assert instruction instanceof IMul || instruction instanceof LMul;
|
||||
if (instruction instanceof IMul)
|
||||
{
|
||||
count += expression.simplify(1);
|
||||
}
|
||||
else if (instruction instanceof LMul)
|
||||
{
|
||||
count += expression.simplify(1L);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int count;
|
||||
|
||||
private int runOnce()
|
||||
{
|
||||
group.buildClassGraph();
|
||||
|
||||
count = 0;
|
||||
|
||||
Execution e = new Execution(group);
|
||||
e.addMethodContextVisitor(m -> visit(m));
|
||||
e.populateInitialMethods();
|
||||
e.run();
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.arithmetic;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.FieldInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
|
||||
public class MultiplicationExpression
|
||||
{
|
||||
List<InstructionContext> instructions = new ArrayList<>(), // push constant instructions that are being multiplied
|
||||
dupedInstructions = new ArrayList<>(),
|
||||
fieldInstructions = new ArrayList<>();
|
||||
List<MultiplicationExpression> subexpressions = new ArrayList<>(); // for distributing, each subexpr is * by this
|
||||
InstructionContext dupmagic; // inverse of what is distributed to subexpressions gets set here
|
||||
|
||||
int simplify(Number start)
|
||||
{
|
||||
int count = 0;
|
||||
Number result = start;
|
||||
|
||||
// calculate result
|
||||
for (InstructionContext i : instructions)
|
||||
{
|
||||
PushConstantInstruction pci = (PushConstantInstruction) i.getInstruction();
|
||||
Number value = (Number) pci.getConstant();
|
||||
|
||||
result = DMath.multiply(result, value);
|
||||
}
|
||||
|
||||
if (dupmagic != null)
|
||||
{
|
||||
// mul dupmagic by result of dup ins?
|
||||
|
||||
PushConstantInstruction pci = (PushConstantInstruction) dupmagic.getInstruction();
|
||||
Number value = (Number) pci.getConstant();
|
||||
|
||||
for (InstructionContext ic : dupedInstructions)
|
||||
{
|
||||
PushConstantInstruction pci2 = (PushConstantInstruction) ic.getInstruction();
|
||||
Number value2 = (Number) pci2.getConstant();
|
||||
|
||||
value = DMath.multiply(value, value2);
|
||||
}
|
||||
|
||||
Instruction newIns = pci.setConstant(value);
|
||||
assert newIns == (Instruction) pci;
|
||||
}
|
||||
|
||||
// multiply subexpressions by result
|
||||
if (!subexpressions.isEmpty())
|
||||
{
|
||||
for (MultiplicationExpression me : subexpressions)
|
||||
{
|
||||
count += me.simplify(result);
|
||||
}
|
||||
|
||||
if (dupmagic != null)
|
||||
{
|
||||
PushConstantInstruction pci = (PushConstantInstruction) dupmagic.getInstruction();
|
||||
Number value = (Number) pci.getConstant();
|
||||
|
||||
value = DMath.multiply(value, DMath.modInverse(result));
|
||||
|
||||
pci.setConstant(value);
|
||||
}
|
||||
|
||||
// constant has been distributed, outer numbers all go to 1
|
||||
if (result instanceof Long)
|
||||
result = 1L;
|
||||
else
|
||||
result = 1;
|
||||
}
|
||||
|
||||
// set result on ins
|
||||
for (InstructionContext i : instructions)
|
||||
{
|
||||
PushConstantInstruction pci = (PushConstantInstruction) i.getInstruction();
|
||||
Instruction newIns = pci.setConstant(result);
|
||||
++count;
|
||||
assert newIns == pci;
|
||||
// rest of the results go to 1
|
||||
if (result instanceof Long)
|
||||
result = 1L;
|
||||
else
|
||||
result = 1;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public boolean hasFieldOtherThan(Field field)
|
||||
{
|
||||
for (InstructionContext i : this.fieldInstructions)
|
||||
{
|
||||
FieldInstruction fi = (FieldInstruction) i.getInstruction();
|
||||
if (fi.getMyField() != null && fi.getMyField() != field)
|
||||
return true;
|
||||
}
|
||||
|
||||
for (MultiplicationExpression ex : this.subexpressions)
|
||||
if (ex.hasFieldOtherThan(field))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.arithmetic;
|
||||
|
||||
import java.util.List;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
|
||||
import net.runelite.asm.attributes.code.instructions.IMul;
|
||||
import net.runelite.asm.attributes.code.instructions.LMul;
|
||||
import net.runelite.asm.execution.Execution;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
import net.runelite.asm.execution.MethodContext;
|
||||
import net.runelite.asm.execution.StackContext;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MultiplyOneDeobfuscator implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(MultiplyOneDeobfuscator.class);
|
||||
|
||||
private final boolean onlyConstants;
|
||||
private int count;
|
||||
|
||||
public MultiplyOneDeobfuscator(boolean onlyConstants)
|
||||
{
|
||||
this.onlyConstants = onlyConstants;
|
||||
}
|
||||
|
||||
private void visit(MethodContext mctx)
|
||||
{
|
||||
for (InstructionContext ictx : mctx.getInstructionContexts())
|
||||
{
|
||||
Instruction instruction = ictx.getInstruction();
|
||||
|
||||
if (!(instruction instanceof IMul) && !(instruction instanceof LMul))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Instructions ins = ictx.getInstruction().getInstructions();
|
||||
if (ins == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
List<Instruction> ilist = ins.getInstructions();
|
||||
|
||||
if (!ilist.contains(ictx.getInstruction()))
|
||||
{
|
||||
continue; // already done
|
||||
}
|
||||
|
||||
StackContext one = ictx.getPops().get(0);
|
||||
StackContext two = ictx.getPops().get(1);
|
||||
|
||||
StackContext other = null;
|
||||
int removeIdx = -1;
|
||||
if (one.getPushed().getInstruction() instanceof PushConstantInstruction
|
||||
&& DMath.equals((Number) ((PushConstantInstruction) one.getPushed().getInstruction()).getConstant(), 1))
|
||||
{
|
||||
removeIdx = 0;
|
||||
other = two;
|
||||
}
|
||||
else if (two.getPushed().getInstruction() instanceof PushConstantInstruction
|
||||
&& DMath.equals((Number) ((PushConstantInstruction) two.getPushed().getInstruction()).getConstant(), 1))
|
||||
{
|
||||
removeIdx = 1;
|
||||
other = one;
|
||||
}
|
||||
|
||||
if (removeIdx == -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (onlyConstants && !(other.getPushed().getInstruction() instanceof PushConstantInstruction))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!MultiplicationDeobfuscator.isOnlyPath(ictx, removeIdx == 0 ? one : two))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ictx.removeStack(removeIdx); // remove 1
|
||||
ins.remove(instruction); // remove mul
|
||||
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
Execution e = new Execution(group);
|
||||
e.addMethodContextVisitor(i -> visit(i));
|
||||
e.populateInitialMethods();
|
||||
e.run();
|
||||
|
||||
logger.info("Removed " + count + " 1 multiplications");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.arithmetic;
|
||||
|
||||
import java.util.List;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
|
||||
import net.runelite.asm.attributes.code.instructions.IMul;
|
||||
import net.runelite.asm.attributes.code.instructions.LDC;
|
||||
import net.runelite.asm.attributes.code.instructions.LMul;
|
||||
import net.runelite.asm.execution.Execution;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
import net.runelite.asm.execution.MethodContext;
|
||||
import net.runelite.asm.execution.StackContext;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MultiplyZeroDeobfuscator implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(MultiplyZeroDeobfuscator.class);
|
||||
|
||||
private int count;
|
||||
|
||||
private void visit(MethodContext mctx)
|
||||
{
|
||||
for (InstructionContext ictx : mctx.getInstructionContexts())
|
||||
{
|
||||
Instruction instruction = ictx.getInstruction();
|
||||
Instructions ins = instruction.getInstructions();
|
||||
if (ins == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(instruction instanceof IMul) && !(instruction instanceof LMul))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
List<Instruction> ilist = ins.getInstructions();
|
||||
|
||||
StackContext one = ictx.getPops().get(0);
|
||||
StackContext two = ictx.getPops().get(1);
|
||||
|
||||
Instruction ione = one.getPushed().getInstruction(),
|
||||
itwo = two.getPushed().getInstruction();
|
||||
|
||||
boolean remove = false;
|
||||
if (ione instanceof PushConstantInstruction)
|
||||
{
|
||||
PushConstantInstruction pci = (PushConstantInstruction) ione;
|
||||
Number value = (Number) pci.getConstant();
|
||||
|
||||
if (DMath.equals(value, 0))
|
||||
{
|
||||
remove = true;
|
||||
}
|
||||
}
|
||||
if (itwo instanceof PushConstantInstruction)
|
||||
{
|
||||
PushConstantInstruction pci = (PushConstantInstruction) itwo;
|
||||
Number value = (Number) pci.getConstant();
|
||||
|
||||
if (DMath.equals(value, 0))
|
||||
{
|
||||
remove = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (remove == false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ilist.contains(instruction))
|
||||
{
|
||||
continue; // already done
|
||||
}
|
||||
if (!MultiplicationDeobfuscator.isOnlyPath(ictx, null))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// remove both, remove imul, push 0
|
||||
ictx.removeStack(1);
|
||||
ictx.removeStack(0);
|
||||
|
||||
if (instruction instanceof IMul)
|
||||
{
|
||||
ins.replace(instruction, new LDC(ins, 0));
|
||||
}
|
||||
else if (instruction instanceof LMul)
|
||||
{
|
||||
ins.replace(instruction, new LDC(ins, 0L));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
++count;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
Execution e = new Execution(group);
|
||||
e.addMethodContextVisitor(i -> visit(i));
|
||||
e.populateInitialMethods();
|
||||
e.run();
|
||||
|
||||
logger.info("Removed " + count + " 0 multiplications");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.arithmetic;
|
||||
|
||||
import net.runelite.asm.pool.Field;
|
||||
|
||||
class Pair
|
||||
{
|
||||
Field field;
|
||||
Number getter, setter;
|
||||
|
||||
public Pair()
|
||||
{
|
||||
}
|
||||
|
||||
public Pair(Pair one, Pair two)
|
||||
{
|
||||
assert one.getType() == two.getType();
|
||||
assert one.field != null;
|
||||
assert two.field != null;
|
||||
assert one.field.equals(two.field);
|
||||
|
||||
getter = DMath.multiply(one.getter, two.getter);
|
||||
setter = DMath.multiply(one.setter, two.setter);
|
||||
field = one.field;
|
||||
}
|
||||
|
||||
public Class getType()
|
||||
{
|
||||
assert getter.getClass() == setter.getClass();
|
||||
return getter.getClass();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.cfg;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
|
||||
public class Block
|
||||
{
|
||||
private int id = -1;
|
||||
private boolean jumptarget; // block is unconditionally jumped to
|
||||
|
||||
/**
|
||||
* blocks which jump here
|
||||
*/
|
||||
private final List<Block> prev = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* blocks which this jumps to
|
||||
*/
|
||||
private final List<Block> next = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* block which flows directly into this block
|
||||
*/
|
||||
private Block flowsFrom;
|
||||
|
||||
/**
|
||||
* block which this directly flows into
|
||||
*/
|
||||
private Block flowsInto;
|
||||
|
||||
/**
|
||||
* instructions in this block
|
||||
*/
|
||||
private final List<Instruction> instructions = new ArrayList<>();
|
||||
|
||||
public int getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public boolean isJumptarget()
|
||||
{
|
||||
return jumptarget;
|
||||
}
|
||||
|
||||
public void setJumptarget(boolean jumptarget)
|
||||
{
|
||||
this.jumptarget = jumptarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Block ID ").append(id).append("\n");
|
||||
if (flowsFrom != null)
|
||||
{
|
||||
sb.append(" flows from ").append(flowsFrom.id).append("\n");
|
||||
}
|
||||
for (Instruction i : instructions)
|
||||
{
|
||||
sb.append(" ").append(i.toString()).append("\n");
|
||||
}
|
||||
if (flowsInto != null)
|
||||
{
|
||||
sb.append(" flows into ").append(flowsInto.id).append("\n");
|
||||
}
|
||||
sb.append("\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void addInstruction(int idx, Instruction i)
|
||||
{
|
||||
assert !instructions.contains(i);
|
||||
instructions.add(idx, i);
|
||||
}
|
||||
|
||||
public void addInstruction(Instruction i)
|
||||
{
|
||||
assert !instructions.contains(i);
|
||||
instructions.add(i);
|
||||
}
|
||||
|
||||
public List<Instruction> getInstructions()
|
||||
{
|
||||
return instructions;
|
||||
}
|
||||
|
||||
public void addPrev(Block block)
|
||||
{
|
||||
if (!prev.contains(block))
|
||||
{
|
||||
prev.add(block);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Block> getPrev()
|
||||
{
|
||||
return prev;
|
||||
}
|
||||
|
||||
public void addNext(Block block)
|
||||
{
|
||||
if (!next.contains(block))
|
||||
{
|
||||
next.add(block);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Block> getNext()
|
||||
{
|
||||
return next;
|
||||
}
|
||||
|
||||
public Block getFlowsFrom()
|
||||
{
|
||||
return flowsFrom;
|
||||
}
|
||||
|
||||
public void setFlowsFrom(Block flowsFrom)
|
||||
{
|
||||
this.flowsFrom = flowsFrom;
|
||||
}
|
||||
|
||||
public Block getFlowsInto()
|
||||
{
|
||||
return flowsInto;
|
||||
}
|
||||
|
||||
public void setFlowsInto(Block flowsInto)
|
||||
{
|
||||
this.flowsInto = flowsInto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.cfg;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Queue;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Exception;
|
||||
import net.runelite.asm.attributes.code.Exceptions;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.Label;
|
||||
import net.runelite.asm.attributes.code.instructions.Goto;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ControlFlowDeobfuscator implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(ControlFlowDeobfuscator.class);
|
||||
|
||||
private int insertedJump;
|
||||
private int placedBlocks;
|
||||
private int removedJumps;
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
Code code = m.getCode();
|
||||
|
||||
if (code == null || !code.getExceptions().getExceptions().isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
split(code);
|
||||
run(code);
|
||||
runJumpLabel(code);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Inserted {} jumps, reordered {} blocks, and removed {} jumps. jump delta {}",
|
||||
insertedJump, placedBlocks, removedJumps, insertedJump - removedJumps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add gotos at the end of blocks without terminal instructions
|
||||
*
|
||||
* @param code
|
||||
*/
|
||||
private void split(Code code)
|
||||
{
|
||||
Instructions ins = code.getInstructions();
|
||||
Exceptions exceptions = code.getExceptions();
|
||||
|
||||
ControlFlowGraph graph = new ControlFlowGraph.Builder().build(code);
|
||||
|
||||
List<Exception> exc = new ArrayList<>(exceptions.getExceptions());
|
||||
|
||||
exceptions.clear(); // Must clear this before ins.clear() runs
|
||||
ins.clear();
|
||||
|
||||
// insert jumps where blocks flow into others
|
||||
for (Block block : graph.getBlocks())
|
||||
{
|
||||
if (block.getFlowsInto() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Block into = block.getFlowsInto();
|
||||
assert into.getFlowsFrom() == block;
|
||||
|
||||
Instruction first = into.getInstructions().get(0);
|
||||
Label label;
|
||||
if (!(first instanceof Label))
|
||||
{
|
||||
label = new Label(null);
|
||||
into.addInstruction(0, label);
|
||||
}
|
||||
else
|
||||
{
|
||||
label = (Label) first;
|
||||
}
|
||||
|
||||
Goto g = new Goto(null, label);
|
||||
block.addInstruction(g);
|
||||
|
||||
block.setFlowsInto(null);
|
||||
into.setFlowsFrom(null);
|
||||
|
||||
++insertedJump;
|
||||
}
|
||||
|
||||
// Readd instructions from modified blocks
|
||||
for (Block block : graph.getBlocks())
|
||||
{
|
||||
for (Instruction i : block.getInstructions())
|
||||
{
|
||||
assert i.getInstructions() == null;
|
||||
i.setInstructions(ins); // I shouldn't have to do this here
|
||||
ins.addInstruction(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Readd exceptions
|
||||
for (Exception ex : exc)
|
||||
{
|
||||
exceptions.add(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private int compareBlock(Block o1, Block o2)
|
||||
{
|
||||
// higher numbers have the lowest priority
|
||||
if (o1.isJumptarget() && !o2.isJumptarget())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (o2.isJumptarget() && !o1.isJumptarget())
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void run(Code code)
|
||||
{
|
||||
Instructions ins = code.getInstructions();
|
||||
Exceptions exceptions = code.getExceptions();
|
||||
|
||||
ControlFlowGraph graph = new ControlFlowGraph.Builder().build(code);
|
||||
|
||||
for (Block block : graph.getBlocks())
|
||||
{
|
||||
assert block.getFlowsFrom() == null;
|
||||
assert block.getFlowsInto() == null;
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) // graph.toString() is expensive
|
||||
{
|
||||
logger.debug(graph.toString());
|
||||
}
|
||||
|
||||
List<Exception> originalExceptions = new ArrayList<>(exceptions.getExceptions());
|
||||
|
||||
// Clear existing exceptions and instructions as we are going to
|
||||
// rebuild them
|
||||
exceptions.clear();
|
||||
ins.clear();
|
||||
|
||||
List<Block> done = new ArrayList<>();
|
||||
Queue<Block> queue = new PriorityQueue<>(this::compareBlock);
|
||||
|
||||
// add initial code block
|
||||
queue.add(graph.getHead());
|
||||
|
||||
while (!queue.isEmpty())
|
||||
{
|
||||
Block block = queue.remove();
|
||||
|
||||
if (done.contains(block))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
done.add(block);
|
||||
++placedBlocks;
|
||||
|
||||
logger.debug("Placed block {}", block.getId());
|
||||
|
||||
List<Block> next = block.getNext();
|
||||
|
||||
if (next.isEmpty() == false)
|
||||
{
|
||||
// jumps are added in order their instructions are reached by ControlFlowGraph,
|
||||
// so the last jump is the goto.
|
||||
//
|
||||
// removing this line causes the priority queue (due to implementation detail on how
|
||||
// it handles objects with equal priority) to try to optimize for block closeness
|
||||
// (how close blocks which are neighbors are to each other in bytecode).
|
||||
// I get a jump delta of ~+14k with this on 143, vs ~-47k when priotiziing optimizing
|
||||
// out jumps. I can't tell which is better.
|
||||
next.get(next.size() - 1).setJumptarget(true);
|
||||
}
|
||||
|
||||
// add next reachable blocks
|
||||
for (Block bl : next)
|
||||
{
|
||||
queue.add(bl);
|
||||
}
|
||||
|
||||
for (Instruction i : block.getInstructions())
|
||||
{
|
||||
assert i.getInstructions() == null;
|
||||
i.setInstructions(ins); // I shouldn't have to do this here
|
||||
ins.addInstruction(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* remove jumps followed immediately by the label they are jumping to
|
||||
*
|
||||
* @param code
|
||||
*/
|
||||
private void runJumpLabel(Code code)
|
||||
{
|
||||
Instructions ins = code.getInstructions();
|
||||
List<Instruction> instructions = ins.getInstructions();
|
||||
|
||||
for (int i = 0; i < instructions.size() - 1; ++i)
|
||||
{
|
||||
Instruction i1 = instructions.get(i),
|
||||
i2 = instructions.get(i + 1);
|
||||
|
||||
if (!(i1 instanceof Goto))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Goto g = (Goto) i1;
|
||||
assert g.getJumps().size() == 1;
|
||||
if (g.getJumps().get(0) != i2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ins.remove(i1); // remove jump
|
||||
++removedJumps;
|
||||
|
||||
// i now points to i2, so next loop we go to next instruction
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.cfg;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.Label;
|
||||
import net.runelite.asm.attributes.code.instruction.types.JumpingInstruction;
|
||||
|
||||
public class ControlFlowGraph
|
||||
{
|
||||
private Map<Label, Block> blocks = new HashMap<>();
|
||||
private List<Block> allBlocks = new ArrayList<>();
|
||||
private final Block head;
|
||||
|
||||
public ControlFlowGraph(Block head)
|
||||
{
|
||||
this.head = head;
|
||||
}
|
||||
|
||||
public Block getBlock(Label label)
|
||||
{
|
||||
return blocks.get(label);
|
||||
}
|
||||
|
||||
public Collection<Block> getBlocks()
|
||||
{
|
||||
return allBlocks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Block b : allBlocks)
|
||||
{
|
||||
sb.append(b.toString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public Block getHead()
|
||||
{
|
||||
return head;
|
||||
}
|
||||
|
||||
public static class Builder
|
||||
{
|
||||
private final Map<Label, Block> blocks = new HashMap<>();
|
||||
private final List<Block> allBlocks = new ArrayList<>();
|
||||
|
||||
public ControlFlowGraph build(Code code)
|
||||
{
|
||||
int id = 0;
|
||||
|
||||
Block head = new Block(),
|
||||
cur = head;
|
||||
allBlocks.add(head);
|
||||
|
||||
for (Instruction i : code.getInstructions().getInstructions())
|
||||
{
|
||||
if (i instanceof Label)
|
||||
{
|
||||
// blocks always begin at labels, so create initial blocks
|
||||
Block block = new Block();
|
||||
blocks.put((Label) i, block);
|
||||
allBlocks.add(block);
|
||||
}
|
||||
}
|
||||
|
||||
for (Instruction i : code.getInstructions().getInstructions())
|
||||
{
|
||||
if (i instanceof Label)
|
||||
{
|
||||
Block next = blocks.get((Label) i);
|
||||
assert next != null;
|
||||
|
||||
if (next.getId() == -1)
|
||||
{
|
||||
next.setId(id++);
|
||||
}
|
||||
|
||||
if (next != cur)
|
||||
{
|
||||
Instruction last = cur.getInstructions().isEmpty()
|
||||
? null
|
||||
: cur.getInstructions().get(cur.getInstructions().size() - 1);
|
||||
|
||||
if (last == null || !last.isTerminal())
|
||||
{
|
||||
assert next.getFlowsFrom() == null;
|
||||
assert cur.getFlowsInto() == null;
|
||||
|
||||
// previous block flows directly into next
|
||||
next.setFlowsFrom(cur);
|
||||
cur.setFlowsInto(next);
|
||||
}
|
||||
|
||||
cur = next;
|
||||
}
|
||||
}
|
||||
|
||||
cur.addInstruction(i);
|
||||
|
||||
if (i instanceof JumpingInstruction)
|
||||
{
|
||||
JumpingInstruction ji = (JumpingInstruction) i;
|
||||
|
||||
for (Label l : ji.getJumps())
|
||||
{
|
||||
Block next = blocks.get(l);
|
||||
|
||||
if (next.getId() == -1)
|
||||
{
|
||||
next.setId(id++);
|
||||
}
|
||||
|
||||
cur.addNext(next);
|
||||
next.addPrev(cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert head != null : "no instructions in code";
|
||||
assert head.getFlowsFrom() == null;
|
||||
|
||||
ControlFlowGraph cfg = new ControlFlowGraph(head);
|
||||
cfg.blocks = blocks;
|
||||
cfg.allBlocks = allBlocks;
|
||||
return cfg;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.constparam;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
|
||||
class ConstantMethodParameter
|
||||
{
|
||||
List<Method> methods; // methods this is a parameter for
|
||||
int paramIndex;
|
||||
int lvtIndex;
|
||||
List<Number> values = new ArrayList<>(); // possible values for the parameter
|
||||
List<Instruction> operations = new ArrayList<>(); // conditional jumps based on the parameter
|
||||
Boolean result; // result of the jumps (branch taken or not)
|
||||
boolean invalid;
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
int hash = 3;
|
||||
hash = 47 * hash + Objects.hashCode(this.methods);
|
||||
hash = 47 * hash + this.lvtIndex;
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
final ConstantMethodParameter other = (ConstantMethodParameter) obj;
|
||||
if (!Objects.equals(this.methods, other.methods))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (this.lvtIndex != other.lvtIndex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,483 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.constparam;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Annotations;
|
||||
import net.runelite.asm.attributes.annotation.Annotation;
|
||||
import net.runelite.asm.attributes.annotation.Element;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.InstructionType;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instruction.types.ComparisonInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.JumpingInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
|
||||
import net.runelite.asm.attributes.code.instructions.Goto;
|
||||
import net.runelite.asm.attributes.code.instructions.If;
|
||||
import net.runelite.asm.attributes.code.instructions.If0;
|
||||
import net.runelite.asm.execution.Execution;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
import net.runelite.asm.execution.MethodContext;
|
||||
import net.runelite.asm.execution.StackContext;
|
||||
import net.runelite.deob.DeobAnnotations;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ConstantParameter implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(ConstantParameter.class);
|
||||
|
||||
private Map<ConstantMethodParameter, ConstantMethodParameter> parameters = new HashMap<>();
|
||||
private Multimap<Method, ConstantMethodParameter> mparams = HashMultimap.create();
|
||||
|
||||
private void checkMethodsAreConsistent(List<Method> methods)
|
||||
{
|
||||
Method prev = null;
|
||||
for (Method m : methods)
|
||||
{
|
||||
if (prev != null)
|
||||
{
|
||||
assert prev.getDescriptor().equals(m.getDescriptor());
|
||||
assert prev.isStatic() == m.isStatic();
|
||||
}
|
||||
prev = m;
|
||||
}
|
||||
}
|
||||
|
||||
private ConstantMethodParameter getCMPFor(List<Method> methods, int paramIndex, int lvtIndex)
|
||||
{
|
||||
ConstantMethodParameter cmp = new ConstantMethodParameter();
|
||||
cmp.methods = methods;
|
||||
cmp.paramIndex = paramIndex;
|
||||
cmp.lvtIndex = lvtIndex;
|
||||
|
||||
ConstantMethodParameter exists = parameters.get(cmp);
|
||||
if (exists != null)
|
||||
{
|
||||
// existing mparams for each method of methods can have different 'methods'
|
||||
// due to invokespecial on virtual methods.
|
||||
return exists;
|
||||
}
|
||||
|
||||
parameters.put(cmp, cmp);
|
||||
|
||||
for (Method m : methods)
|
||||
{
|
||||
mparams.put(m, cmp);
|
||||
}
|
||||
|
||||
return cmp;
|
||||
}
|
||||
|
||||
// find constant values passed as parameters
|
||||
private void findConstantParameter(List<Method> methods, InstructionContext invokeCtx)
|
||||
{
|
||||
checkMethodsAreConsistent(methods);
|
||||
|
||||
Method method = methods.get(0); // all methods must have the same signature etc
|
||||
int offset = method.isStatic() ? 0 : 1;
|
||||
|
||||
List<StackContext> pops = invokeCtx.getPops();
|
||||
|
||||
outer:
|
||||
// object is popped first, then param 1, 2, 3, etc. double and long take two slots.
|
||||
for (int lvtOffset = offset, parameterIndex = 0;
|
||||
parameterIndex < method.getDescriptor().size();
|
||||
lvtOffset += method.getDescriptor().getTypeOfArg(parameterIndex++).getSize())
|
||||
{
|
||||
// get(0) == first thing popped which is the last parameter,
|
||||
// get(descriptor.size() - 1) == first parameter
|
||||
StackContext ctx = pops.get(method.getDescriptor().size() - 1 - parameterIndex);
|
||||
|
||||
ConstantMethodParameter cmp = getCMPFor(methods, parameterIndex, lvtOffset);
|
||||
|
||||
if (cmp.invalid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ctx.getPushed().getInstruction() instanceof PushConstantInstruction)
|
||||
{
|
||||
PushConstantInstruction pc = (PushConstantInstruction) ctx.getPushed().getInstruction();
|
||||
|
||||
if (!(pc.getConstant() instanceof Number))
|
||||
{
|
||||
cmp.invalid = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
Number number = (Number) pc.getConstant();
|
||||
|
||||
if (!cmp.values.contains(number))
|
||||
{
|
||||
cmp.values.add((Number) pc.getConstant());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cmp.invalid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find constant valuess passed to parameters
|
||||
private void findParameters(InstructionContext ins)
|
||||
{
|
||||
if (!(ins.getInstruction() instanceof InvokeInstruction))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<Method> methods = ((InvokeInstruction) ins.getInstruction()).getMethods();
|
||||
if (methods.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
findConstantParameter(methods, ins);
|
||||
}
|
||||
|
||||
private List<ConstantMethodParameter> findParametersForMethod(Method m)
|
||||
{
|
||||
Collection<ConstantMethodParameter> c = mparams.get(m);
|
||||
if (c == null)
|
||||
{
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
return new ArrayList<>(c);
|
||||
}
|
||||
|
||||
// compare known values against a jump. also invalidates constant param
|
||||
// if lvt is reassigned or a comparison is made against a non constant
|
||||
private void findDeadParameters(MethodContext mctx)
|
||||
{
|
||||
mctx.getInstructionContexts().forEach(this::findDeadParameters);
|
||||
}
|
||||
|
||||
private void findDeadParameters(InstructionContext ins)
|
||||
{
|
||||
List<ConstantMethodParameter> parameters = this.findParametersForMethod(ins.getFrame().getMethod());
|
||||
|
||||
for (ConstantMethodParameter parameter : parameters)
|
||||
{
|
||||
int lvtIndex = parameter.lvtIndex;
|
||||
|
||||
if (parameter.invalid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ins.getInstruction() instanceof LVTInstruction)
|
||||
{
|
||||
LVTInstruction lvt = (LVTInstruction) ins.getInstruction();
|
||||
|
||||
if (lvt.getVariableIndex() != lvtIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lvt.store() || ins.getInstruction().getType() == InstructionType.IINC)
|
||||
{
|
||||
parameter.invalid = true;
|
||||
continue; // value changes at some point, parameter is used
|
||||
}
|
||||
|
||||
// check what pops the parameter is a comparison
|
||||
assert ins.getPushes().size() == 1;
|
||||
StackContext sctx = ins.getPushes().get(0);
|
||||
|
||||
if (sctx.getPopped().size() != 1
|
||||
|| !(sctx.getPopped().get(0).getInstruction() instanceof ComparisonInstruction))
|
||||
{
|
||||
parameter.invalid = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(ins.getInstruction() instanceof ComparisonInstruction))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// assume that this will always be variable index #paramIndex comp with a constant.
|
||||
ComparisonInstruction comp = (ComparisonInstruction) ins.getInstruction();
|
||||
|
||||
StackContext one, two = null;
|
||||
|
||||
if (comp instanceof If0)
|
||||
{
|
||||
one = ins.getPops().get(0);
|
||||
}
|
||||
else if (comp instanceof If)
|
||||
{
|
||||
one = ins.getPops().get(0);
|
||||
two = ins.getPops().get(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new RuntimeException("Unknown comp ins");
|
||||
}
|
||||
|
||||
// find if one is a lvt ins
|
||||
LVTInstruction lvt = null;
|
||||
StackContext other = null;
|
||||
|
||||
if (one.getPushed().getInstruction() instanceof LVTInstruction)
|
||||
{
|
||||
lvt = (LVTInstruction) one.getPushed().getInstruction();
|
||||
other = two;
|
||||
}
|
||||
else if (two != null && two.getPushed().getInstruction() instanceof LVTInstruction)
|
||||
{
|
||||
lvt = (LVTInstruction) two.getPushed().getInstruction();
|
||||
other = one;
|
||||
}
|
||||
|
||||
assert lvt == null || !lvt.store();
|
||||
|
||||
if (lvt == null || lvt.getVariableIndex() != lvtIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Number otherValue = null;
|
||||
|
||||
if (two != null) // two is null for if0
|
||||
{
|
||||
if (!(other.getPushed().getInstruction() instanceof PushConstantInstruction))
|
||||
{
|
||||
parameter.invalid = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
PushConstantInstruction pc = (PushConstantInstruction) other.getPushed().getInstruction();
|
||||
otherValue = (Number) pc.getConstant();
|
||||
}
|
||||
|
||||
for (Number value : parameter.values)
|
||||
{
|
||||
// the result of the comparison doesn't matter, only that it always goes the same direction for every invocation
|
||||
boolean result = doLogicalComparison(value, comp, otherValue);
|
||||
|
||||
// XXX this should check that the particular if always takes the same path,
|
||||
// not that all ifs for a specific parameter always take the same path
|
||||
if (parameter.result != null && parameter.result != result)
|
||||
{
|
||||
parameter.invalid = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
parameter.operations.add(ins.getInstruction());
|
||||
parameter.result = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean doLogicalComparison(Number value, ComparisonInstruction comparison, Number otherValue)
|
||||
{
|
||||
Instruction ins = (Instruction) comparison;
|
||||
|
||||
assert (comparison instanceof If0) == (otherValue == null);
|
||||
|
||||
switch (ins.getType())
|
||||
{
|
||||
case IFEQ:
|
||||
return value.equals(0);
|
||||
case IFNE:
|
||||
return !value.equals(0);
|
||||
case IFLT:
|
||||
return value.intValue() < 0;
|
||||
case IFGE:
|
||||
return value.intValue() >= 0;
|
||||
case IFGT:
|
||||
return value.intValue() > 0;
|
||||
case IFLE:
|
||||
return value.intValue() <= 0;
|
||||
case IF_ICMPEQ:
|
||||
return value.equals(otherValue);
|
||||
case IF_ICMPNE:
|
||||
return !value.equals(otherValue);
|
||||
case IF_ICMPLT:
|
||||
return value.intValue() < otherValue.intValue();
|
||||
case IF_ICMPGE:
|
||||
return value.intValue() >= otherValue.intValue();
|
||||
case IF_ICMPGT:
|
||||
return value.intValue() > otherValue.intValue();
|
||||
case IF_ICMPLE:
|
||||
return value.intValue() <= otherValue.intValue();
|
||||
default:
|
||||
throw new RuntimeException("Unknown constant comparison instructuction");
|
||||
}
|
||||
}
|
||||
|
||||
// remove logically dead comparisons
|
||||
private int removeDeadOperations(MethodContext mctx)
|
||||
{
|
||||
int count = 0;
|
||||
for (ConstantMethodParameter cmp : parameters.values())
|
||||
{
|
||||
if (cmp.invalid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cmp.methods.contains(mctx.getMethod()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// only annotate garbage value of last param
|
||||
if (cmp.paramIndex + 1 == mctx.getMethod().getDescriptor().size())
|
||||
{
|
||||
annotateObfuscatedSignature(cmp);
|
||||
}
|
||||
|
||||
for (Instruction ins : cmp.operations) // comparisons
|
||||
{
|
||||
if (ins.getInstructions() == null || ins.getInstructions().getCode().getMethod() != mctx.getMethod())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
InstructionContext ctx = mctx.getInstructonContexts(ins).toArray(new InstructionContext[0])[0];
|
||||
boolean branch = cmp.result; // branch that is always taken
|
||||
|
||||
if (ins.getInstructions() == null)
|
||||
{
|
||||
continue; // ins already removed?
|
||||
}
|
||||
Instructions instructions = ins.getInstructions();
|
||||
|
||||
// remove the if
|
||||
if (ctx.getInstruction() instanceof If)
|
||||
{
|
||||
ctx.removeStack(1);
|
||||
}
|
||||
ctx.removeStack(0);
|
||||
|
||||
int idx = instructions.getInstructions().indexOf(ins);
|
||||
if (idx == -1)
|
||||
{
|
||||
continue; // already removed?
|
||||
}
|
||||
++count;
|
||||
|
||||
Instruction to;
|
||||
if (branch)
|
||||
{
|
||||
JumpingInstruction jumpIns = (JumpingInstruction) ins;
|
||||
assert jumpIns.getJumps().size() == 1;
|
||||
to = jumpIns.getJumps().get(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// just go to next instruction
|
||||
to = instructions.getInstructions().get(idx + 1);
|
||||
}
|
||||
assert to.getInstructions() == instructions;
|
||||
assert ins != to;
|
||||
assert instructions.getInstructions().contains(to);
|
||||
|
||||
instructions.remove(ins);
|
||||
|
||||
assert instructions.getInstructions().contains(to);
|
||||
|
||||
if (branch)
|
||||
{
|
||||
Goto gotoins = new Goto(instructions, instructions.createLabelFor(to));
|
||||
|
||||
// insert goto
|
||||
instructions.getInstructions().add(idx, gotoins);
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private void annotateObfuscatedSignature(ConstantMethodParameter parameter)
|
||||
{
|
||||
for (Method m : parameter.methods)
|
||||
{
|
||||
Object value = parameter.values.get(0);
|
||||
|
||||
Annotations annotations = m.getAnnotations();
|
||||
|
||||
Annotation obfuscatedSignature = annotations.find(DeobAnnotations.OBFUSCATED_SIGNATURE);
|
||||
if (obfuscatedSignature != null && obfuscatedSignature.getElements().size() == 2)
|
||||
{
|
||||
// already annotated
|
||||
continue;
|
||||
}
|
||||
|
||||
if (obfuscatedSignature == null)
|
||||
{
|
||||
obfuscatedSignature = annotations.addAnnotation(DeobAnnotations.OBFUSCATED_SIGNATURE, "signature", m.getDescriptor().toString());
|
||||
}
|
||||
|
||||
// Add garbage value
|
||||
Element element = new Element(obfuscatedSignature);
|
||||
element.setName("garbageValue");
|
||||
element.setValue(value.toString());
|
||||
obfuscatedSignature.addElement(element);
|
||||
}
|
||||
}
|
||||
|
||||
private int count;
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
Execution execution = new Execution(group);
|
||||
execution.addExecutionVisitor(i -> findParameters(i));
|
||||
execution.populateInitialMethods();
|
||||
execution.run();
|
||||
|
||||
execution = new Execution(group);
|
||||
execution.addMethodContextVisitor(mc -> findDeadParameters(mc));
|
||||
execution.populateInitialMethods();
|
||||
execution.run();
|
||||
|
||||
execution = new Execution(group);
|
||||
execution.addMethodContextVisitor(m -> count += removeDeadOperations(m));
|
||||
execution.populateInitialMethods();
|
||||
execution.run();
|
||||
|
||||
logger.info("Removed {} logically dead conditional jumps", count);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,544 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.exprargorder;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.Type;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.InstructionType;
|
||||
import static net.runelite.asm.attributes.code.InstructionType.IADD;
|
||||
import static net.runelite.asm.attributes.code.InstructionType.IF_ACMPEQ;
|
||||
import static net.runelite.asm.attributes.code.InstructionType.IF_ACMPNE;
|
||||
import static net.runelite.asm.attributes.code.InstructionType.IF_ICMPEQ;
|
||||
import static net.runelite.asm.attributes.code.InstructionType.IF_ICMPNE;
|
||||
import static net.runelite.asm.attributes.code.InstructionType.IMUL;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
|
||||
import net.runelite.asm.attributes.code.instructions.AConstNull;
|
||||
import net.runelite.asm.attributes.code.instructions.IAdd;
|
||||
import net.runelite.asm.attributes.code.instructions.IInc;
|
||||
import net.runelite.asm.attributes.code.instructions.IMul;
|
||||
import net.runelite.asm.attributes.code.instructions.If;
|
||||
import net.runelite.asm.attributes.code.instructions.IfACmpEq;
|
||||
import net.runelite.asm.attributes.code.instructions.IfACmpNe;
|
||||
import net.runelite.asm.attributes.code.instructions.IfICmpEq;
|
||||
import net.runelite.asm.attributes.code.instructions.IfICmpNe;
|
||||
import net.runelite.asm.execution.Execution;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
import net.runelite.asm.execution.MethodContext;
|
||||
import net.runelite.asm.execution.StackContext;
|
||||
import net.runelite.asm.signature.Signature;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ExprArgOrder implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(ExprArgOrder.class);
|
||||
|
||||
private final List<Instruction> exprIns = new ArrayList<>();
|
||||
private final Map<Instruction, Expression> exprs = new HashMap<>();
|
||||
private int count;
|
||||
|
||||
static boolean isCommutative(InstructionType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case IADD:
|
||||
case IMUL:
|
||||
case IF_ICMPEQ:
|
||||
case IF_ICMPNE:
|
||||
case IF_ACMPEQ:
|
||||
case IF_ACMPNE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Instruction newInstruction(Instructions ins, Instruction old, InstructionType type)
|
||||
{
|
||||
assert isCommutative(type) : "type is " + type;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case IADD:
|
||||
return new IAdd(ins);
|
||||
case IMUL:
|
||||
return new IMul(ins);
|
||||
case IF_ICMPEQ:
|
||||
{
|
||||
If i = (If) old;
|
||||
return new IfICmpEq(ins, i.getJumps().get(0));
|
||||
}
|
||||
case IF_ICMPNE:
|
||||
{
|
||||
If i = (If) old;
|
||||
return new IfICmpNe(ins, i.getJumps().get(0));
|
||||
}
|
||||
case IF_ACMPEQ:
|
||||
{
|
||||
If i = (If) old;
|
||||
return new IfACmpEq(ins, i.getJumps().get(0));
|
||||
}
|
||||
case IF_ACMPNE:
|
||||
{
|
||||
If i = (If) old;
|
||||
return new IfACmpNe(ins, i.getJumps().get(0));
|
||||
}
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private void parseExpr(Expression expr, InstructionContext ctx)
|
||||
{
|
||||
InstructionType type = ctx.getInstruction().getType();
|
||||
|
||||
List<StackContext> pops = ctx.getPops();
|
||||
|
||||
for (StackContext sctx : pops)
|
||||
{
|
||||
InstructionContext i = sctx.getPushed();
|
||||
|
||||
if (isCommutative(type) && i.getInstruction().getType() == type)
|
||||
{
|
||||
parseExpr(expr, i);
|
||||
}
|
||||
else if (isCommutative(type))
|
||||
{
|
||||
Expression sub = new Expression(i);
|
||||
parseExpr(sub, i);
|
||||
expr.addComExpr(sub);
|
||||
}
|
||||
else
|
||||
{
|
||||
Expression sub = new Expression(i);
|
||||
parseExpr(sub, i);
|
||||
expr.addExpr(sub);
|
||||
}
|
||||
|
||||
exprIns.remove(i.getInstruction());
|
||||
exprs.remove(i.getInstruction()); // remove sub expr
|
||||
}
|
||||
}
|
||||
|
||||
private void visit(InstructionContext ctx)
|
||||
{
|
||||
Instruction ins = ctx.getInstruction();
|
||||
|
||||
if (ins instanceof IAdd || ins instanceof IMul
|
||||
|| ins instanceof IfICmpEq || ins instanceof IfICmpNe
|
||||
|| ins instanceof IfACmpEq || ins instanceof IfACmpNe)
|
||||
{
|
||||
Expression expression = new Expression(ctx);
|
||||
parseExpr(expression, ctx);
|
||||
if (!exprs.containsKey(ins))
|
||||
{
|
||||
exprIns.add(ins);
|
||||
exprs.put(ins, expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canRemove(MethodContext mctx, Instructions ins, Instruction i)
|
||||
{
|
||||
Set<InstructionContext> ctxs = new HashSet<>(mctx.getInstructonContexts(i));
|
||||
|
||||
if (!alwaysPoppedBySameInstruction(ctxs, i) || !alwaysPopsFromSameInstructions(ctxs, i))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (i instanceof InvokeInstruction)
|
||||
{
|
||||
// don't ever order lhs/rhs if an invoke is involved?
|
||||
// func1() + func2() vs func2() + func1() is not the same thing
|
||||
return false;
|
||||
}
|
||||
|
||||
int idx = ins.getInstructions().indexOf(i);
|
||||
if (idx == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (InstructionContext ictx : ctxs)
|
||||
{
|
||||
for (StackContext sctx : ictx.getPops())
|
||||
{
|
||||
Instruction pushed = sctx.getPushed().getInstruction();
|
||||
|
||||
int idx2 = ins.getInstructions().indexOf(pushed);
|
||||
if (idx2 == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
assert idx > idx2;
|
||||
|
||||
// If there is a lvt store (incl iinc!) between the two
|
||||
// instructions, we can't move them
|
||||
for (int j = idx2; j <= idx; ++j)
|
||||
{
|
||||
Instruction i2 = ins.getInstructions().get(j);
|
||||
if (i2 instanceof LVTInstruction)
|
||||
{
|
||||
if (((LVTInstruction) i2).store())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (i2 instanceof IInc)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!canRemove(mctx, ins, pushed))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
private void remove(Instructions ins, InstructionContext ic)
|
||||
{
|
||||
ins.remove(ic.getInstruction());
|
||||
|
||||
for (StackContext sc : ic.getPops())
|
||||
{
|
||||
assert sc.getPopped().size() == 1;
|
||||
remove(ins, sc.getPushed());
|
||||
}
|
||||
}
|
||||
|
||||
private void insert(Instructions ins, InstructionContext ic, Instruction before)
|
||||
{
|
||||
Instruction i = ic.getInstruction();
|
||||
assert i.getInstructions() == null;
|
||||
|
||||
int idx = ins.getInstructions().indexOf(before);
|
||||
assert idx != -1;
|
||||
|
||||
i.setInstructions(ins);
|
||||
ins.addInstruction(idx, i);
|
||||
}
|
||||
|
||||
public static int hash(Method method, InstructionContext ic)
|
||||
{
|
||||
MessageDigest sha256;
|
||||
try
|
||||
{
|
||||
sha256 = MessageDigest.getInstance("SHA-256");
|
||||
}
|
||||
catch (NoSuchAlgorithmException ex)
|
||||
{
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
hash(method, sha256, ic);
|
||||
byte[] res = sha256.digest();
|
||||
return Ints.fromByteArray(res);
|
||||
}
|
||||
|
||||
private static void hash(Method method, MessageDigest sha256, InstructionContext ic)
|
||||
{
|
||||
Instruction i = ic.getInstruction();
|
||||
|
||||
// this relies on all push constants are converted to ldc..
|
||||
sha256.update((byte) i.getType().getCode());
|
||||
|
||||
if (i instanceof PushConstantInstruction)
|
||||
{
|
||||
PushConstantInstruction pci = (PushConstantInstruction) i;
|
||||
Object constant = pci.getConstant();
|
||||
if (constant instanceof Number)
|
||||
{
|
||||
long l = ((Number) constant).longValue();
|
||||
sha256.update(Longs.toByteArray(l));
|
||||
}
|
||||
}
|
||||
else if (i instanceof LVTInstruction)
|
||||
{
|
||||
int idx = ((LVTInstruction) i).getVariableIndex();
|
||||
Signature signature = method.getDescriptor();
|
||||
|
||||
int lvt = method.isStatic() ? 0 : 1;
|
||||
for (Type type : signature.getArguments())
|
||||
{
|
||||
if (idx == lvt)
|
||||
{
|
||||
// Accessing a method parameter
|
||||
sha256.update(Ints.toByteArray(idx));
|
||||
break;
|
||||
}
|
||||
|
||||
lvt += type.getSize();
|
||||
}
|
||||
}
|
||||
|
||||
for (StackContext sctx : ic.getPops())
|
||||
{
|
||||
hash(method, sha256, sctx.getPushed());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean alwaysPopsFromSameInstructions(Set<InstructionContext> instructonContexts, Instruction i)
|
||||
{
|
||||
InstructionContext ictx = instructonContexts.iterator().next();
|
||||
|
||||
for (InstructionContext i2 : instructonContexts)
|
||||
{
|
||||
if (!i2.equals(ictx))
|
||||
{
|
||||
// this instruction doesn't always pop the same thing
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean alwaysPoppedBySameInstruction(Set<InstructionContext> instructonContexts, Instruction i)
|
||||
{
|
||||
Set<Instruction> c = new HashSet<>();
|
||||
|
||||
for (InstructionContext i2 : instructonContexts)
|
||||
{
|
||||
i2.getPushes().stream()
|
||||
.flatMap(sctx -> sctx.getPopped().stream())
|
||||
.map(ic -> ic.getInstruction())
|
||||
.forEach(i3 -> c.add(i3));
|
||||
}
|
||||
|
||||
return c.size() <= 1;
|
||||
}
|
||||
|
||||
public static int compare(Method method, InstructionType type, InstructionContext ic1, InstructionContext ic2)
|
||||
{
|
||||
Instruction i1 = ic1.getInstruction();
|
||||
Instruction i2 = ic2.getInstruction();
|
||||
|
||||
if (type == IF_ICMPEQ || type == IF_ICMPNE
|
||||
|| type == IADD || type == IMUL)
|
||||
{
|
||||
if (!(i1 instanceof PushConstantInstruction)
|
||||
&& (i2 instanceof PushConstantInstruction))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (i1 instanceof PushConstantInstruction
|
||||
&& !(i2 instanceof PushConstantInstruction))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == IF_ACMPEQ || type == IF_ACMPNE)
|
||||
{
|
||||
if (!(i1 instanceof AConstNull)
|
||||
&& (i2 instanceof AConstNull))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (i1 instanceof AConstNull
|
||||
&& !(i2 instanceof AConstNull))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int hash1 = hash(method, ic1);
|
||||
int hash2 = hash(method, ic2);
|
||||
|
||||
if (hash1 == hash2)
|
||||
{
|
||||
logger.debug("Unable to differentiate {} from {}", ic1, ic2);
|
||||
}
|
||||
|
||||
return Integer.compare(hash1, hash2);
|
||||
}
|
||||
|
||||
public static int compare(Method method, InstructionType type, Expression expr1, Expression expr2)
|
||||
{
|
||||
Instruction i1 = expr1.getHead().getInstruction();
|
||||
Instruction i2 = expr2.getHead().getInstruction();
|
||||
|
||||
if (type == IF_ICMPEQ || type == IF_ICMPNE
|
||||
|| type == IADD || type == IMUL)
|
||||
{
|
||||
if (!(i1 instanceof PushConstantInstruction)
|
||||
&& (i2 instanceof PushConstantInstruction))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (i1 instanceof PushConstantInstruction
|
||||
&& !(i2 instanceof PushConstantInstruction))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == IF_ACMPEQ || type == IF_ACMPNE)
|
||||
{
|
||||
if (!(i1 instanceof AConstNull)
|
||||
&& (i2 instanceof AConstNull))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (i1 instanceof AConstNull
|
||||
&& !(i2 instanceof AConstNull))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int hash1 = hash(method, expr1.getHead());
|
||||
int hash2 = hash(method, expr2.getHead());
|
||||
|
||||
if (hash1 == hash2)
|
||||
{
|
||||
logger.debug("Unable to differentiate {} from {}", expr1.getHead(), expr2.getHead());
|
||||
}
|
||||
|
||||
return Integer.compare(hash1, hash2);
|
||||
}
|
||||
|
||||
private void insert(Instructions ins, Instruction next, Expression expression)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
if (expression.sortedExprs != null)
|
||||
{
|
||||
for (Expression sub : expression.sortedExprs)
|
||||
{
|
||||
if (count == 2)
|
||||
{
|
||||
Instruction newOp;
|
||||
if (isCommutative(expression.getType()))
|
||||
{
|
||||
// there might be >2 sortedExprs so we can't reuse instructions
|
||||
newOp = newInstruction(ins, expression.getHead().getInstruction(), expression.getType());
|
||||
}
|
||||
else
|
||||
{
|
||||
newOp = expression.getHead().getInstruction();
|
||||
assert newOp.getInstructions() == null;
|
||||
newOp.setInstructions(ins);
|
||||
}
|
||||
|
||||
int idx = ins.getInstructions().indexOf(next);
|
||||
assert idx != -1;
|
||||
|
||||
ins.addInstruction(idx, newOp);
|
||||
count = 1;
|
||||
}
|
||||
|
||||
insert(ins, next, sub);
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
List<Expression> reverseExpr = new ArrayList<>(expression.getExprs());
|
||||
Collections.reverse(reverseExpr);
|
||||
for (Expression sub : reverseExpr)
|
||||
{
|
||||
insert(ins, next, sub);
|
||||
}
|
||||
|
||||
insert(ins, expression.getHead(), next);
|
||||
}
|
||||
|
||||
private void visit(MethodContext ctx)
|
||||
{
|
||||
Instructions ins = ctx.getMethod().getCode().getInstructions();
|
||||
|
||||
for (Instruction i : exprIns)
|
||||
{
|
||||
Expression expression = exprs.get(i);
|
||||
assert expression != null;
|
||||
|
||||
if (!canRemove(ctx, ins, i))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int idx = ins.getInstructions().indexOf(expression.getHead().getInstruction());
|
||||
assert idx != -1;
|
||||
|
||||
// get next instruction
|
||||
Instruction next = ins.getInstructions().get(idx + 1);;
|
||||
|
||||
// remove expression
|
||||
remove(ins, expression.getHead());
|
||||
|
||||
// sort expression
|
||||
expression.sort(ctx);
|
||||
|
||||
// insert expression
|
||||
insert(ins, next, expression);
|
||||
|
||||
++count;
|
||||
}
|
||||
|
||||
exprIns.clear();
|
||||
exprs.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
Execution execution = new Execution(group);
|
||||
execution.addExecutionVisitor(i -> visit(i));
|
||||
execution.addMethodContextVisitor(i -> visit(i));
|
||||
execution.populateInitialMethods();
|
||||
execution.run();
|
||||
|
||||
logger.info("Reordered {} expressions", count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.exprargorder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import net.runelite.asm.attributes.code.InstructionType;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
import net.runelite.asm.execution.MethodContext;
|
||||
|
||||
public class Expression
|
||||
{
|
||||
private final InstructionContext head;
|
||||
private final List<Expression> exprs = new ArrayList<>();
|
||||
private final List<Expression> comExprs = new ArrayList<>();
|
||||
|
||||
public List<Expression> sortedExprs;
|
||||
private int exprHash;
|
||||
|
||||
public Expression(InstructionContext head)
|
||||
{
|
||||
this.head = head;
|
||||
}
|
||||
|
||||
public InstructionType getType()
|
||||
{
|
||||
return head.getInstruction().getType();
|
||||
}
|
||||
|
||||
public InstructionContext getHead()
|
||||
{
|
||||
return head;
|
||||
}
|
||||
|
||||
public void addExpr(Expression expr)
|
||||
{
|
||||
exprs.add(expr);
|
||||
}
|
||||
|
||||
public List<Expression> getExprs()
|
||||
{
|
||||
return Collections.unmodifiableList(exprs);
|
||||
}
|
||||
|
||||
public void addComExpr(Expression expr)
|
||||
{
|
||||
comExprs.add(expr);
|
||||
}
|
||||
|
||||
public List<Expression> getComExprs()
|
||||
{
|
||||
return Collections.unmodifiableList(comExprs);
|
||||
}
|
||||
|
||||
public void sort(MethodContext ctx)
|
||||
{
|
||||
for (Expression e : comExprs)
|
||||
{
|
||||
e.sort(ctx);
|
||||
}
|
||||
for (Expression e : exprs)
|
||||
{
|
||||
e.sort(ctx);
|
||||
}
|
||||
|
||||
sortedExprs = new ArrayList<>(comExprs);
|
||||
Collections.sort(sortedExprs, (e1, e2) -> ExprArgOrder.compare(ctx.getMethod(), getType(), e1, e2));
|
||||
Collections.reverse(sortedExprs);
|
||||
|
||||
int hash = 0;
|
||||
for (Expression e : sortedExprs)
|
||||
{
|
||||
hash ^= e.exprHash;
|
||||
}
|
||||
exprHash = hash;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.lvt;
|
||||
|
||||
public enum LVTType
|
||||
{
|
||||
INT,
|
||||
LONG,
|
||||
OBJECT
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.lvt;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class MapKey
|
||||
{
|
||||
private final int idx;
|
||||
private final LVTType type;
|
||||
|
||||
public MapKey(int idx, LVTType type)
|
||||
{
|
||||
this.idx = idx;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
int hash = 7;
|
||||
hash = 89 * hash + this.idx;
|
||||
hash = 89 * hash + Objects.hashCode(this.type);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (obj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
final MapKey other = (MapKey) obj;
|
||||
if (this.idx != other.idx)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (this.type != other.type)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.lvt;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import net.runelite.asm.attributes.code.instruction.types.LVTInstructionType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Mappings
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(Mappings.class);
|
||||
|
||||
private final int maxVariables;
|
||||
private int offset;
|
||||
private Map<Integer, LVTType> map = new HashMap<>();
|
||||
private Map<MapKey, Integer> newIdxMap = new HashMap<>();
|
||||
|
||||
public Mappings(int maxVariables)
|
||||
{
|
||||
this.maxVariables = maxVariables;
|
||||
}
|
||||
|
||||
private static LVTType toLvtType(LVTInstructionType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case DOUBLE:
|
||||
case LONG:
|
||||
return LVTType.LONG;
|
||||
case FLOAT:
|
||||
case INT:
|
||||
return LVTType.INT;
|
||||
case OBJECT:
|
||||
return LVTType.OBJECT;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown type " + type);
|
||||
}
|
||||
}
|
||||
|
||||
public Integer remap(int idx, LVTInstructionType type)
|
||||
{
|
||||
LVTType seen = map.get(idx);
|
||||
|
||||
if (seen == null)
|
||||
{
|
||||
map.put(idx, toLvtType(type));
|
||||
}
|
||||
else if (toLvtType(type) != seen)
|
||||
{
|
||||
MapKey key = new MapKey(idx, toLvtType(type));
|
||||
|
||||
Integer newIdx = newIdxMap.get(key);
|
||||
if (newIdx == null)
|
||||
{
|
||||
newIdx = maxVariables + offset;
|
||||
newIdxMap.put(key, newIdx);
|
||||
|
||||
logger.debug("Mapping {} -> {}", idx, newIdx);
|
||||
|
||||
offset += type.getSlots();
|
||||
}
|
||||
|
||||
return newIdx;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.mapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Annotations;
|
||||
import net.runelite.asm.attributes.annotation.Annotation;
|
||||
import net.runelite.deob.DeobAnnotations;
|
||||
import net.runelite.mapping.Import;
|
||||
import net.runelite.rs.api.RSClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AnnotationIntegrityChecker
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(AnnotationIntegrityChecker.class);
|
||||
|
||||
public static final java.lang.Class<?> CLIENT_CLASS = RSClient.class;
|
||||
|
||||
public static final String API_PACKAGE_BASE = "rs.api.RS";
|
||||
|
||||
private final ClassGroup one;
|
||||
private final ClassGroup two;
|
||||
private final ParallelExecutorMapping mapping;
|
||||
|
||||
private int errors;
|
||||
private int warnings;
|
||||
|
||||
public AnnotationIntegrityChecker(ClassGroup one, ClassGroup two, ParallelExecutorMapping mapping)
|
||||
{
|
||||
this.one = one;
|
||||
this.two = two;
|
||||
this.mapping = mapping;
|
||||
}
|
||||
|
||||
public int getErrors()
|
||||
{
|
||||
return errors;
|
||||
}
|
||||
|
||||
public int getWarnings()
|
||||
{
|
||||
return warnings;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
for (ClassFile cf : one.getClasses())
|
||||
{
|
||||
ClassFile other = (ClassFile) mapping.get(cf);
|
||||
|
||||
List<Field> exf1 = getExportedFields(cf);
|
||||
List<Method> exm1 = getExportedMethods(cf);
|
||||
|
||||
for (Field f1 : exf1)
|
||||
{
|
||||
boolean isImported = isImported(cf, f1.getName(), f1.isStatic());
|
||||
Field f2;
|
||||
|
||||
if (other == null)
|
||||
{
|
||||
if (!f1.isStatic() && isImported)
|
||||
{
|
||||
++errors;
|
||||
logger.error("No other class for {} which contains imported field {}", cf, f1);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (f1.isStatic())
|
||||
{
|
||||
f2 = findExportedFieldStatic(two, DeobAnnotations.getExportedName(f1.getAnnotations()));
|
||||
}
|
||||
else
|
||||
{
|
||||
f2 = findExportedField(other, DeobAnnotations.getExportedName(f1.getAnnotations()));
|
||||
}
|
||||
|
||||
if (f2 == null)
|
||||
{
|
||||
if (isImported)
|
||||
{
|
||||
logger.error("Missing IMPORTED field on {} named {}",
|
||||
other,
|
||||
DeobAnnotations.getExportedName(f1.getAnnotations()));
|
||||
|
||||
++errors;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.warn("Missing exported field on {} named {}",
|
||||
other,
|
||||
DeobAnnotations.getExportedName(f1.getAnnotations()));
|
||||
|
||||
++warnings;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Method m1 : exm1)
|
||||
{
|
||||
boolean isImported = isImported(cf, m1.getName(), m1.isStatic());
|
||||
Method m2;
|
||||
|
||||
if (other == null)
|
||||
{
|
||||
if (!m1.isStatic() && isImported)
|
||||
{
|
||||
++errors;
|
||||
logger.error("No other class for {} which contains imported method {}", cf, m1);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m1.isStatic())
|
||||
{
|
||||
m2 = findExportedMethodStatic(two, DeobAnnotations.getExportedName(m1.getAnnotations()));
|
||||
}
|
||||
else
|
||||
{
|
||||
m2 = findExportedMethod(other, DeobAnnotations.getExportedName(m1.getAnnotations()));
|
||||
}
|
||||
|
||||
if (m2 == null)
|
||||
{
|
||||
if (isImported)
|
||||
{
|
||||
logger.error("Missing IMPORTED method on {} named {} ({})",
|
||||
other,
|
||||
DeobAnnotations.getExportedName(m1.getAnnotations()),
|
||||
m1);
|
||||
|
||||
++errors;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.warn("Missing exported method on {} named {} ({})",
|
||||
other,
|
||||
DeobAnnotations.getExportedName(m1.getAnnotations()),
|
||||
m1);
|
||||
|
||||
++warnings;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkAnnotationCounts();
|
||||
}
|
||||
|
||||
private void checkAnnotationCounts()
|
||||
{
|
||||
for (ClassFile cf : two.getClasses())
|
||||
{
|
||||
for (Field f : cf.getFields())
|
||||
{
|
||||
int num = this.getNumberOfExports(f.getAnnotations());
|
||||
|
||||
if (num > 1)
|
||||
{
|
||||
logger.warn("Field {} has more than 1 export", f);
|
||||
++errors;
|
||||
}
|
||||
}
|
||||
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
int num = this.getNumberOfExports(m.getAnnotations());
|
||||
|
||||
if (num > 1)
|
||||
{
|
||||
logger.warn("Method {} has more than 1 export", m);
|
||||
++errors;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the api imports a given exported field or method by
|
||||
* namee
|
||||
*
|
||||
* @param cf Class file field/method is on
|
||||
* @param name Exported name of field/method
|
||||
* @param isStatic Whether or not field/method is static
|
||||
* @return
|
||||
*/
|
||||
private boolean isImported(ClassFile cf, String name, boolean isStatic)
|
||||
{
|
||||
Class<?> clazz;
|
||||
if (isStatic)
|
||||
{
|
||||
// Use client
|
||||
clazz = CLIENT_CLASS;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find interface for class
|
||||
String iface = DeobAnnotations.getImplements(cf);
|
||||
if (iface == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
clazz = Class.forName(API_PACKAGE_BASE + iface);
|
||||
}
|
||||
catch (ClassNotFoundException ex)
|
||||
{
|
||||
return false; // this is okay
|
||||
}
|
||||
}
|
||||
|
||||
for (java.lang.reflect.Method method : clazz.getDeclaredMethods())
|
||||
{
|
||||
Import im = method.getAnnotation(Import.class);
|
||||
if (im != null && im.value().equals(name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<Field> getExportedFields(ClassFile clazz)
|
||||
{
|
||||
List<Field> list = new ArrayList<>();
|
||||
for (Field f : clazz.getFields())
|
||||
{
|
||||
if (DeobAnnotations.getExportedName(f.getAnnotations()) != null)
|
||||
{
|
||||
list.add(f);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private List<Method> getExportedMethods(ClassFile clazz)
|
||||
{
|
||||
List<Method> list = new ArrayList<>();
|
||||
for (Method m : clazz.getMethods())
|
||||
{
|
||||
if (DeobAnnotations.getExportedName(m.getAnnotations()) != null)
|
||||
{
|
||||
list.add(m);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private int getNumberOfExports(Annotations an)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (Annotation a : an.getAnnotations())
|
||||
{
|
||||
if (a.getType().equals(DeobAnnotations.EXPORT))
|
||||
{
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private Field findExportedField(ClassFile clazz, String name)
|
||||
{
|
||||
for (Field f : getExportedFields(clazz))
|
||||
{
|
||||
if (DeobAnnotations.getExportedName(f.getAnnotations()).equals(name))
|
||||
{
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Field findExportedFieldStatic(ClassGroup group, String name)
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Field f : cf.getFields())
|
||||
{
|
||||
if (f.isStatic())
|
||||
{
|
||||
if (name.equals(DeobAnnotations.getExportedName(f.getAnnotations())))
|
||||
{
|
||||
return f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Method findExportedMethodStatic(ClassGroup group, String name)
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
if (m.isStatic())
|
||||
{
|
||||
if (name.equals(DeobAnnotations.getExportedName(m.getAnnotations())))
|
||||
{
|
||||
return m;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Method findExportedMethod(ClassFile clazz, String name)
|
||||
{
|
||||
for (Method m : getExportedMethods(clazz))
|
||||
{
|
||||
if (DeobAnnotations.getExportedName(m.getAnnotations()).equals(name))
|
||||
{
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.mapping;
|
||||
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Annotations;
|
||||
import net.runelite.asm.attributes.annotation.Annotation;
|
||||
import net.runelite.asm.attributes.annotation.Element;
|
||||
import net.runelite.deob.DeobAnnotations;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AnnotationMapper
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(AnnotationMapper.class);
|
||||
|
||||
private final ClassGroup source, target;
|
||||
private final ParallelExecutorMapping mapping;
|
||||
|
||||
public AnnotationMapper(ClassGroup source, ClassGroup target, ParallelExecutorMapping mapping)
|
||||
{
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
this.mapping = mapping;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (ClassFile c : source.getClasses())
|
||||
{
|
||||
ClassFile other = (ClassFile) mapping.get(c);
|
||||
|
||||
count += run(c, other);
|
||||
}
|
||||
|
||||
logger.info("Copied {} annotations", count);
|
||||
}
|
||||
|
||||
private int run(ClassFile from, ClassFile to)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
if (hasCopyableAnnotation(from.getAnnotations()))
|
||||
{
|
||||
if (to != null)
|
||||
{
|
||||
count += copyAnnotations(from.getAnnotations(), to.getAnnotations());
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.warn("Class {} has copyable annotations but there is no mapped class", from);
|
||||
}
|
||||
}
|
||||
|
||||
for (Field f : from.getFields())
|
||||
{
|
||||
if (!hasCopyableAnnotation(f.getAnnotations()))
|
||||
continue;
|
||||
|
||||
Field other = (Field) mapping.get(f);
|
||||
if (other == null)
|
||||
{
|
||||
logger.warn("Unable to map annotated field {} named {}", f, DeobAnnotations.getExportedName(f.getAnnotations()));
|
||||
continue;
|
||||
}
|
||||
|
||||
count += copyAnnotations(f.getAnnotations(), other.getAnnotations());
|
||||
}
|
||||
|
||||
for (Method m : from.getMethods())
|
||||
{
|
||||
if (!hasCopyableAnnotation(m.getAnnotations()))
|
||||
continue;
|
||||
|
||||
Method other = (Method) mapping.get(m);
|
||||
if (other == null)
|
||||
{
|
||||
logger.warn("Unable to map annotated method {} named {}", m, DeobAnnotations.getExportedName(m.getAnnotations()));
|
||||
continue;
|
||||
}
|
||||
|
||||
count += copyAnnotations(m.getAnnotations(), other.getAnnotations());
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private int copyAnnotations(Annotations from, Annotations to)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
if (from.getAnnotations() == null)
|
||||
return count;
|
||||
|
||||
for (Annotation a : from.getAnnotations())
|
||||
{
|
||||
if (isCopyable(a))
|
||||
{
|
||||
Annotation annotation = new Annotation(to);
|
||||
annotation.setType(a.getType());
|
||||
to.addAnnotation(annotation);
|
||||
|
||||
for (Element e : a.getElements())
|
||||
{
|
||||
Element element = new Element(annotation);
|
||||
element.setName(e.getName());
|
||||
element.setValue(e.getValue());
|
||||
annotation.addElement(element);
|
||||
}
|
||||
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private boolean hasCopyableAnnotation(Annotations a)
|
||||
{
|
||||
for (Annotation an : a.getAnnotations())
|
||||
if (isCopyable(an))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isCopyable(Annotation a)
|
||||
{
|
||||
return a.getType().equals(DeobAnnotations.EXPORT)
|
||||
|| a.getType().equals(DeobAnnotations.IMPLEMENTS)
|
||||
|| a.getType().equals(DeobAnnotations.HOOK);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.mapping;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
|
||||
public class ClassGroupMapper
|
||||
{
|
||||
private final ClassGroup one, two;
|
||||
private final Map<ClassFile, ClassFile> map = new HashMap<>();
|
||||
|
||||
public ClassGroupMapper(ClassGroup one, ClassGroup two)
|
||||
{
|
||||
this.one = one;
|
||||
this.two = two;
|
||||
}
|
||||
|
||||
public void map()
|
||||
{
|
||||
for (ClassFile cf1 : one.getClasses())
|
||||
for (ClassFile cf2 : two.getClasses())
|
||||
{
|
||||
if (!MappingExecutorUtil.isMaybeEqual(cf1, cf2))
|
||||
continue;
|
||||
|
||||
ClassMapper m = new ClassMapper(cf1, cf2);
|
||||
if (!m.same())
|
||||
continue;
|
||||
|
||||
map.put(cf1, cf2);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<ClassFile, ClassFile> getMap()
|
||||
{
|
||||
return map;
|
||||
}
|
||||
|
||||
public ClassFile get(ClassFile c)
|
||||
{
|
||||
return map.get(c);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.mapping;
|
||||
|
||||
import com.google.common.collect.ImmutableMultiset;
|
||||
import com.google.common.collect.Multiset;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.Type;
|
||||
import net.runelite.asm.signature.Signature;
|
||||
|
||||
public class ClassMapper
|
||||
{
|
||||
private final ClassFile one, two;
|
||||
|
||||
public ClassMapper(ClassFile one, ClassFile two)
|
||||
{
|
||||
this.one = one;
|
||||
this.two = two;
|
||||
}
|
||||
|
||||
private Multiset<Type> fieldCardinalities(ClassFile cf)
|
||||
{
|
||||
List<Type> t = cf.getFields().stream()
|
||||
.filter(f -> !f.isStatic())
|
||||
.map(f -> f.getType())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return ImmutableMultiset.copyOf(t);
|
||||
}
|
||||
|
||||
private Multiset<Signature> methodCardinalities(ClassFile cf)
|
||||
{
|
||||
List<Signature> t = cf.getMethods().stream()
|
||||
.filter(m -> !m.isStatic())
|
||||
.filter(m -> !m.getName().startsWith("<"))
|
||||
.map(m -> m.getDescriptor())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return ImmutableMultiset.copyOf(t);
|
||||
}
|
||||
|
||||
public boolean same()
|
||||
{
|
||||
Multiset<Type> c1 = fieldCardinalities(one), c2 = fieldCardinalities(two);
|
||||
|
||||
if (!c1.equals(c2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Multiset<Signature> s1 = methodCardinalities(one);
|
||||
Multiset<Signature> s2 = methodCardinalities(two);
|
||||
|
||||
if (!s1.equals(s2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.mapping;
|
||||
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.Type;
|
||||
import net.runelite.asm.signature.Signature;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ConstructorMapper
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(ConstructorMapper.class);
|
||||
|
||||
private final ClassGroup source, target;
|
||||
private final ParallelExecutorMapping mapping;
|
||||
|
||||
public ConstructorMapper(ClassGroup source, ClassGroup target, ParallelExecutorMapping mapping)
|
||||
{
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
this.mapping = mapping;
|
||||
}
|
||||
|
||||
private Type toOtherType(Type type)
|
||||
{
|
||||
if (type.isPrimitive())
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
ClassFile cf = source.findClass(type.getInternalName());
|
||||
if (cf == null)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
ClassFile other = (ClassFile) mapping.get(cf);
|
||||
if (other == null)
|
||||
{
|
||||
logger.debug("Unable to map other type due to no class mapping for {}", cf);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Type("L" + other.getName() + ";");
|
||||
}
|
||||
|
||||
private Signature toOtherSignature(Signature s)
|
||||
{
|
||||
Signature.Builder builder = new Signature.Builder()
|
||||
.setReturnType(toOtherType(s.getReturnValue()));
|
||||
for (Type t : s.getArguments())
|
||||
{
|
||||
Type other = toOtherType(t);
|
||||
if (other == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
builder.addArgument(other);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Map constructors based on the class mapping of the given mapping
|
||||
*/
|
||||
public void mapConstructors()
|
||||
{
|
||||
for (ClassFile cf : source.getClasses())
|
||||
{
|
||||
ClassFile other = (ClassFile) mapping.get(cf);
|
||||
|
||||
if (other == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
if (!m.getName().equals("<init>"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Signature otherSig = toOtherSignature(m.getDescriptor());
|
||||
if (otherSig == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.debug("Converted signature {} -> {}", m.getDescriptor(), otherSig);
|
||||
|
||||
Method m2 = other.findMethod(m.getName(), otherSig);
|
||||
if (m2 == null)
|
||||
{
|
||||
logger.warn("Unable to find other constructor for {}, looking for signature {} on class {}", m, otherSig, other);
|
||||
continue;
|
||||
}
|
||||
|
||||
ParallelExecutorMapping p = MappingExecutorUtil.map(m, m2);
|
||||
p.map(null, m, m2);
|
||||
|
||||
mapping.merge(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.mapping;
|
||||
|
||||
import java.util.Collection;
|
||||
import net.runelite.asm.Method;
|
||||
|
||||
public class ExecutionMapper
|
||||
{
|
||||
// method1 maps to one of methods2, find out based on mapping
|
||||
|
||||
private Method method1;
|
||||
private Collection<Method> methods2;
|
||||
|
||||
public ExecutionMapper(Method method1, Collection<Method> methods2)
|
||||
{
|
||||
this.method1 = method1;
|
||||
this.methods2 = methods2;
|
||||
}
|
||||
|
||||
public ParallelExecutorMapping run()
|
||||
{
|
||||
ParallelExecutorMapping highest = null;
|
||||
boolean multiple = false;
|
||||
|
||||
for (Method m : methods2)
|
||||
{
|
||||
ParallelExecutorMapping mapping = MappingExecutorUtil.map(method1, m);
|
||||
|
||||
if (highest == null || mapping.same > highest.same)
|
||||
{
|
||||
highest = mapping;
|
||||
multiple = false;
|
||||
}
|
||||
else if (mapping.same == highest.same)
|
||||
{
|
||||
multiple = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (multiple)
|
||||
return null;
|
||||
|
||||
return highest;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.mapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Mapper
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(Mapper.class);
|
||||
|
||||
private final ClassGroup source, target;
|
||||
private ParallelExecutorMapping mapping;
|
||||
|
||||
public Mapper(ClassGroup source, ClassGroup target)
|
||||
{
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public ParallelExecutorMapping getMapping()
|
||||
{
|
||||
return mapping;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
ParallelExecutorMapping finalm = new ParallelExecutorMapping(source, target);
|
||||
|
||||
finalm.merge(mapStaticMethods());
|
||||
finalm.merge(mapMethods());
|
||||
|
||||
finalm.reduce();
|
||||
|
||||
// map unexecuted methods (their mapping have not yet been merged)
|
||||
while (mapUnexecutedMethods(finalm));
|
||||
|
||||
finalm.buildClasses();
|
||||
|
||||
mapMemberMethods(finalm);
|
||||
|
||||
new ConstructorMapper(source, target, finalm).mapConstructors();
|
||||
|
||||
finalm.reduce();
|
||||
|
||||
mapping = finalm;
|
||||
}
|
||||
|
||||
private ParallelExecutorMapping mapMethods()
|
||||
{
|
||||
MethodSignatureMapper msm = new MethodSignatureMapper();
|
||||
msm.map(source, target);
|
||||
|
||||
List<ParallelExecutorMapping> pmes = new ArrayList<>();
|
||||
|
||||
for (Method m : msm.getMap().keySet())
|
||||
{
|
||||
Collection<Method> methods = msm.getMap().get(m);
|
||||
|
||||
ExecutionMapper em = new ExecutionMapper(m, methods);
|
||||
|
||||
ParallelExecutorMapping mapping = em.run();
|
||||
if (mapping == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
mapping.map(null, mapping.m1, mapping.m2).wasExecuted = true;
|
||||
|
||||
logger.debug("map methods mapped {} -> {}", mapping.m1, mapping.m2);
|
||||
|
||||
pmes.add(mapping);
|
||||
}
|
||||
|
||||
ParallelExecutorMapping finalm = new ParallelExecutorMapping(source, target);
|
||||
for (ParallelExecutorMapping pme : pmes)
|
||||
{
|
||||
finalm.merge(pme);
|
||||
}
|
||||
|
||||
return finalm;
|
||||
}
|
||||
|
||||
private ParallelExecutorMapping mapStaticMethods()
|
||||
{
|
||||
StaticMethodSignatureMapper smsm = new StaticMethodSignatureMapper();
|
||||
smsm.map(source, target);
|
||||
|
||||
List<ParallelExecutorMapping> pmes = new ArrayList<>();
|
||||
|
||||
for (Method m : smsm.getMap().keySet())
|
||||
{
|
||||
Collection<Method> methods = smsm.getMap().get(m);
|
||||
|
||||
ExecutionMapper em = new ExecutionMapper(m, methods);
|
||||
|
||||
ParallelExecutorMapping mapping = em.run();
|
||||
if (mapping == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Mapping map = mapping.map(null, mapping.m1, mapping.m2);
|
||||
map.wasExecuted = true;
|
||||
map.setWeight(mapping.same);
|
||||
|
||||
logger.debug("map static methods mapped {} -> {}", mapping.m1, mapping.m2);
|
||||
|
||||
pmes.add(mapping);
|
||||
}
|
||||
|
||||
ParallelExecutorMapping finalm = new ParallelExecutorMapping(source, target);
|
||||
for (ParallelExecutorMapping pme : pmes)
|
||||
{
|
||||
finalm.merge(pme);
|
||||
}
|
||||
|
||||
return finalm;
|
||||
}
|
||||
|
||||
private void mapMemberMethods(ParallelExecutorMapping mapping)
|
||||
{
|
||||
// pass #2 at method mapping, can use class file mapping learned
|
||||
|
||||
for (ClassFile cf : source.getClasses())
|
||||
{
|
||||
ClassFile other = (ClassFile) mapping.get(cf);
|
||||
if (other == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
List<Method> methods1 = cf.getMethods().stream()
|
||||
.filter(m -> !m.isStatic())
|
||||
.filter(m -> !m.getName().equals("<init>"))
|
||||
.filter(m -> m.getCode() != null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Method> methods2 = other.getMethods().stream()
|
||||
.filter(m -> !m.isStatic())
|
||||
.filter(m -> !m.getName().equals("<init>"))
|
||||
.filter(m -> m.getCode() != null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Method method : methods1)
|
||||
{
|
||||
if (mapping.get(method) != null) // already mapped
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
List<Method> possible = methods2.stream()
|
||||
.filter(m -> MappingExecutorUtil.isMaybeEqual(m.getDescriptor(), method.getDescriptor()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Run over execution mapper
|
||||
ExecutionMapper em = new ExecutionMapper(method, possible);
|
||||
ParallelExecutorMapping map = em.run();
|
||||
if (map == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
map.map(null, map.m1, map.m2);
|
||||
|
||||
logger.debug("Mapped {} -> {} based on exiting class mapping and method signatures", map.m1, map.m2);
|
||||
|
||||
mapping.merge(map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* execute and map already mapped methods that have not yet been "executed",
|
||||
* and apply their mapping
|
||||
* @param mapping
|
||||
* @return
|
||||
*/
|
||||
private boolean mapUnexecutedMethods(ParallelExecutorMapping mapping)
|
||||
{
|
||||
// map has already been reduced
|
||||
boolean mapped = false;
|
||||
for (Object o : mapping.getMap().keySet())
|
||||
{
|
||||
Mapping m = mapping.getMappings(o).iterator().next();
|
||||
|
||||
if (m.wasExecuted || !(m.getFrom() instanceof Method))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Method m1 = (Method) m.getFrom(), m2 = (Method) m.getObject();
|
||||
|
||||
if (m1.getCode() == null || m2.getCode() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// m was picked up as an invoke instruction when mapping
|
||||
// something else, but wasn't executed itself
|
||||
logger.debug("Wasn't executed {}", m);
|
||||
|
||||
ParallelExecutorMapping ma = MappingExecutorUtil.map(m1, m2);
|
||||
m.wasExecuted = true;
|
||||
mapped = true;
|
||||
mapping.merge(ma);
|
||||
}
|
||||
return mapped;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.mapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
|
||||
public class Mapping
|
||||
{
|
||||
private Object from;
|
||||
private Object object;
|
||||
private int count;
|
||||
private List<Instruction> ins = new ArrayList<>();
|
||||
public boolean wasExecuted;
|
||||
public int weight; // weight of mapping, based on same instruction count
|
||||
|
||||
public Mapping(Object from, Object object)
|
||||
{
|
||||
this.from = from;
|
||||
this.object = object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "Mapping{" + "from=" + from + ", object=" + object + ", count=" + count + '}';
|
||||
}
|
||||
|
||||
public Object getFrom()
|
||||
{
|
||||
return from;
|
||||
}
|
||||
|
||||
public Object getObject()
|
||||
{
|
||||
return object;
|
||||
}
|
||||
|
||||
public int getCount()
|
||||
{
|
||||
return count;
|
||||
}
|
||||
|
||||
public void inc()
|
||||
{
|
||||
++count;
|
||||
}
|
||||
|
||||
public void merge(Mapping other)
|
||||
{
|
||||
assert object == other.object;
|
||||
count += other.count;
|
||||
for (Instruction i : other.ins)
|
||||
{
|
||||
addInstruction(i);
|
||||
}
|
||||
wasExecuted |= other.wasExecuted;
|
||||
weight = Math.max(weight, other.weight);
|
||||
}
|
||||
|
||||
public void addInstruction(Instruction i)
|
||||
{
|
||||
if (!ins.contains(i))
|
||||
{
|
||||
ins.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void setWeight(int w)
|
||||
{
|
||||
if (w > weight)
|
||||
{
|
||||
weight = w;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.mapping;
|
||||
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Interfaces;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.Type;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.ArrayLoad;
|
||||
import net.runelite.asm.attributes.code.instruction.types.ConversionInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.DupInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.MappableInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.SetFieldInstruction;
|
||||
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
|
||||
import net.runelite.asm.execution.Execution;
|
||||
import net.runelite.asm.execution.Frame;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
import net.runelite.asm.execution.ParallellMappingExecutor;
|
||||
import net.runelite.asm.execution.StackContext;
|
||||
import net.runelite.asm.execution.VariableContext;
|
||||
import net.runelite.asm.execution.Variables;
|
||||
import net.runelite.asm.signature.Signature;
|
||||
|
||||
public class MappingExecutorUtil
|
||||
{
|
||||
public static ParallelExecutorMapping map(Method m1, Method m2)
|
||||
{
|
||||
ClassGroup group1 = m1.getClassFile().getGroup();
|
||||
ClassGroup group2 = m2.getClassFile().getGroup();
|
||||
|
||||
Execution e = new Execution(group1);
|
||||
e.step = true;
|
||||
Frame frame = new Frame(e, m1);
|
||||
frame.initialize();
|
||||
e.frames.add(frame);
|
||||
|
||||
Execution e2 = new Execution(group2);
|
||||
e2.step = true;
|
||||
Frame frame2 = new Frame(e2, m2);
|
||||
frame2.initialize();
|
||||
e2.frames.add(frame2);
|
||||
|
||||
frame.other = frame2;
|
||||
frame2.other = frame;
|
||||
|
||||
ParallellMappingExecutor parallel = new ParallellMappingExecutor(e, e2);
|
||||
ParallelExecutorMapping mappings = new ParallelExecutorMapping(m1.getClassFile().getGroup(),
|
||||
m2.getClassFile().getGroup());
|
||||
|
||||
mappings.m1 = m1;
|
||||
mappings.m2 = m2;
|
||||
|
||||
parallel.mappings = mappings;
|
||||
|
||||
int same = 0;
|
||||
while (parallel.step())
|
||||
{
|
||||
// get what each frame is paused/exited on
|
||||
InstructionContext p1 = parallel.getP1(), p2 = parallel.getP2();
|
||||
|
||||
assert p1.getInstruction() instanceof MappableInstruction;
|
||||
assert p2.getInstruction() instanceof MappableInstruction;
|
||||
|
||||
MappableInstruction mi1 = (MappableInstruction) p1.getInstruction(),
|
||||
mi2 = (MappableInstruction) p2.getInstruction();
|
||||
|
||||
boolean isSame = mi1.isSame(p1, p2);
|
||||
assert isSame == mi2.isSame(p2, p1)
|
||||
: "isSame fail " + p1.getInstruction() + " <> " + p2.getInstruction();
|
||||
|
||||
if (!isSame)
|
||||
{
|
||||
mappings.crashed = true;
|
||||
p1.getFrame().stop();
|
||||
p2.getFrame().stop();
|
||||
continue;
|
||||
}
|
||||
|
||||
++same;
|
||||
mi1.map(mappings, p1, p2);
|
||||
}
|
||||
|
||||
mappings.same = same;
|
||||
|
||||
return mappings;
|
||||
}
|
||||
|
||||
public static boolean isMappable(InvokeInstruction ii)
|
||||
{
|
||||
String className;
|
||||
|
||||
net.runelite.asm.pool.Method m = ii.getMethod();
|
||||
className = m.getClazz().getName();
|
||||
|
||||
if (className.startsWith("java/lang/reflect/") || className.startsWith("java/io/") || className.startsWith("java/util/"))
|
||||
return true;
|
||||
|
||||
if (className.startsWith("java/") || className.startsWith("netscape/") || className.startsWith("javax/"))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isInlineable(Instruction i)
|
||||
{
|
||||
if (!(i instanceof InvokeStatic))
|
||||
return false;
|
||||
|
||||
ClassGroup group = i.getInstructions().getCode().getMethod().getClassFile().getGroup();
|
||||
InvokeStatic is = (InvokeStatic) i;
|
||||
net.runelite.asm.pool.Method m = is.getMethod();
|
||||
|
||||
return group.findClass(m.getClazz().getName()) != null;
|
||||
}
|
||||
|
||||
public static InstructionContext resolve(
|
||||
InstructionContext ctx,
|
||||
StackContext from // pushed from ctx
|
||||
)
|
||||
{
|
||||
if (ctx.getInstruction() instanceof SetFieldInstruction)
|
||||
{
|
||||
StackContext s = ctx.getPops().get(0);
|
||||
return resolve(s.getPushed(), s);
|
||||
}
|
||||
|
||||
if (ctx.getInstruction() instanceof ConversionInstruction)
|
||||
{
|
||||
// assume it pops one and pushes one
|
||||
StackContext s = ctx.getPops().get(0);
|
||||
return resolve(s.getPushed(), s);
|
||||
}
|
||||
|
||||
if (ctx.getInstruction() instanceof DupInstruction)
|
||||
{
|
||||
DupInstruction d = (DupInstruction) ctx.getInstruction();
|
||||
StackContext s = d.getOriginal(from);
|
||||
return resolve(s.getPushed(), s);
|
||||
}
|
||||
|
||||
if (ctx.getInstruction() instanceof ArrayLoad)
|
||||
{
|
||||
// might be multidimensional array
|
||||
StackContext s = ctx.getPops().get(1); // the array
|
||||
return resolve(s.getPushed(), s);
|
||||
}
|
||||
|
||||
if (ctx.getInstruction() instanceof LVTInstruction)
|
||||
{
|
||||
LVTInstruction lvt = (LVTInstruction) ctx.getInstruction();
|
||||
Variables variables = ctx.getVariables();
|
||||
|
||||
if (lvt.store())
|
||||
{
|
||||
StackContext s = ctx.getPops().get(0); // is this right?
|
||||
return resolve(s.getPushed(), s);
|
||||
}
|
||||
else
|
||||
{
|
||||
VariableContext vctx = variables.get(lvt.getVariableIndex()); // variable being loaded
|
||||
assert vctx != null;
|
||||
|
||||
InstructionContext storedCtx = vctx.getInstructionWhichStored();
|
||||
if (storedCtx == null)
|
||||
return ctx; // initial parameter
|
||||
|
||||
if (vctx.isIsParameter())
|
||||
{
|
||||
// Normally storedCtx being an InvokeInstruction means that this resolves to the
|
||||
// return value of an invoke instruction, but if the variable is a parameter, it means
|
||||
// this storedCtx is the invoke instruction which called this method.
|
||||
assert storedCtx.getInstruction() instanceof InvokeInstruction;
|
||||
// In PME non static functions are never stepped into/aren't inline obfuscated
|
||||
assert storedCtx.getInstruction() instanceof InvokeStatic;
|
||||
|
||||
// Figure out parameter index from variable index.
|
||||
Signature sig = ctx.getFrame().getMethod().getDescriptor(); // signature of current method
|
||||
int paramIndex = 0;
|
||||
for (int lvtIndex = 0 /* static */;
|
||||
paramIndex < sig.size();
|
||||
lvtIndex += sig.getTypeOfArg(paramIndex++).getSize())
|
||||
if (lvtIndex == lvt.getVariableIndex())
|
||||
break;
|
||||
assert paramIndex < sig.size();
|
||||
|
||||
// Get stack context that was popped by the invoke
|
||||
// pops[0] is the first thing popped, which is the last parameter.
|
||||
StackContext sctx = storedCtx.getPops().get(sig.size() - 1 - paramIndex);
|
||||
return resolve(sctx.getPushed(), sctx);
|
||||
}
|
||||
|
||||
return resolve(storedCtx, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.getInstruction() instanceof InvokeStatic)
|
||||
{
|
||||
if (from.returnSource != null)
|
||||
{
|
||||
return resolve(from.returnSource.getPushed(), from.returnSource);
|
||||
}
|
||||
}
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
public static boolean isMaybeEqual(Type t1, Type t2)
|
||||
{
|
||||
if (t1.getDimensions() != t2.getDimensions())
|
||||
return false;
|
||||
|
||||
while (t1.getDimensions() > 0)
|
||||
{
|
||||
t1 = t1.getSubtype();
|
||||
t2 = t2.getSubtype();
|
||||
}
|
||||
|
||||
if (t1.isPrimitive() || t2.isPrimitive())
|
||||
return t1.equals(t2);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isMaybeEqual(Signature s1, Signature s2)
|
||||
{
|
||||
if (s1.size() != s2.size())
|
||||
return false;
|
||||
|
||||
if (!isMaybeEqual(s1.getReturnValue(), s2.getReturnValue()))
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < s1.size(); ++i)
|
||||
{
|
||||
Type t1 = s1.getTypeOfArg(i), t2 = s2.getTypeOfArg(i);
|
||||
|
||||
if (!isMaybeEqual(t1, t2))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isMaybeEqual(ClassFile cf1, ClassFile cf2)
|
||||
{
|
||||
if (cf1 == null && cf2 == null)
|
||||
return true;
|
||||
|
||||
if (cf1 == null || cf2 == null)
|
||||
return false;
|
||||
|
||||
if (cf1.getParent() != null || cf2.getParent() != null)
|
||||
{
|
||||
if (!isMaybeEqual(cf1.getParent(), cf2.getParent()))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise parents are not our classes
|
||||
if (!cf1.getParentClass().equals(cf2.getParentClass()))
|
||||
return false;
|
||||
}
|
||||
|
||||
Interfaces i1 = cf1.getInterfaces(), i2 = cf2.getInterfaces();
|
||||
if (i1.getInterfaces().size() != i2.getInterfaces().size())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isMaybeEqual(Field f1, Field f2)
|
||||
{
|
||||
if (f1 == null && f2 == null)
|
||||
return true;
|
||||
|
||||
if (f1 == null || f2 == null)
|
||||
return false;
|
||||
|
||||
if (f1.isStatic() != f2.isStatic())
|
||||
return false;
|
||||
|
||||
return isMaybeEqual(f1.getType(), f2.getType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.mapping;
|
||||
|
||||
import com.google.common.collect.LinkedHashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.signature.Signature;
|
||||
|
||||
public class MethodSignatureMapper
|
||||
{
|
||||
private Multimap<Method, Method> map = LinkedHashMultimap.create();
|
||||
|
||||
private List<Method> getMethods(ClassGroup group)
|
||||
{
|
||||
List<Method> methods = new ArrayList<>();
|
||||
for (ClassFile cf : group.getClasses())
|
||||
for (Method m : cf.getMethods())
|
||||
if (!m.isStatic() && !m.getName().equals("<init>") && m.getCode() != null)
|
||||
methods.add(m);
|
||||
return methods;
|
||||
}
|
||||
|
||||
private List<Method> getMethodsOfSignature(ClassGroup group, ClassFile cf, Signature sig)
|
||||
{
|
||||
return getMethods(group).stream()
|
||||
.filter(m -> MappingExecutorUtil.isMaybeEqual(cf, m.getClassFile()))
|
||||
.filter(m -> MappingExecutorUtil.isMaybeEqual(m.getDescriptor(), sig))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void map(ClassGroup group1, ClassGroup group2)
|
||||
{
|
||||
for (Method m : getMethods(group1))
|
||||
{
|
||||
map.putAll(m, getMethodsOfSignature(group2, m.getClassFile(), m.getDescriptor()));
|
||||
}
|
||||
}
|
||||
|
||||
public Multimap<Method, Method> getMap()
|
||||
{
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,420 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.mapping;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ParallelExecutorMapping
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(ParallelExecutorMapping.class);
|
||||
|
||||
private ClassGroup group, group2;
|
||||
private Multimap<Object, Mapping> map = HashMultimap.create();
|
||||
public Method m1, m2;
|
||||
public boolean crashed;
|
||||
public int same;
|
||||
|
||||
public ParallelExecutorMapping(ClassGroup group, ClassGroup group2)
|
||||
{
|
||||
this.group = group;
|
||||
this.group2 = group2;
|
||||
assert group != group2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "ParallelExecutorMapping{size = " + map.keySet().size() + ", crashed = " + crashed + ", same = " + same + ", m1 = " + m1 + ", m2 = " + m2 + "}";
|
||||
}
|
||||
|
||||
private Mapping getMapping(Object from, Object to)
|
||||
{
|
||||
for (Mapping m : map.get(from))
|
||||
{
|
||||
if (m.getObject() == to)
|
||||
{
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
||||
Mapping m = new Mapping(from, to);
|
||||
map.put(from, m);
|
||||
return m;
|
||||
}
|
||||
|
||||
private Object highest(Object from)
|
||||
{
|
||||
Mapping highest = null;
|
||||
for (Mapping m : map.get(from))
|
||||
{
|
||||
if (highest == null || m.getCount() > highest.getCount())
|
||||
{
|
||||
highest = m;
|
||||
}
|
||||
else if (m.getCount() == highest.getCount() && getName(from).compareTo(getName(highest.getObject())) > 0)
|
||||
{
|
||||
highest = m;
|
||||
}
|
||||
}
|
||||
return highest != null ? highest.getObject() : null;
|
||||
}
|
||||
|
||||
public void merge(ParallelExecutorMapping other)
|
||||
{
|
||||
assert this != other;
|
||||
|
||||
for (Entry<Object, Mapping> e : other.map.entries())
|
||||
{
|
||||
Object o = e.getKey();
|
||||
Mapping v = e.getValue();
|
||||
|
||||
Mapping m = this.getMapping(o, v.getObject());
|
||||
m.merge(v);
|
||||
}
|
||||
}
|
||||
|
||||
public Mapping map(Instruction mapper, Object one, Object two)
|
||||
{
|
||||
Mapping m = getMapping(one, two);
|
||||
|
||||
if (mapper != null)
|
||||
{
|
||||
m.addInstruction(mapper);
|
||||
}
|
||||
|
||||
belongs(one, group);
|
||||
belongs(two, group2);
|
||||
|
||||
sanityCheck(one, two);
|
||||
|
||||
m.inc();
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
private void sanityCheck(Object one, Object two)
|
||||
{
|
||||
if (one instanceof Field && two instanceof Field)
|
||||
{
|
||||
Field f1 = (Field) one;
|
||||
Field f2 = (Field) two;
|
||||
|
||||
assert f1.isStatic() == f2.isStatic() : "field map with static mismatch!";
|
||||
}
|
||||
}
|
||||
|
||||
private void mapClass(StaticInitializerIndexer staticIndexer1, StaticInitializerIndexer staticIndexer2, Object one, Object two)
|
||||
{
|
||||
ClassFile cf1, cf2;
|
||||
|
||||
if (one instanceof Field || two instanceof Field)
|
||||
{
|
||||
assert one instanceof Field;
|
||||
assert two instanceof Field;
|
||||
|
||||
Field f1 = (Field) one;
|
||||
Field f2 = (Field) two;
|
||||
|
||||
assert f1.isStatic() == f2.isStatic();
|
||||
|
||||
if (staticIndexer1.isStatic(f1) && staticIndexer2.isStatic(f2))
|
||||
{
|
||||
logger.debug("Mapping class of {} -> {} due to static initializer", f1, f2);
|
||||
}
|
||||
else if (f1.isStatic() || f2.isStatic())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cf1 = f1.getClassFile();
|
||||
cf2 = f2.getClassFile();
|
||||
}
|
||||
else if (one instanceof Method || two instanceof Method)
|
||||
{
|
||||
assert one instanceof Method;
|
||||
assert two instanceof Method;
|
||||
|
||||
Method m1 = (Method) one;
|
||||
Method m2 = (Method) two;
|
||||
|
||||
assert m1.isStatic() == m1.isStatic();
|
||||
|
||||
if (m1.isStatic() || m2.isStatic())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cf1 = m1.getClassFile();
|
||||
cf2 = m2.getClassFile();
|
||||
}
|
||||
else
|
||||
{
|
||||
assert false;
|
||||
return;
|
||||
}
|
||||
|
||||
belongs(cf1, group);
|
||||
belongs(cf2, group2);
|
||||
|
||||
Mapping m = getMapping(cf1, cf2);
|
||||
|
||||
m.inc();
|
||||
}
|
||||
|
||||
/**
|
||||
* makes the map one to one based on the weight of each mapping. If a
|
||||
* mapping is mapped from the same object multiple places, the highest
|
||||
* is used, and the other mapping are not considered when deducing the
|
||||
* mapping of the other objects.
|
||||
*/
|
||||
public void reduce()
|
||||
{
|
||||
List<Mapping> sorted = new ArrayList<>(map.values());
|
||||
|
||||
// Sort indepdent of the map's order, which is undefined
|
||||
Collections.sort(sorted, (m1, m2) ->
|
||||
{
|
||||
|
||||
// Number of times mapped
|
||||
int i = Integer.compare(m1.getCount(), m2.getCount());
|
||||
if (i != 0)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
|
||||
if (m1.weight != m2.weight)
|
||||
{
|
||||
// If equal, number of ins equal in the mapping
|
||||
return Integer.compare(m1.weight, m2.weight);
|
||||
}
|
||||
|
||||
logger.debug("Count and weight for {} <-> {} are the same! Sorting from name", m1, m2);
|
||||
|
||||
return getName(m1.getFrom()).compareTo(getName(m2.getFrom()));
|
||||
});
|
||||
|
||||
// reverse so highest is first
|
||||
Collections.reverse(sorted);
|
||||
|
||||
Multimap<Object, Mapping> reducedMap = HashMultimap.create();
|
||||
Map<Object, Object> reverse = new HashMap<>();
|
||||
|
||||
for (Mapping m : sorted)
|
||||
{
|
||||
if (reducedMap.containsKey(m.getFrom()))
|
||||
{
|
||||
logger.debug("Reduced out mapping {} because of {}", m, reducedMap.get(m.getFrom()).iterator().next());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (reverse.containsKey(m.getObject()))
|
||||
{
|
||||
logger.debug("Redudced out mapping {} because of {}", m, reducedMap.get(reverse.get(m.getObject())).iterator().next());
|
||||
continue;
|
||||
}
|
||||
|
||||
reducedMap.put(m.getFrom(), m);
|
||||
reverse.put(m.getObject(), m.getFrom());
|
||||
}
|
||||
|
||||
map = reducedMap;
|
||||
// map is now one to one
|
||||
}
|
||||
|
||||
public void buildClasses()
|
||||
{
|
||||
for (Object o : new HashSet<>(map.keySet()))
|
||||
{
|
||||
if (o instanceof ClassFile)
|
||||
{
|
||||
map.removeAll(o);
|
||||
}
|
||||
}
|
||||
|
||||
StaticInitializerIndexer staticIndexer1 = new StaticInitializerIndexer(group);
|
||||
staticIndexer1.index();
|
||||
|
||||
StaticInitializerIndexer staticIndexer2 = new StaticInitializerIndexer(group2);
|
||||
staticIndexer2.index();
|
||||
|
||||
Map<Object, Object> map = getMap();
|
||||
for (Object key : map.keySet())
|
||||
{
|
||||
Object value = map.get(key);
|
||||
|
||||
mapClass(staticIndexer1, staticIndexer2, key, value);
|
||||
}
|
||||
|
||||
map = getMap(); // rebuild map now we've inserted classes...
|
||||
reduce();
|
||||
|
||||
/* get leftover classes, they usually contain exclusively static
|
||||
* fields and methods so they're hard to pinpoint
|
||||
*/
|
||||
ClassGroupMapper m = new ClassGroupMapper(group, group2);
|
||||
m.map();
|
||||
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
if (!map.containsKey(cf))
|
||||
{
|
||||
ClassFile other = m.get(cf);
|
||||
if (other == null)
|
||||
{
|
||||
logger.info("Unable to map class {}", cf);
|
||||
}
|
||||
else
|
||||
{
|
||||
// these are both probably very small
|
||||
long nonStaticFields = cf.getFields().stream().filter(f -> !f.isStatic()).count(),
|
||||
nonStaticMethods = cf.getMethods().stream().filter(m2 -> !m2.isStatic()).count();
|
||||
|
||||
logger.info("Build classes fallback {} -> {}, non static fields: {}, non static methods: {}",
|
||||
cf, other, nonStaticFields, nonStaticMethods);
|
||||
|
||||
Mapping ma = getMapping(cf, other);
|
||||
ma.inc();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Object get(Object o)
|
||||
{
|
||||
return highest(o);
|
||||
}
|
||||
|
||||
public Collection<Mapping> getMappings(Object o)
|
||||
{
|
||||
return map.get(o);
|
||||
}
|
||||
|
||||
public Map<Object, Object> getMap()
|
||||
{
|
||||
Map<Object, Object> m = new HashMap<>();
|
||||
|
||||
for (Object o : map.keySet())
|
||||
{
|
||||
m.put(o, highest(o));
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
private void belongs(Object o, ClassGroup to)
|
||||
{
|
||||
if (o instanceof Field)
|
||||
{
|
||||
Field f = (Field) o;
|
||||
assert f.getClassFile().getGroup() == to;
|
||||
}
|
||||
else if (o instanceof Method)
|
||||
{
|
||||
Method m = (Method) o;
|
||||
assert m.getClassFile().getGroup() == to;
|
||||
}
|
||||
else if (o instanceof ClassFile)
|
||||
{
|
||||
ClassFile c = (ClassFile) o;
|
||||
assert c.getGroup() == to;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert false;
|
||||
}
|
||||
}
|
||||
|
||||
public int contradicts(ParallelExecutorMapping other)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (Entry<Object, Mapping> e : other.map.entries())
|
||||
{
|
||||
Object key = e.getKey();
|
||||
Mapping value = e.getValue();
|
||||
|
||||
Object highest = highest(key);
|
||||
|
||||
if (highest != null && highest != value.getObject())
|
||||
{
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public boolean hasAnyMultiples()
|
||||
{
|
||||
for (Object o : map.keySet())
|
||||
{
|
||||
if (map.get(o).size() > 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String getName(Object o)
|
||||
{
|
||||
if (o instanceof Field)
|
||||
{
|
||||
Field f = (Field) o;
|
||||
return f.getClassFile().getClassName() + "." + f.getName();
|
||||
}
|
||||
else if (o instanceof Method)
|
||||
{
|
||||
Method m = (Method) o;
|
||||
return m.getClassFile().getClassName() + "." + m.getName();
|
||||
}
|
||||
else if (o instanceof ClassFile)
|
||||
{
|
||||
ClassFile c = (ClassFile) o;
|
||||
return c.getName();
|
||||
}
|
||||
else
|
||||
{
|
||||
assert false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2018, 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 net.runelite.deob.deobfuscators.mapping;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.InstructionType;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instructions.PutStatic;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Finds fields which are initialized in clinit
|
||||
*
|
||||
* @author Adam
|
||||
*/
|
||||
public class StaticInitializerIndexer
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(StaticInitializerIndexer.class);
|
||||
|
||||
private final ClassGroup group;
|
||||
private final Set<Field> fields = new HashSet<>();
|
||||
|
||||
public StaticInitializerIndexer(ClassGroup group)
|
||||
{
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public void index()
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
Method method = cf.findMethod("<clinit>");
|
||||
if (method == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Instructions instructions = method.getCode().getInstructions();
|
||||
for (Instruction i : instructions.getInstructions())
|
||||
{
|
||||
if (i.getType() != InstructionType.PUTSTATIC)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
PutStatic putstatic = (PutStatic) i;
|
||||
if (!putstatic.getField().getClazz().equals(cf.getPoolClass()) || putstatic.getMyField() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
fields.add(putstatic.getMyField());
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Indexed {} statically initialized fields", fields.size());
|
||||
}
|
||||
|
||||
public boolean isStatic(Field field)
|
||||
{
|
||||
return fields.contains(field);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.mapping;
|
||||
|
||||
import com.google.common.collect.LinkedHashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.signature.Signature;
|
||||
|
||||
public class StaticMethodSignatureMapper
|
||||
{
|
||||
private Multimap<Method, Method> map = LinkedHashMultimap.create();
|
||||
|
||||
private List<Method> getStaticMethods(ClassGroup group)
|
||||
{
|
||||
List<Method> methods = new ArrayList<>();
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
if (cf.getName().startsWith("net/runelite"))
|
||||
{
|
||||
// XXX net/runelite/rs/Reflection uses invokedynamic
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
// this used to check the method wasnt <clinit>,
|
||||
// but fernflower was modified to not remove code
|
||||
// in clinit and place into ConstantValue attriutes
|
||||
// on fields, so the execution order of clinit no longer
|
||||
// depends on field order
|
||||
if (m.isStatic())
|
||||
{
|
||||
methods.add(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
private List<Method> getStaticMethodsOfSignature(ClassGroup group, Signature sig)
|
||||
{
|
||||
return getStaticMethods(group).stream().filter(
|
||||
m -> MappingExecutorUtil.isMaybeEqual(m.getDescriptor(), sig)
|
||||
).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void map(ClassGroup group1, ClassGroup group2)
|
||||
{
|
||||
for (Method m : getStaticMethods(group1))
|
||||
{
|
||||
map.putAll(m, getStaticMethodsOfSignature(group2, m.getDescriptor()));
|
||||
}
|
||||
}
|
||||
|
||||
public Multimap<Method, Method> getMap()
|
||||
{
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.menuaction;
|
||||
|
||||
import java.util.Objects;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.Label;
|
||||
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
|
||||
|
||||
class Comparison
|
||||
{
|
||||
LVTInstruction lvt;
|
||||
Instruction ldc;
|
||||
Instruction cmp;
|
||||
|
||||
Label next;
|
||||
|
||||
Number getConstant()
|
||||
{
|
||||
return (Number) ((PushConstantInstruction) ldc).getConstant();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
int hash = 3;
|
||||
hash = 41 * hash + Objects.hashCode(this.cmp);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (obj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
final Comparison other = (Comparison) obj;
|
||||
if (!Objects.equals(this.cmp, other.cmp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "Comparison{" + "lvt=" + lvt + ", ldc=" + ldc + ", cmp=" + cmp + ", next=" + next + '}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.menuaction;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.InstructionType;
|
||||
import static net.runelite.asm.attributes.code.InstructionType.IF_ICMPEQ;
|
||||
import static net.runelite.asm.attributes.code.InstructionType.IF_ICMPGE;
|
||||
import static net.runelite.asm.attributes.code.InstructionType.IF_ICMPGT;
|
||||
import static net.runelite.asm.attributes.code.InstructionType.IF_ICMPLE;
|
||||
import static net.runelite.asm.attributes.code.InstructionType.IF_ICMPLT;
|
||||
import static net.runelite.asm.attributes.code.InstructionType.IF_ICMPNE;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.Label;
|
||||
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
|
||||
import net.runelite.asm.attributes.code.instructions.Goto;
|
||||
import net.runelite.asm.attributes.code.instructions.If;
|
||||
import net.runelite.asm.attributes.code.instructions.IfICmpEq;
|
||||
import net.runelite.asm.attributes.code.instructions.IfICmpNe;
|
||||
import net.runelite.asm.execution.Execution;
|
||||
import net.runelite.asm.execution.Frame;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Sort the ifs in menuAction
|
||||
*
|
||||
* @author Adam
|
||||
*/
|
||||
public class MenuActionDeobfuscator implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(MenuActionDeobfuscator.class);
|
||||
|
||||
private static final int THRESHOLD_EQ = 50;
|
||||
private static final int THRESHOLD_LT = 1;
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
run(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void run(Method method)
|
||||
{
|
||||
if (method.getCode() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Execution execution = new Execution(method.getClassFile().getGroup());
|
||||
execution.addMethod(method);
|
||||
execution.noInvoke = true;
|
||||
|
||||
Multimap<Integer, Comparison> comps = HashMultimap.create();
|
||||
|
||||
execution.addExecutionVisitor((InstructionContext ictx) ->
|
||||
{
|
||||
Instruction i = ictx.getInstruction();
|
||||
Frame frame = ictx.getFrame();
|
||||
|
||||
if (i instanceof If)
|
||||
{
|
||||
InstructionContext ctx1 = ictx.getPops().get(0).getPushed(); // constant
|
||||
InstructionContext ctx2 = ictx.getPops().get(1).getPushed(); // lvt
|
||||
|
||||
if (ctx1.getInstruction() instanceof PushConstantInstruction
|
||||
&& ctx2.getInstruction() instanceof LVTInstruction)
|
||||
{
|
||||
Comparison comparison = new Comparison();
|
||||
comparison.cmp = i;
|
||||
comparison.ldc = ctx1.getInstruction();
|
||||
comparison.lvt = (LVTInstruction) ctx2.getInstruction();
|
||||
|
||||
comps.put(comparison.lvt.getVariableIndex(), comparison);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
execution.run();
|
||||
|
||||
for (int i : comps.keySet())
|
||||
{
|
||||
Collection<Comparison> get = comps.get(i);
|
||||
long l = get.stream().filter(c -> c.cmp.getType() == IF_ICMPGE
|
||||
|| c.cmp.getType() == IF_ICMPGT
|
||||
|| c.cmp.getType() == IF_ICMPLE
|
||||
|| c.cmp.getType() == IF_ICMPLT
|
||||
).count();
|
||||
|
||||
List<Comparison> eqcmp = get.stream()
|
||||
.filter(c -> c.cmp.getType() == IF_ICMPEQ || c.cmp.getType() == IF_ICMPNE)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (get.size() > THRESHOLD_EQ && l <= THRESHOLD_LT)
|
||||
{
|
||||
logger.info("Sorting {} comparisons in {}", eqcmp.size(), method);
|
||||
insert(method, eqcmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void insert(Method method, List<Comparison> comparisons)
|
||||
{
|
||||
Instructions instructions = method.getCode().getInstructions();
|
||||
List<Instruction> ins = instructions.getInstructions();
|
||||
|
||||
// replace all if(var == constant) with a jump to the false branch
|
||||
// then, insert before the first jump the ifs to jump to the old
|
||||
// true branch
|
||||
//
|
||||
// this is probably actually lookupswitch but it isn't mappable
|
||||
// currently...
|
||||
int min = -1;
|
||||
|
||||
for (Comparison comp : comparisons)
|
||||
{
|
||||
if (min == -1)
|
||||
{
|
||||
min = ins.indexOf(comp.lvt);
|
||||
}
|
||||
else
|
||||
{
|
||||
min = Math.min(min, ins.indexOf(comp.lvt));
|
||||
}
|
||||
|
||||
if (comp.cmp.getType() == InstructionType.IF_ICMPEQ)
|
||||
{
|
||||
If cmp = (If) comp.cmp;
|
||||
|
||||
// remove
|
||||
instructions.remove(comp.ldc);
|
||||
instructions.remove((Instruction) comp.lvt);
|
||||
instructions.remove(comp.cmp);
|
||||
|
||||
comp.next = cmp.getJumps().get(0);
|
||||
}
|
||||
else if (comp.cmp.getType() == InstructionType.IF_ICMPNE)
|
||||
{
|
||||
// replace with goto dest
|
||||
If cmp = (If) comp.cmp;
|
||||
|
||||
int idx = ins.indexOf(cmp);
|
||||
assert idx != -1;
|
||||
comp.next = instructions.createLabelFor(ins.get(idx + 1));
|
||||
|
||||
instructions.remove(comp.ldc);
|
||||
instructions.remove((Instruction) comp.lvt);
|
||||
|
||||
instructions.replace(comp.cmp, new Goto(instructions, cmp.getJumps().get(0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
assert min != -1;
|
||||
|
||||
// sort comparisons - but if they jump to the same address, they are equal..
|
||||
List<Comparison> sortedComparisons = new ArrayList<>(comparisons);
|
||||
Collections.sort(sortedComparisons, (c1, c2) -> compare(comparisons, c1, c2));
|
||||
|
||||
// reinsert jumps
|
||||
for (int i = 0; i < sortedComparisons.size(); ++i)
|
||||
{
|
||||
Comparison comp = sortedComparisons.get(i);
|
||||
Instruction lvt = (Instruction) comp.lvt;
|
||||
|
||||
lvt.setInstructions(instructions);
|
||||
comp.ldc.setInstructions(instructions);
|
||||
|
||||
instructions.addInstruction(min++, lvt);
|
||||
instructions.addInstruction(min++, comp.ldc);
|
||||
|
||||
// use if_icmpeq if what follows also jumps to the same location
|
||||
boolean multiple = i + 1 < sortedComparisons.size()
|
||||
&& sortedComparisons.get(i + 1).next == comp.next;
|
||||
if (multiple)
|
||||
{
|
||||
instructions.addInstruction(min++, new IfICmpEq(instructions, comp.next));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// fernflower decompiles a series of if_icmpeq as chains of not equal expressions
|
||||
Label label = instructions.createLabelFor(ins.get(min));
|
||||
instructions.addInstruction(min++, new IfICmpNe(instructions, label));
|
||||
instructions.addInstruction(min++, new Goto(instructions, comp.next));
|
||||
++min; // go past label
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int compare(List<Comparison> comparisons, Comparison c1, Comparison c2)
|
||||
{
|
||||
// Compare by constant
|
||||
|
||||
assert comparisons.contains(c1);
|
||||
assert comparisons.contains(c2);
|
||||
|
||||
// The constant of a comparison is the lowest comparison that jumps to the same location,
|
||||
// if all jumps to the same label are in the same location fernflower will decompile
|
||||
// to if (foo || bar)
|
||||
Comparison mc1 = comparisons.stream()
|
||||
.filter(c -> c1.next == c.next)
|
||||
.min((m1, m2) -> Integer.compare(m1.getConstant().intValue(), m2.getConstant().intValue()))
|
||||
.get();
|
||||
|
||||
Comparison mc2 = comparisons.stream()
|
||||
.filter(c -> c2.next == c.next)
|
||||
.min((m1, m2) -> Integer.compare(m1.getConstant().intValue(), m2.getConstant().intValue()))
|
||||
.get();
|
||||
|
||||
return Integer.compare(mc1.getConstant().intValue(), mc2.getConstant().intValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.packethandler;
|
||||
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instructions.GetStatic;
|
||||
import net.runelite.asm.attributes.code.instructions.IALoad;
|
||||
import net.runelite.asm.attributes.code.instructions.PutStatic;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class PacketLengthFinder
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(PacketLengthFinder.class);
|
||||
|
||||
private final ClassGroup group;
|
||||
private final PacketTypeFinder packetType;
|
||||
|
||||
private Field packetLength;
|
||||
private GetStatic getArray;
|
||||
private GetStatic getType;
|
||||
private IALoad load;
|
||||
private PutStatic store;
|
||||
|
||||
public PacketLengthFinder(ClassGroup group, PacketTypeFinder packetType)
|
||||
{
|
||||
this.group = group;
|
||||
this.packetType = packetType;
|
||||
}
|
||||
|
||||
public Field getPacketLength()
|
||||
{
|
||||
return packetLength;
|
||||
}
|
||||
|
||||
public GetStatic getGetArray()
|
||||
{
|
||||
return getArray;
|
||||
}
|
||||
|
||||
public GetStatic getGetType()
|
||||
{
|
||||
return getType;
|
||||
}
|
||||
|
||||
public IALoad getLoad()
|
||||
{
|
||||
return load;
|
||||
}
|
||||
|
||||
public PutStatic getStore()
|
||||
{
|
||||
return store;
|
||||
}
|
||||
|
||||
public void find()
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method method : cf.getMethods())
|
||||
{
|
||||
run(method.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Found packet length: {}", packetLength);
|
||||
}
|
||||
|
||||
// getstatic class272/field3690 [I
|
||||
// getstatic Client/packetType I
|
||||
// iaload
|
||||
// putstatic Client/packetLength I
|
||||
private void run(Code code)
|
||||
{
|
||||
if (code == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Instructions instructions = code.getInstructions();
|
||||
Field type = packetType.getPacketType();
|
||||
|
||||
for (int i = 0; i < instructions.getInstructions().size() - 3; ++i)
|
||||
{
|
||||
Instruction i1 = instructions.getInstructions().get(i),
|
||||
i2 = instructions.getInstructions().get(i + 1),
|
||||
i3 = instructions.getInstructions().get(i + 2),
|
||||
i4 = instructions.getInstructions().get(i + 3);
|
||||
|
||||
if (!(i1 instanceof GetStatic))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(i2 instanceof GetStatic))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GetStatic gs = (GetStatic) i2;
|
||||
|
||||
if (gs.getMyField() != type)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(i3 instanceof IALoad))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(i4 instanceof PutStatic))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
PutStatic ps = (PutStatic) i4;
|
||||
assert packetLength == null : "packetLength already found";
|
||||
packetLength = ps.getMyField();
|
||||
getArray = (GetStatic) i1;
|
||||
getType = gs;
|
||||
load = (IALoad) i3;
|
||||
store = ps;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.packethandler;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import net.runelite.asm.Type;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
|
||||
public class PacketRead
|
||||
{
|
||||
private final Type type; // type of read, from return of function
|
||||
private final Instruction getBuffer; // getstatic
|
||||
private final InstructionContext invokeCtx;
|
||||
private final Instruction invoke; // invoke instruction to read data
|
||||
private Instruction store; // lvt store for reorderable reads
|
||||
|
||||
public PacketRead(Type type, Instruction getBuffer, InstructionContext invokeCtx)
|
||||
{
|
||||
this.type = type;
|
||||
this.getBuffer = getBuffer;
|
||||
this.invokeCtx = invokeCtx;
|
||||
this.invoke = invokeCtx.getInstruction();
|
||||
}
|
||||
|
||||
public Type getType()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
public Instruction getGetBuffer()
|
||||
{
|
||||
return getBuffer;
|
||||
}
|
||||
|
||||
public InstructionContext getInvokeCtx()
|
||||
{
|
||||
return invokeCtx;
|
||||
}
|
||||
|
||||
public Instruction getInvoke()
|
||||
{
|
||||
return invoke;
|
||||
}
|
||||
|
||||
public Instruction getStore()
|
||||
{
|
||||
return store;
|
||||
}
|
||||
|
||||
public void setStore(Instruction store)
|
||||
{
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
int hash = 5;
|
||||
hash = 37 * hash + Objects.hashCode(this.type);
|
||||
hash = 37 * hash + Objects.hashCode(this.invoke);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (obj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
final PacketRead other = (PacketRead) obj;
|
||||
if (!Objects.equals(this.type, other.type))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.invoke, other.invoke))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.packethandler;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.InstructionType;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.SetFieldInstruction;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class PacketTypeFinder
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(PacketTypeFinder.class);
|
||||
|
||||
private final ClassGroup group;
|
||||
|
||||
private final Map<Field, Integer> sets = new HashMap<>();
|
||||
private Field packetType;
|
||||
|
||||
public PacketTypeFinder(ClassGroup group)
|
||||
{
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public Field getPacketType()
|
||||
{
|
||||
return packetType;
|
||||
}
|
||||
|
||||
public void find()
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method method : cf.getMethods())
|
||||
{
|
||||
run(method.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
packetType = sets.entrySet().stream()
|
||||
.max((entry1, entry2) -> Integer.compare(entry1.getValue(), entry2.getValue()))
|
||||
.get()
|
||||
.getKey();
|
||||
|
||||
logger.info("Identified {} as packetType", packetType);
|
||||
}
|
||||
|
||||
private void run(Code code)
|
||||
{
|
||||
if (code == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Instructions instructions = code.getInstructions();
|
||||
|
||||
for (int i = 0; i < instructions.getInstructions().size() - 1; ++i)
|
||||
{
|
||||
Instruction i1 = instructions.getInstructions().get(i),
|
||||
i2 = instructions.getInstructions().get(i + 1);
|
||||
|
||||
if (i1 instanceof PushConstantInstruction && i2.getType() == InstructionType.PUTSTATIC)
|
||||
{
|
||||
PushConstantInstruction pci = (PushConstantInstruction) i1;
|
||||
SetFieldInstruction sfi = (SetFieldInstruction) i2;
|
||||
Field field = sfi.getMyField();
|
||||
|
||||
if (Objects.equal(-1, pci.getConstant()) && field != null)
|
||||
{
|
||||
Integer count = sets.get(field);
|
||||
if (count == null)
|
||||
{
|
||||
sets.put(field, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
sets.put(field, count + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.packetwrite;
|
||||
|
||||
import java.util.Collection;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.Type;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
|
||||
import net.runelite.asm.attributes.code.instructions.GetStatic;
|
||||
import net.runelite.asm.attributes.code.instructions.LDC;
|
||||
import net.runelite.asm.attributes.code.instructions.PutStatic;
|
||||
import static net.runelite.deob.deobfuscators.transformers.OpcodesTransformer.RUNELITE_OPCODES;
|
||||
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
|
||||
import static org.objectweb.asm.Opcodes.ACC_STATIC;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
class OpcodeReplacer
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(OpcodeReplacer.class);
|
||||
|
||||
public void run(ClassGroup group, Collection<PacketWrite> writes)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
ClassFile runeliteOpcodes = group.findClass(RUNELITE_OPCODES);
|
||||
assert runeliteOpcodes != null : "Opcodes class must exist";
|
||||
|
||||
for (PacketWrite wp : writes)
|
||||
{
|
||||
Instructions ins = wp.getInstructions();
|
||||
|
||||
Instruction param = wp.getOpcodeIns();
|
||||
if (!(param instanceof PushConstantInstruction))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
final String fieldName = "PACKET_CLIENT_" + wp.getOpcode();
|
||||
|
||||
net.runelite.asm.pool.Field field = new net.runelite.asm.pool.Field(
|
||||
new net.runelite.asm.pool.Class(RUNELITE_OPCODES),
|
||||
fieldName,
|
||||
Type.INT
|
||||
);
|
||||
ins.replace(param, new GetStatic(ins, field));
|
||||
|
||||
if (runeliteOpcodes.findField(fieldName) == null)
|
||||
{
|
||||
Field opField = new Field(runeliteOpcodes, fieldName, Type.INT);
|
||||
// ACC_FINAL causes javac to inline the fields, which prevents
|
||||
// the mapper from doing field mapping
|
||||
opField.setAccessFlags(ACC_PUBLIC | ACC_STATIC);
|
||||
// setting a non-final static field value
|
||||
// doesn't work with fernflower
|
||||
opField.setValue(wp.getOpcode());
|
||||
runeliteOpcodes.addField(opField);
|
||||
|
||||
// add initialization
|
||||
Method clinit = runeliteOpcodes.findMethod("<clinit>");
|
||||
assert clinit != null;
|
||||
Instructions instructions = clinit.getCode().getInstructions();
|
||||
instructions.addInstruction(0, new LDC(instructions, wp.getOpcode()));
|
||||
instructions.addInstruction(1, new PutStatic(instructions, opField));
|
||||
}
|
||||
|
||||
++count;
|
||||
}
|
||||
|
||||
logger.info("Injected {} packet writes", count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.packetwrite;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
|
||||
class PacketWrite
|
||||
{
|
||||
InstructionContext putOpcode;
|
||||
List<InstructionContext> writes = new ArrayList<>();
|
||||
|
||||
Instruction getOpcodeIns()
|
||||
{
|
||||
return putOpcode.getPops().get(0).getPushed().getInstruction();
|
||||
}
|
||||
|
||||
public int getOpcode()
|
||||
{
|
||||
return ((Number) ((PushConstantInstruction) getOpcodeIns()).getConstant()).intValue();
|
||||
}
|
||||
|
||||
Instructions getInstructions()
|
||||
{
|
||||
return putOpcode.getInstruction().getInstructions();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.packetwrite;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Type;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import static net.runelite.asm.attributes.code.InstructionType.INVOKEVIRTUAL;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.Label;
|
||||
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.ReturnInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.SetFieldInstruction;
|
||||
import net.runelite.asm.attributes.code.instructions.GetStatic;
|
||||
import net.runelite.asm.attributes.code.instructions.Goto;
|
||||
import net.runelite.asm.attributes.code.instructions.If;
|
||||
import net.runelite.asm.attributes.code.instructions.If0;
|
||||
import net.runelite.asm.attributes.code.instructions.IfEq;
|
||||
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
|
||||
import net.runelite.asm.execution.Execution;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
import net.runelite.asm.execution.StackContext;
|
||||
import net.runelite.asm.pool.Method;
|
||||
import net.runelite.asm.signature.Signature;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import net.runelite.deob.c2s.RWOpcodeFinder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class PacketWriteDeobfuscator implements Deobfuscator
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(PacketWriteDeobfuscator.class);
|
||||
|
||||
private static final String RUNELITE_PACKET = "RUNELITE_PACKET";
|
||||
|
||||
private final OpcodeReplacer opcodeReplacer = new OpcodeReplacer();
|
||||
|
||||
private RWOpcodeFinder rw;
|
||||
private PacketWrite cur;
|
||||
private final Map<Instruction, PacketWrite> writes = new HashMap<>();
|
||||
|
||||
public void visit(InstructionContext ctx)
|
||||
{
|
||||
if (isEnd(ctx))
|
||||
{
|
||||
end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.getInstruction().getType() != INVOKEVIRTUAL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InvokeInstruction ii = (InvokeInstruction) ctx.getInstruction();
|
||||
|
||||
if (ii.getMethods().contains(rw.getWriteOpcode()))
|
||||
{
|
||||
end();
|
||||
PacketWrite write = start();
|
||||
write.putOpcode = ctx;
|
||||
return;
|
||||
}
|
||||
|
||||
PacketWrite write = cur;
|
||||
if (write == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ii.getMethod().getClazz().getName().equals(rw.getWriteOpcode().getClassFile().getSuperName()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
write.writes.add(ctx);
|
||||
}
|
||||
|
||||
private PacketWrite start()
|
||||
{
|
||||
end();
|
||||
cur = new PacketWrite();
|
||||
return cur;
|
||||
}
|
||||
|
||||
private void end()
|
||||
{
|
||||
if (cur != null)
|
||||
{
|
||||
if (!writes.containsKey(cur.putOpcode.getInstruction()))
|
||||
{
|
||||
writes.put(cur.putOpcode.getInstruction(), cur);
|
||||
}
|
||||
cur = null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEnd(InstructionContext ctx)
|
||||
{
|
||||
// conditions where packet write ends:
|
||||
// any invoke that isn't to the packet buffer
|
||||
// any variable assignment
|
||||
// any field assignment
|
||||
// any conditional jump
|
||||
// any return
|
||||
|
||||
Instruction i = ctx.getInstruction();
|
||||
|
||||
if (i instanceof InvokeInstruction)
|
||||
{
|
||||
InvokeInstruction ii = (InvokeInstruction) i;
|
||||
Method method = ii.getMethod();
|
||||
|
||||
if (!method.getClazz().equals(rw.getSecretBuffer().getPoolClass())
|
||||
&& !method.getClazz().equals(rw.getBuffer().getPoolClass()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (i instanceof LVTInstruction)
|
||||
{
|
||||
LVTInstruction lvt = (LVTInstruction) i;
|
||||
if (lvt.store())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (i instanceof SetFieldInstruction)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (i instanceof If || i instanceof If0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (i instanceof ReturnInstruction)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
rw = new RWOpcodeFinder(group);
|
||||
rw.find();
|
||||
|
||||
Execution e = new Execution(group);
|
||||
e.addExecutionVisitor(this::visit);
|
||||
e.populateInitialMethods();
|
||||
e.run();
|
||||
|
||||
end();
|
||||
|
||||
opcodeReplacer.run(group, writes.values());
|
||||
|
||||
int count = 0;
|
||||
int writesCount = 0;
|
||||
|
||||
for (PacketWrite write : writes.values())
|
||||
{
|
||||
if (write.writes.isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
insert(group, write);
|
||||
++count;
|
||||
writesCount += write.writes.size();
|
||||
}
|
||||
|
||||
logger.info("Converted buffer write methods for {} opcodes ({} writes)", count, writesCount);
|
||||
}
|
||||
|
||||
private void insert(ClassGroup group, PacketWrite write)
|
||||
{
|
||||
Instructions instructions = write.putOpcode.getInstruction().getInstructions();
|
||||
List<Instruction> ins = instructions.getInstructions();
|
||||
|
||||
InstructionContext firstWrite = write.writes.get(0);
|
||||
InstructionContext lastWrite = write.writes.get(write.writes.size() - 1);
|
||||
|
||||
int idx = ins.indexOf(lastWrite.getInstruction());
|
||||
assert idx != -1;
|
||||
++idx; // past write
|
||||
|
||||
Label afterWrites = instructions.createLabelFor(ins.get(idx));
|
||||
|
||||
// pops arg, getfield
|
||||
InstructionContext beforeFirstWrite = firstWrite.getPops().get(1).getPushed();
|
||||
Label putOpcode = instructions.createLabelFor(beforeFirstWrite.getInstruction(), true);
|
||||
|
||||
idx = ins.indexOf(beforeFirstWrite.getInstruction());
|
||||
assert idx != -1;
|
||||
--idx;
|
||||
|
||||
net.runelite.asm.pool.Field field = new net.runelite.asm.pool.Field(
|
||||
new net.runelite.asm.pool.Class(findClient(group).getName()),
|
||||
RUNELITE_PACKET,
|
||||
Type.BOOLEAN
|
||||
);
|
||||
|
||||
instructions.addInstruction(idx++, new GetStatic(instructions, field));
|
||||
instructions.addInstruction(idx++, new IfEq(instructions, putOpcode));
|
||||
Instruction before = ins.get(idx);
|
||||
for (InstructionContext ctx : write.writes)
|
||||
{
|
||||
insert(instructions, ctx, before);
|
||||
}
|
||||
idx = ins.indexOf(before);
|
||||
instructions.addInstruction(idx++, new Goto(instructions, afterWrites));
|
||||
}
|
||||
|
||||
private void insert(Instructions ins, InstructionContext ic, Instruction before)
|
||||
{
|
||||
List<StackContext> pops = new ArrayList<>(ic.getPops());
|
||||
Collections.reverse(pops);
|
||||
for (StackContext sc : pops)
|
||||
{
|
||||
insert(ins, sc.getPushed(), before);
|
||||
}
|
||||
|
||||
Instruction i = ic.getInstruction().clone();
|
||||
i = translate(i);
|
||||
assert i.getInstructions() == ins;
|
||||
|
||||
int idx = ins.getInstructions().indexOf(before);
|
||||
assert idx != -1;
|
||||
|
||||
ins.addInstruction(idx, i);
|
||||
}
|
||||
|
||||
private Instruction translate(Instruction i)
|
||||
{
|
||||
if (!(i instanceof InvokeVirtual))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
|
||||
InvokeVirtual ii = (InvokeVirtual) i;
|
||||
Method invoked = ii.getMethod();
|
||||
|
||||
assert invoked.getType().size() == 1;
|
||||
|
||||
Type argumentType = invoked.getType().getTypeOfArg(0);
|
||||
|
||||
Method invokeMethod;
|
||||
if (argumentType.equals(Type.BYTE))
|
||||
{
|
||||
invokeMethod = new Method(
|
||||
ii.getMethod().getClazz(),
|
||||
"runeliteWriteByte",
|
||||
new Signature("(B)V")
|
||||
);
|
||||
}
|
||||
else if (argumentType.equals(Type.SHORT))
|
||||
{
|
||||
invokeMethod = new Method(
|
||||
ii.getMethod().getClazz(),
|
||||
"runeliteWriteShort",
|
||||
new Signature("(S)V")
|
||||
);
|
||||
}
|
||||
else if (argumentType.equals(Type.INT))
|
||||
{
|
||||
invokeMethod = new Method(
|
||||
ii.getMethod().getClazz(),
|
||||
"runeliteWriteInt",
|
||||
new Signature("(I)V")
|
||||
);
|
||||
}
|
||||
else if (argumentType.equals(Type.LONG))
|
||||
{
|
||||
invokeMethod = new Method(
|
||||
ii.getMethod().getClazz(),
|
||||
"runeliteWriteLong",
|
||||
new Signature("(J)V")
|
||||
);
|
||||
}
|
||||
else if (argumentType.equals(Type.STRING))
|
||||
{
|
||||
invokeMethod = new Method(
|
||||
ii.getMethod().getClazz(),
|
||||
"runeliteWriteString",
|
||||
new Signature("(Ljava/lang/String;)V")
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalStateException("Unknown type " + argumentType);
|
||||
}
|
||||
|
||||
return new InvokeVirtual(i.getInstructions(), invokeMethod);
|
||||
}
|
||||
|
||||
private ClassFile findClient(ClassGroup group)
|
||||
{
|
||||
// "client" in vainlla but "Client" in deob..
|
||||
ClassFile cf = group.findClass("client");
|
||||
return cf != null ? cf : group.findClass("Client");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.transformers;
|
||||
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.Type;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.InstructionType;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instructions.ALoad;
|
||||
import net.runelite.asm.attributes.code.instructions.IfNull;
|
||||
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
|
||||
import net.runelite.asm.attributes.code.instructions.VReturn;
|
||||
import net.runelite.asm.signature.Signature;
|
||||
import net.runelite.deob.Transformer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ClientErrorTransformer implements Transformer
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(ClientErrorTransformer.class);
|
||||
|
||||
private boolean done = false;
|
||||
|
||||
@Override
|
||||
public void transform(ClassGroup group)
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
transform(m);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Transformed: " + done);
|
||||
}
|
||||
|
||||
private void transform(Method m)
|
||||
{
|
||||
if (!m.isStatic() || m.getDescriptor().size() != 2
|
||||
|| !m.getDescriptor().getTypeOfArg(0).equals(Type.STRING)
|
||||
|| !m.getDescriptor().getTypeOfArg(1).equals(Type.THROWABLE))
|
||||
return;
|
||||
|
||||
Code code = m.getCode();
|
||||
Instructions ins = code.getInstructions();
|
||||
|
||||
/*
|
||||
Makes it so the old code in this method is logically dead,
|
||||
letting the mapper map it but making it so it's never executed.
|
||||
*/
|
||||
|
||||
Instruction aload0 = new ALoad(ins, 1); // load throwable
|
||||
|
||||
IfNull ifNull = new IfNull(ins, InstructionType.IFNULL);
|
||||
ifNull.setTo(ins.createLabelFor(ins.getInstructions().get(0)));
|
||||
|
||||
Instruction aload1 = new ALoad(ins, 1); // load throwable
|
||||
|
||||
InvokeVirtual printStackTrace = new InvokeVirtual(ins,
|
||||
new net.runelite.asm.pool.Method(
|
||||
new net.runelite.asm.pool.Class("java/lang/Throwable"),
|
||||
"printStackTrace",
|
||||
new Signature("()V")
|
||||
)
|
||||
);
|
||||
|
||||
Instruction ret = new VReturn(ins);
|
||||
|
||||
ins.addInstruction(0, aload0);
|
||||
ins.addInstruction(1, ifNull);
|
||||
ins.addInstruction(2, aload1);
|
||||
ins.addInstruction(3, printStackTrace);
|
||||
ins.addInstruction(4, ret);
|
||||
|
||||
done = true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.deobfuscators.transformers;
|
||||
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
|
||||
import net.runelite.asm.attributes.code.instructions.LDC;
|
||||
import net.runelite.asm.attributes.code.instructions.Pop;
|
||||
import net.runelite.deob.Transformer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class GetPathTransformer implements Transformer
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(GetPathTransformer.class);
|
||||
|
||||
private boolean done = false;
|
||||
|
||||
@Override
|
||||
public void transform(ClassGroup group)
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
transform(m);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Transformed: " + done);
|
||||
}
|
||||
|
||||
private void transform(Method m)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
if (m.getCode() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (Instruction i : m.getCode().getInstructions().getInstructions())
|
||||
{
|
||||
if (i instanceof InvokeInstruction)
|
||||
{
|
||||
InvokeInstruction ii = (InvokeInstruction) i;
|
||||
|
||||
if (ii.getMethod().getName().equals("getPath"))
|
||||
{
|
||||
if (++count == 2)
|
||||
{
|
||||
removeInvoke(i);
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeInvoke(Instruction i)
|
||||
{
|
||||
Instructions ins = i.getInstructions();
|
||||
|
||||
int idx = ins.getInstructions().indexOf(i);
|
||||
|
||||
ins.remove(i);
|
||||
ins.getInstructions().add(idx, new Pop(ins)); // pop File
|
||||
ins.getInstructions().add(idx + 1, new LDC(ins, ""));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.transformers;
|
||||
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instructions.Dup;
|
||||
import net.runelite.asm.attributes.code.instructions.I2L;
|
||||
import net.runelite.asm.attributes.code.instructions.IAdd;
|
||||
import net.runelite.asm.attributes.code.instructions.InvokeSpecial;
|
||||
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
|
||||
import net.runelite.asm.attributes.code.instructions.LDC;
|
||||
import net.runelite.asm.attributes.code.instructions.New;
|
||||
import net.runelite.asm.attributes.code.instructions.Pop;
|
||||
import net.runelite.asm.pool.Class;
|
||||
import net.runelite.asm.signature.Signature;
|
||||
import net.runelite.deob.Transformer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MaxMemoryTransformer implements Transformer
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(MaxMemoryTransformer.class);
|
||||
|
||||
private boolean done = false;
|
||||
|
||||
@Override
|
||||
public void transform(ClassGroup group)
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
transform(m);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Transformed: " + done);
|
||||
}
|
||||
|
||||
private void transform(Method m)
|
||||
{
|
||||
Code code = m.getCode();
|
||||
|
||||
if (code == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Instructions ins = code.getInstructions();
|
||||
|
||||
for (Instruction i : ins.getInstructions())
|
||||
{
|
||||
if (i instanceof InvokeVirtual)
|
||||
{
|
||||
/*
|
||||
invokestatic java/lang/Runtime/getRuntime()Ljava/lang/Runtime;
|
||||
invokevirtual java/lang/Runtime/maxMemory()J
|
||||
ldc2_w 1048576
|
||||
ldiv
|
||||
l2i
|
||||
*/
|
||||
if (((InvokeVirtual) i).getMethod().getName().equals("maxMemory"))
|
||||
{
|
||||
insert(ins, ins.getInstructions().indexOf(i));
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void insert(Instructions ins, int idx)
|
||||
{
|
||||
Class randomClass = new net.runelite.asm.pool.Class("java/util/Random");
|
||||
|
||||
ins.getInstructions().remove(idx);
|
||||
ins.getInstructions().add(idx++, new Pop(ins)); // pop runtime
|
||||
ins.getInstructions().add(idx++, new New(ins, randomClass));
|
||||
ins.getInstructions().add(idx++, new Dup(ins));
|
||||
ins.getInstructions().add(idx++, new InvokeSpecial(ins, new net.runelite.asm.pool.Method(randomClass, "<init>", new Signature("()V")))); // new Random
|
||||
ins.getInstructions().add(idx++, new LDC(ins, 31457280));
|
||||
ins.getInstructions().add(idx++, new InvokeVirtual(ins, new net.runelite.asm.pool.Method(randomClass, "nextInt", new Signature("(I)I")))); // nextInt(31457280)
|
||||
ins.getInstructions().add(idx++, new LDC(ins, 230686720));
|
||||
ins.getInstructions().add(idx++, new IAdd(ins)); // 230686720 + nextInt(31457280)
|
||||
ins.getInstructions().add(idx++, new I2L(ins));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.transformers;
|
||||
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.Type;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instructions.VReturn;
|
||||
import net.runelite.asm.signature.Signature;
|
||||
import net.runelite.deob.Transformer;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
public class OpcodesTransformer implements Transformer
|
||||
{
|
||||
public static final String RUNELITE_OPCODES = "net/runelite/rs/Opcodes";
|
||||
|
||||
@Override
|
||||
public void transform(ClassGroup group)
|
||||
{
|
||||
ClassFile runeliteOpcodes = group.findClass(RUNELITE_OPCODES);
|
||||
if (runeliteOpcodes == null)
|
||||
{
|
||||
runeliteOpcodes = new ClassFile(group);
|
||||
runeliteOpcodes.setName(RUNELITE_OPCODES);
|
||||
runeliteOpcodes.setSuperName(Type.OBJECT.getInternalName());
|
||||
runeliteOpcodes.setAccess(Opcodes.ACC_PUBLIC);
|
||||
group.addClass(runeliteOpcodes);
|
||||
}
|
||||
else
|
||||
{
|
||||
runeliteOpcodes.getFields().clear();
|
||||
}
|
||||
|
||||
Method clinit = runeliteOpcodes.findMethod("<clinit>");
|
||||
if (clinit == null)
|
||||
{
|
||||
clinit = new Method(runeliteOpcodes, "<clinit>", new Signature("()V"));
|
||||
clinit.setStatic();
|
||||
Code code = new Code(clinit);
|
||||
code.setMaxStack(1);
|
||||
clinit.setCode(code);
|
||||
runeliteOpcodes.addMethod(clinit);
|
||||
|
||||
Instructions instructions = code.getInstructions();
|
||||
instructions.addInstruction(new VReturn(instructions));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.transformers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
|
||||
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
|
||||
import net.runelite.asm.signature.Signature;
|
||||
import net.runelite.deob.Transformer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ReflectionTransformer implements Transformer
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(ReflectionTransformer.class);
|
||||
|
||||
@Override
|
||||
public void transform(ClassGroup group)
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method method : cf.getMethods())
|
||||
{
|
||||
transform(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void transform(Method method)
|
||||
{
|
||||
Code code = method.getCode();
|
||||
|
||||
if (code == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Instructions ins = code.getInstructions();
|
||||
|
||||
for (Instruction i : new ArrayList<>(ins.getInstructions()))
|
||||
{
|
||||
transformFindClass(i);
|
||||
transformMethodName(ins, i);
|
||||
transformGetParameterTypes(ins, i);
|
||||
transformGetDeclaredField(ins, i);
|
||||
transformSetInt(ins, i);
|
||||
transformGetInt(ins, i);
|
||||
transformInvokeVirtual(ins, i);
|
||||
}
|
||||
}
|
||||
|
||||
// invokestatic java/lang/Class/forName(Ljava/lang/String;)Ljava/lang/Class;
|
||||
// to
|
||||
// invokestatic net/runelite/rs/Reflection/findClass(Ljava/lang/String;)Ljava/lang/Class;
|
||||
private void transformFindClass(Instruction i)
|
||||
{
|
||||
if (!(i instanceof InvokeStatic))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InvokeStatic is = (InvokeStatic) i;
|
||||
|
||||
if (is.getMethod().getClazz().getName().equals("java/lang/Class")
|
||||
&& is.getMethod().getName().equals("forName"))
|
||||
{
|
||||
is.setMethod(
|
||||
new net.runelite.asm.pool.Method(
|
||||
new net.runelite.asm.pool.Class("net/runelite/rs/Reflection"), "findClass", new Signature("(Ljava/lang/String;)Ljava/lang/Class;")
|
||||
)
|
||||
);
|
||||
|
||||
logger.info("Transformed Class.forName call");
|
||||
}
|
||||
}
|
||||
|
||||
// invokevirtual java/lang/reflect/Method/getName()Ljava/lang/String;
|
||||
// to
|
||||
// invokestatic net/runelite/rs/Reflection/getMethodName(Ljava/lang/reflect/Method;)Ljava/lang/String;
|
||||
private void transformMethodName(Instructions instructions, Instruction i)
|
||||
{
|
||||
if (!(i instanceof InvokeVirtual))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InvokeVirtual iv = (InvokeVirtual) i;
|
||||
|
||||
if (iv.getMethod().getClazz().getName().equals("java/lang/reflect/Method")
|
||||
&& iv.getMethod().getName().equals("getName"))
|
||||
{
|
||||
InvokeStatic is = new InvokeStatic(instructions,
|
||||
new net.runelite.asm.pool.Method(
|
||||
new net.runelite.asm.pool.Class("net/runelite/rs/Reflection"), "getMethodName", new Signature("(Ljava/lang/reflect/Method;)Ljava/lang/String;")
|
||||
)
|
||||
);
|
||||
instructions.replace(iv, is);
|
||||
|
||||
logger.info("Transformed Method.getName call");
|
||||
}
|
||||
}
|
||||
|
||||
// invokevirtual java/lang/reflect/Method/getParameterTypes()[Ljava/lang/Class;
|
||||
// to
|
||||
// invokestatic net/runelite/rs/Reflection/getParameterTypes(Ljava/lang/reflect/Method;)[Ljava/lang/Class;
|
||||
private void transformGetParameterTypes(Instructions instructions, Instruction i)
|
||||
{
|
||||
if (!(i instanceof InvokeVirtual))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InvokeVirtual iv = (InvokeVirtual) i;
|
||||
|
||||
if (iv.getMethod().getClazz().getName().equals("java/lang/reflect/Method")
|
||||
&& iv.getMethod().getName().equals("getParameterTypes"))
|
||||
{
|
||||
InvokeStatic is = new InvokeStatic(instructions,
|
||||
new net.runelite.asm.pool.Method(
|
||||
new net.runelite.asm.pool.Class("net/runelite/rs/Reflection"), "getParameterTypes", new Signature("(Ljava/lang/reflect/Method;)[Ljava/lang/Class;")
|
||||
)
|
||||
);
|
||||
instructions.replace(iv, is);
|
||||
|
||||
logger.info("Transformed Method.getParameterTypes call");
|
||||
}
|
||||
}
|
||||
|
||||
// invokevirtual java/lang/Class/getDeclaredField(Ljava/lang/String;)Ljava/lang/reflect/Field;
|
||||
// to
|
||||
// invokestatic net/runelite/rs/Reflection/findField
|
||||
private void transformGetDeclaredField(Instructions instructions, Instruction i)
|
||||
{
|
||||
if (!(i instanceof InvokeVirtual))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InvokeVirtual iv = (InvokeVirtual) i;
|
||||
|
||||
if (iv.getMethod().getClazz().getName().equals("java/lang/Class")
|
||||
&& iv.getMethod().getName().equals("getDeclaredField"))
|
||||
{
|
||||
InvokeStatic is = new InvokeStatic(instructions,
|
||||
new net.runelite.asm.pool.Method(
|
||||
new net.runelite.asm.pool.Class("net/runelite/rs/Reflection"), "findField", new Signature("(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field;")
|
||||
)
|
||||
);
|
||||
instructions.replace(iv, is);
|
||||
|
||||
logger.info("Transformed Class.getDeclaredField call");
|
||||
}
|
||||
}
|
||||
|
||||
// invokevirtual java/lang/reflect/Field/setInt(Ljava/lang/Object;I)V
|
||||
private void transformSetInt(Instructions instructions, Instruction i)
|
||||
{
|
||||
if (!(i instanceof InvokeVirtual))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InvokeVirtual iv = (InvokeVirtual) i;
|
||||
|
||||
if (iv.getMethod().getClazz().getName().equals("java/lang/reflect/Field")
|
||||
&& iv.getMethod().getName().equals("setInt"))
|
||||
{
|
||||
InvokeStatic is = new InvokeStatic(instructions,
|
||||
new net.runelite.asm.pool.Method(
|
||||
new net.runelite.asm.pool.Class("net/runelite/rs/Reflection"), "setInt", new Signature("(Ljava/lang/reflect/Field;Ljava/lang/Object;I)V")
|
||||
)
|
||||
);
|
||||
instructions.replace(iv, is);
|
||||
|
||||
logger.info("Transformed Field.setInt call");
|
||||
}
|
||||
}
|
||||
|
||||
// invokevirtual java/lang/reflect/Field/getInt(Ljava/lang/Object;)I
|
||||
private void transformGetInt(Instructions instructions, Instruction i)
|
||||
{
|
||||
if (!(i instanceof InvokeVirtual))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InvokeVirtual iv = (InvokeVirtual) i;
|
||||
|
||||
if (iv.getMethod().getClazz().getName().equals("java/lang/reflect/Field")
|
||||
&& iv.getMethod().getName().equals("getInt"))
|
||||
{
|
||||
InvokeStatic is = new InvokeStatic(instructions,
|
||||
new net.runelite.asm.pool.Method(
|
||||
new net.runelite.asm.pool.Class("net/runelite/rs/Reflection"), "getInt", new Signature("(Ljava/lang/reflect/Field;Ljava/lang/Object;)I")
|
||||
)
|
||||
);
|
||||
instructions.replace(iv, is);
|
||||
|
||||
logger.info("Transformed Field.getInt call");
|
||||
}
|
||||
}
|
||||
|
||||
// invokevirtual java/lang/reflect/Method/invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
|
||||
private void transformInvokeVirtual(Instructions instructions, Instruction i)
|
||||
{
|
||||
if (!(i instanceof InvokeVirtual))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InvokeVirtual iv = (InvokeVirtual) i;
|
||||
|
||||
if (iv.getMethod().getClazz().getName().equals("java/lang/reflect/Method")
|
||||
&& iv.getMethod().getName().equals("invoke"))
|
||||
{
|
||||
InvokeStatic is = new InvokeStatic(instructions,
|
||||
new net.runelite.asm.pool.Method(
|
||||
new net.runelite.asm.pool.Class("net/runelite/rs/Reflection"), "invoke", new Signature("(Ljava/lang/reflect/Method;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;")
|
||||
)
|
||||
);
|
||||
instructions.replace(iv, is);
|
||||
|
||||
logger.info("Transformed Method.invoke call");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.transformers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.Type;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import static net.runelite.asm.attributes.code.InstructionType.RETURN;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.Label;
|
||||
import net.runelite.asm.attributes.code.instructions.ALoad;
|
||||
import net.runelite.asm.attributes.code.instructions.GetStatic;
|
||||
import net.runelite.asm.attributes.code.instructions.IfEq;
|
||||
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
import net.runelite.asm.signature.Signature;
|
||||
import net.runelite.deob.Transformer;
|
||||
import net.runelite.deob.c2s.RWOpcodeFinder;
|
||||
import net.runelite.deob.deobfuscators.transformers.buffer.BufferFinder;
|
||||
import net.runelite.deob.deobfuscators.transformers.buffer.BufferMethodInjector;
|
||||
import net.runelite.deob.deobfuscators.transformers.buffer.BufferPayloadFinder;
|
||||
import net.runelite.deob.deobfuscators.transformers.buffer.PacketFlushFinder;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class RuneliteBufferTransformer implements Transformer
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(RuneliteBufferTransformer.class);
|
||||
|
||||
private static final String RUNELITE_PACKET = "RUNELITE_PACKET";
|
||||
private static final String RUNELITE_INIT_PACKET = "runeliteInitPacket";
|
||||
private static final String RUNELITE_FINISH_PACKET = "runeliteFinishPacket";
|
||||
|
||||
@Override
|
||||
public void transform(ClassGroup group)
|
||||
{
|
||||
injectRunelitePacket(group);
|
||||
injectBufferMethods(group);
|
||||
injectLengthHeader(group);
|
||||
injectPacketFinish(group);
|
||||
}
|
||||
|
||||
/**
|
||||
* inject RUNELITE_PACKET field
|
||||
*
|
||||
* @param group
|
||||
*/
|
||||
private void injectRunelitePacket(ClassGroup group)
|
||||
{
|
||||
ClassFile client = findClient(group);
|
||||
assert client != null : "no client class";
|
||||
|
||||
if (client.findField(RUNELITE_PACKET) != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Field field = new Field(client, RUNELITE_PACKET, Type.BOOLEAN);
|
||||
field.setAccessFlags(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC);
|
||||
client.addField(field);
|
||||
}
|
||||
|
||||
private ClassFile findClient(ClassGroup group)
|
||||
{
|
||||
// "client" in vainlla but "Client" in deob..
|
||||
ClassFile cf = group.findClass("client");
|
||||
return cf != null ? cf : group.findClass("Client");
|
||||
}
|
||||
|
||||
/**
|
||||
* inject runelite buffer methods
|
||||
*
|
||||
* @param group
|
||||
*/
|
||||
private void injectBufferMethods(ClassGroup group)
|
||||
{
|
||||
BufferFinder bf = new BufferFinder(group);
|
||||
bf.find();
|
||||
|
||||
BufferPayloadFinder bpf = new BufferPayloadFinder(bf.getBuffer());
|
||||
bpf.find();
|
||||
|
||||
BufferMethodInjector bmi = new BufferMethodInjector(bpf);
|
||||
try
|
||||
{
|
||||
bmi.inject();
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
logger.warn("unable to inject buffer methods", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* inject the length header after the packet opcode
|
||||
*
|
||||
* @param group
|
||||
*/
|
||||
private void injectLengthHeader(ClassGroup group)
|
||||
{
|
||||
RWOpcodeFinder rw = new RWOpcodeFinder(group);
|
||||
rw.find();
|
||||
|
||||
Method writeOpcode = rw.getWriteOpcode();
|
||||
Code code = writeOpcode.getCode();
|
||||
Instructions instructions = code.getInstructions();
|
||||
List<Instruction> ins = instructions.getInstructions();
|
||||
|
||||
Instruction start = ins.get(0);
|
||||
Instruction end = ins.stream().filter(i -> i.getType() == RETURN).findFirst().get();
|
||||
|
||||
Label labelForStart = instructions.createLabelFor(start);
|
||||
Label labelForEnd = instructions.createLabelFor(end);
|
||||
|
||||
final net.runelite.asm.pool.Field runelitePacketField = new net.runelite.asm.pool.Field(
|
||||
new net.runelite.asm.pool.Class(findClient(group).getName()),
|
||||
RUNELITE_PACKET,
|
||||
Type.BOOLEAN
|
||||
);
|
||||
|
||||
int idx = ins.indexOf(labelForStart);
|
||||
|
||||
instructions.addInstruction(idx++, new GetStatic(instructions, runelitePacketField));
|
||||
instructions.addInstruction(idx++, new IfEq(instructions, labelForStart));
|
||||
|
||||
net.runelite.asm.pool.Method method = new net.runelite.asm.pool.Method(
|
||||
new net.runelite.asm.pool.Class(writeOpcode.getClassFile().getName()),
|
||||
RUNELITE_FINISH_PACKET,
|
||||
new Signature("()V")
|
||||
);
|
||||
instructions.addInstruction(idx++, new ALoad(instructions, 0));
|
||||
instructions.addInstruction(idx++, new InvokeVirtual(instructions, method));
|
||||
|
||||
idx = ins.indexOf(labelForEnd);
|
||||
|
||||
instructions.addInstruction(idx++, new GetStatic(instructions, runelitePacketField));
|
||||
instructions.addInstruction(idx++, new IfEq(instructions, labelForEnd));
|
||||
|
||||
method = new net.runelite.asm.pool.Method(
|
||||
new net.runelite.asm.pool.Class(writeOpcode.getClassFile().getName()),
|
||||
RUNELITE_INIT_PACKET,
|
||||
new Signature("()V")
|
||||
);
|
||||
instructions.addInstruction(idx++, new ALoad(instructions, 0));
|
||||
instructions.addInstruction(idx++, new InvokeVirtual(instructions, method));
|
||||
|
||||
logger.info("Injected finish/init packet calls into {}", writeOpcode);
|
||||
}
|
||||
|
||||
private void injectPacketFinish(ClassGroup group)
|
||||
{
|
||||
PacketFlushFinder pff = new PacketFlushFinder(group);
|
||||
pff.find();
|
||||
|
||||
for (InstructionContext queueForWriteCtx : pff.getQueueForWrite())
|
||||
{
|
||||
Instruction before = queueForWriteCtx.getPops().get(3) // socket
|
||||
.getPushed().getInstruction();
|
||||
GetStatic getBuffer;
|
||||
|
||||
try
|
||||
{
|
||||
getBuffer = (GetStatic) queueForWriteCtx.getPops().get(2) // buffer
|
||||
.getPushed().getPops().get(0) // getstatic
|
||||
.getPushed().getInstruction();
|
||||
}
|
||||
catch (ClassCastException ex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Instructions instructions = before.getInstructions();
|
||||
|
||||
int idx = instructions.getInstructions().indexOf(before);
|
||||
assert idx != -1;
|
||||
|
||||
instructions.addInstruction(idx++, getBuffer.clone());
|
||||
|
||||
net.runelite.asm.pool.Method method = new net.runelite.asm.pool.Method(
|
||||
new net.runelite.asm.pool.Class(getBuffer.getField().getType().getInternalName()),
|
||||
RUNELITE_FINISH_PACKET,
|
||||
new Signature("()V")
|
||||
);
|
||||
instructions.addInstruction(idx++, new InvokeVirtual(instructions, method));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.transformers.buffer;
|
||||
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Class which identifies Buffer and PacketBuffer
|
||||
*
|
||||
* @author Adam
|
||||
*/
|
||||
public class BufferFinder
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(BufferFinder.class);
|
||||
|
||||
private final ClassGroup group;
|
||||
|
||||
private ClassFile buffer, packetBuffer;
|
||||
|
||||
public BufferFinder(ClassGroup group)
|
||||
{
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public ClassFile getBuffer()
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public ClassFile getPacketBuffer()
|
||||
{
|
||||
return packetBuffer;
|
||||
}
|
||||
|
||||
public void find()
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
Code code = m.getCode();
|
||||
|
||||
if (findModPow(code))
|
||||
{
|
||||
buffer = cf;
|
||||
|
||||
// packetBuffer extends this
|
||||
packetBuffer = group.getClasses().stream()
|
||||
.filter(cl -> cl.getParent() == cf)
|
||||
.findAny()
|
||||
.get();
|
||||
|
||||
logger.info("Identified buffer {}, packetBuffer {}", buffer, packetBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find encryptRsa in buffer
|
||||
private boolean findModPow(Code code)
|
||||
{
|
||||
if (code == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Instructions instructions = code.getInstructions();
|
||||
|
||||
for (Instruction i : instructions.getInstructions())
|
||||
{
|
||||
if (!(i instanceof InvokeVirtual))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
InvokeVirtual iv = (InvokeVirtual) i;
|
||||
net.runelite.asm.pool.Method method = iv.getMethod();
|
||||
if (method.getName().equals("modPow"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.transformers.buffer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Label;
|
||||
import net.runelite.asm.attributes.code.instruction.types.FieldInstruction;
|
||||
import net.runelite.asm.visitors.ClassFileVisitor;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
|
||||
public class BufferMethodInjector
|
||||
{
|
||||
private final BufferPayloadFinder bp;
|
||||
|
||||
public BufferMethodInjector(BufferPayloadFinder bp)
|
||||
{
|
||||
this.bp = bp;
|
||||
}
|
||||
|
||||
public void inject() throws IOException
|
||||
{
|
||||
Field buffer = bp.getBuffer();
|
||||
Field offset = bp.getOffset();
|
||||
|
||||
assert buffer.getClassFile() == offset.getClassFile();
|
||||
|
||||
InputStream in = getClass().getResourceAsStream("RuneliteBuffer.class");
|
||||
assert in != null : "no RuneliteBuffer";
|
||||
ClassFile runeliteBuffer = loadClass(in);
|
||||
|
||||
ClassFile bufferClass = buffer.getClassFile();
|
||||
|
||||
for (Field f : runeliteBuffer.getFields())
|
||||
{
|
||||
if (!f.getName().startsWith("runelite"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
inject(bufferClass, f);
|
||||
}
|
||||
|
||||
for (Method m : runeliteBuffer.getMethods())
|
||||
{
|
||||
if (!m.getName().startsWith("runelite"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
inject(bufferClass, m);
|
||||
}
|
||||
}
|
||||
|
||||
private void inject(ClassFile bufferClass, Method method)
|
||||
{
|
||||
assert method.getExceptions().getExceptions().isEmpty();
|
||||
|
||||
Method newMethod = new Method(bufferClass, method.getName(), method.getDescriptor());
|
||||
Code code = new Code(newMethod);
|
||||
newMethod.setCode(code);
|
||||
|
||||
method.getCode().getInstructions().getInstructions().stream()
|
||||
.forEach(i ->
|
||||
{
|
||||
if (!(i instanceof Label))
|
||||
{
|
||||
i = i.clone();
|
||||
}
|
||||
|
||||
if (i instanceof FieldInstruction)
|
||||
{
|
||||
FieldInstruction fi = (FieldInstruction) i;
|
||||
if (fi.getField().getName().equals("offset"))
|
||||
{
|
||||
fi.setField(bp.getOffset().getPoolField());
|
||||
}
|
||||
else if (fi.getField().getName().equals("payload"))
|
||||
{
|
||||
fi.setField(bp.getBuffer().getPoolField());
|
||||
}
|
||||
else if (fi.getField().getName().equals("runeliteLengthOffset"))
|
||||
{
|
||||
fi.setField(bufferClass.findField("runeliteLengthOffset").getPoolField());
|
||||
}
|
||||
}
|
||||
|
||||
i.setInstructions(code.getInstructions());
|
||||
code.getInstructions().addInstruction(i);
|
||||
});
|
||||
|
||||
code.getExceptions().getExceptions().addAll(method.getCode().getExceptions().getExceptions());
|
||||
|
||||
bufferClass.addMethod(newMethod);
|
||||
}
|
||||
|
||||
private void inject(ClassFile bufferClass, Field field)
|
||||
{
|
||||
Field newField = new Field(bufferClass, field.getName(), field.getType());
|
||||
newField.setAccessFlags(field.getAccessFlags());
|
||||
newField.setValue(field.getValue());
|
||||
bufferClass.addField(newField);
|
||||
}
|
||||
|
||||
static ClassFile loadClass(InputStream in) throws IOException
|
||||
{
|
||||
ClassReader reader = new ClassReader(in);
|
||||
ClassFileVisitor cv = new ClassFileVisitor();
|
||||
|
||||
reader.accept(cv, ClassReader.SKIP_FRAMES);
|
||||
|
||||
return cv.getClassFile();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.transformers.buffer;
|
||||
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Type;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class BufferPayloadFinder
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(BufferPayloadFinder.class);
|
||||
|
||||
private final ClassFile bufferClass;
|
||||
|
||||
private Field offset;
|
||||
private Field buffer;
|
||||
|
||||
public BufferPayloadFinder(ClassFile bufferClass)
|
||||
{
|
||||
this.bufferClass = bufferClass;
|
||||
}
|
||||
|
||||
public void find()
|
||||
{
|
||||
for (Field field : bufferClass.getFields())
|
||||
{
|
||||
if (field.getType().equals(Type.INT))
|
||||
{
|
||||
offset = field;
|
||||
}
|
||||
else if (field.getType().equals(new Type("[B")))
|
||||
{
|
||||
buffer = field;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Found offset {} buffer {}", offset, buffer);
|
||||
}
|
||||
|
||||
public Field getOffset()
|
||||
{
|
||||
return offset;
|
||||
}
|
||||
|
||||
public Field getBuffer()
|
||||
{
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.transformers.buffer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Code;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import static net.runelite.asm.attributes.code.InstructionType.GETFIELD;
|
||||
import static net.runelite.asm.attributes.code.InstructionType.INVOKEVIRTUAL;
|
||||
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
|
||||
import net.runelite.asm.execution.Execution;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
import net.runelite.asm.signature.Signature;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class PacketFlushFinder
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(PacketFlushFinder.class);
|
||||
|
||||
private final ClassGroup group;
|
||||
|
||||
private final List<InstructionContext> queueForWrite = new ArrayList<>();
|
||||
|
||||
public PacketFlushFinder(ClassGroup group)
|
||||
{
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public List<InstructionContext> getQueueForWrite()
|
||||
{
|
||||
return queueForWrite;
|
||||
}
|
||||
|
||||
public void find()
|
||||
{
|
||||
ClassFile client = group.findClass("Client");
|
||||
if (client == null)
|
||||
{
|
||||
client = group.findClass("client");
|
||||
}
|
||||
|
||||
for (Method method : client.getMethods())
|
||||
{
|
||||
find(method);
|
||||
}
|
||||
}
|
||||
|
||||
private void find(Method method)
|
||||
{
|
||||
Code code = method.getCode();
|
||||
Set<Instruction> checked = new HashSet<>();
|
||||
|
||||
Execution e = new Execution(group);
|
||||
e.addMethod(method);
|
||||
e.noInvoke = true;
|
||||
e.noExceptions = true;
|
||||
e.addExecutionVisitor(ic ->
|
||||
{
|
||||
Instruction i = ic.getInstruction();
|
||||
|
||||
if (checked.contains(i))
|
||||
{
|
||||
return;
|
||||
}
|
||||
checked.add(i);
|
||||
|
||||
if (i.getType() != INVOKEVIRTUAL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InvokeVirtual iv = (InvokeVirtual) i;
|
||||
// queueForWrite
|
||||
if (!iv.getMethod().getType().equals(new Signature("([BII)V")))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InstructionContext lengthCtx = ic.getPops().get(0).getPushed();
|
||||
if (lengthCtx.getInstruction().getType() != GETFIELD)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
queueForWrite.add(ic);
|
||||
});
|
||||
e.run();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.deobfuscators.transformers.buffer;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/**
|
||||
* Methods injected into runescape-client's Buffer
|
||||
*
|
||||
* @author Adam
|
||||
*/
|
||||
public class RuneliteBuffer
|
||||
{
|
||||
// so that it compiles
|
||||
private int offset;
|
||||
private byte[] payload;
|
||||
private int runeliteLengthOffset;
|
||||
|
||||
public int getOffset()
|
||||
{
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void setOffset(int offset)
|
||||
{
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public byte[] getPayload()
|
||||
{
|
||||
return payload;
|
||||
}
|
||||
|
||||
public void setPayload(byte[] payload)
|
||||
{
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public int getRuneliteLengthOffset()
|
||||
{
|
||||
return runeliteLengthOffset;
|
||||
}
|
||||
|
||||
public void setRuneliteLengthOffset(int runeliteLengthOffset)
|
||||
{
|
||||
this.runeliteLengthOffset = runeliteLengthOffset;
|
||||
}
|
||||
|
||||
public byte runeliteReadByte()
|
||||
{
|
||||
offset += 1;
|
||||
return payload[offset - 1];
|
||||
}
|
||||
|
||||
public short runeliteReadShort()
|
||||
{
|
||||
offset += 2;
|
||||
return (short) (((payload[offset - 2] & 0xFF) << 8)
|
||||
| (payload[offset - 1] & 0xFF));
|
||||
}
|
||||
|
||||
public int runeliteReadInt()
|
||||
{
|
||||
offset += 4;
|
||||
return ((payload[offset - 4] & 0xFF) << 24)
|
||||
| ((payload[offset - 3] & 0xFF) << 16)
|
||||
| ((payload[offset - 2] & 0xFF) << 8)
|
||||
| (payload[offset - 1] & 0xFF);
|
||||
}
|
||||
|
||||
public long runeliteReadLong()
|
||||
{
|
||||
offset += 8;
|
||||
return ((payload[offset - 8] & 0xFFL) << 56)
|
||||
| ((payload[offset - 7] & 0xFFL) << 48)
|
||||
| ((payload[offset - 6] & 0xFFL) << 40)
|
||||
| ((payload[offset - 5] & 0xFFL) << 32)
|
||||
| ((payload[offset - 4] & 0xFFL) << 24)
|
||||
| ((payload[offset - 3] & 0xFFL) << 16)
|
||||
| ((payload[offset - 2] & 0xFFL) << 8)
|
||||
| (payload[offset - 1] & 0xFFL);
|
||||
}
|
||||
|
||||
public String runeliteReadString()
|
||||
{
|
||||
int length = runeliteReadShort();
|
||||
if (length < 0)
|
||||
{
|
||||
throw new RuntimeException("length < 0");
|
||||
}
|
||||
|
||||
offset += length;
|
||||
try
|
||||
{
|
||||
return new String(payload, offset - length, length, "UTF-8");
|
||||
}
|
||||
catch (UnsupportedEncodingException ex)
|
||||
{
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void runeliteWriteByte(byte b)
|
||||
{
|
||||
payload[offset++] = b;
|
||||
}
|
||||
|
||||
public void runeliteWriteShort(short s)
|
||||
{
|
||||
payload[offset++] = (byte) (s >> 8);
|
||||
payload[offset++] = (byte) s;
|
||||
}
|
||||
|
||||
public void runeliteWriteInt(int i)
|
||||
{
|
||||
payload[offset++] = (byte) (i >> 24);
|
||||
payload[offset++] = (byte) (i >> 16);
|
||||
payload[offset++] = (byte) (i >> 8);
|
||||
payload[offset++] = (byte) i;
|
||||
}
|
||||
|
||||
public void runeliteWriteLong(long l)
|
||||
{
|
||||
payload[offset++] = (byte) (l >> 56);
|
||||
payload[offset++] = (byte) (l >> 48);
|
||||
payload[offset++] = (byte) (l >> 40);
|
||||
payload[offset++] = (byte) (l >> 32);
|
||||
payload[offset++] = (byte) (l >> 24);
|
||||
payload[offset++] = (byte) (l >> 16);
|
||||
payload[offset++] = (byte) (l >> 8);
|
||||
payload[offset++] = (byte) l;
|
||||
}
|
||||
|
||||
public void runeliteWriteString(String s)
|
||||
{
|
||||
byte[] bytes;
|
||||
|
||||
try
|
||||
{
|
||||
bytes = s.getBytes("UTF-8");
|
||||
}
|
||||
catch (UnsupportedEncodingException ex)
|
||||
{
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
runeliteWriteShort((short) bytes.length);
|
||||
for (byte b : bytes)
|
||||
{
|
||||
payload[offset++] = b;
|
||||
}
|
||||
}
|
||||
|
||||
public void runeliteInitPacket()
|
||||
{
|
||||
runeliteLengthOffset = offset;
|
||||
runeliteWriteShort((short) 0); // flush() relies on default length of 0
|
||||
}
|
||||
|
||||
public void runeliteFinishPacket()
|
||||
{
|
||||
if (runeliteLengthOffset > 0)
|
||||
{
|
||||
int length = offset - runeliteLengthOffset - 2;
|
||||
if (length < 0)
|
||||
{
|
||||
// on flush() it sets offset = 0
|
||||
// but runeliteLengthOffset remains >0
|
||||
return;
|
||||
}
|
||||
payload[runeliteLengthOffset++] = (byte) (length >> 8);
|
||||
payload[runeliteLengthOffset++] = (byte) length;
|
||||
runeliteLengthOffset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.s2c;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.InstructionType;
|
||||
import net.runelite.asm.attributes.code.Instructions;
|
||||
import net.runelite.asm.attributes.code.Label;
|
||||
import net.runelite.asm.attributes.code.instruction.types.JumpingInstruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
|
||||
import net.runelite.asm.attributes.code.instructions.GetStatic;
|
||||
import net.runelite.asm.attributes.code.instructions.If;
|
||||
import net.runelite.asm.attributes.code.instructions.VReturn;
|
||||
import net.runelite.asm.execution.Execution;
|
||||
import net.runelite.asm.execution.Frame;
|
||||
import net.runelite.asm.execution.InstructionContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class HandlerFinder
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(HandlerFinder.class);
|
||||
|
||||
private final ClassGroup group;
|
||||
private final Field packetType;
|
||||
private final Execution execution;
|
||||
|
||||
public HandlerFinder(ClassGroup group, Field packetType)
|
||||
{
|
||||
this.group = group;
|
||||
this.packetType = packetType;
|
||||
this.execution = new Execution(group);
|
||||
}
|
||||
|
||||
public ClassGroup getGroup()
|
||||
{
|
||||
return group;
|
||||
}
|
||||
|
||||
public Execution getExecution()
|
||||
{
|
||||
return execution;
|
||||
}
|
||||
|
||||
public PacketHandlers findHandlers()
|
||||
{
|
||||
// Some of the packet handlers are if (type == 1 || type == 2) func()
|
||||
// and func() checks the type... so search all functions for packet handlers
|
||||
|
||||
List<PacketHandler> handlers = new ArrayList<>();
|
||||
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
for (Method method : cf.getMethods())
|
||||
{
|
||||
if (method.getCode() == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
List<PacketHandler> h = findHandlers(method, packetType);
|
||||
handlers.addAll(h);
|
||||
}
|
||||
}
|
||||
|
||||
PacketHandlers packetHandlers = new PacketHandlers(group, packetType, handlers);
|
||||
removeDuplicates(packetHandlers);
|
||||
prepareFrame(execution, packetHandlers);
|
||||
return packetHandlers;
|
||||
}
|
||||
|
||||
private List<PacketHandler> findHandlers(Method process, Field packetOpcode)
|
||||
{
|
||||
List<PacketHandler> handlers = new ArrayList<>();
|
||||
|
||||
Instructions ins = process.getCode().getInstructions();
|
||||
|
||||
for (int j = 0; j < ins.getInstructions().size(); ++j)
|
||||
{
|
||||
Instruction i = ins.getInstructions().get(j);
|
||||
|
||||
if (i.getType() != InstructionType.GETSTATIC)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GetStatic gs = (GetStatic) i;
|
||||
if (gs.getMyField() != packetOpcode)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Instruction push = ins.getInstructions().get(j + 1);
|
||||
if (!(push instanceof PushConstantInstruction))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
PushConstantInstruction pci = (PushConstantInstruction) push;
|
||||
if (!(pci.getConstant() instanceof Number))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int opcode = ((Number) pci.getConstant()).intValue();
|
||||
|
||||
if (opcode == -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Instruction jump = ins.getInstructions().get(j + 2);
|
||||
if (jump.getType() != InstructionType.IF_ICMPEQ && jump.getType() != InstructionType.IF_ICMPNE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Instruction start, end;
|
||||
if (jump.getType() == InstructionType.IF_ICMPEQ)
|
||||
{
|
||||
// this seems to not ever happen
|
||||
start = ((If) jump).getJumps().get(0);
|
||||
//end = ins.getInstructions().get(j + 3);
|
||||
end = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
start = ins.getInstructions().get(j + 3);
|
||||
end = ((If) jump).getJumps().get(0);
|
||||
}
|
||||
|
||||
PacketHandler handler = new PacketHandler(process, jump, start, push, opcode);
|
||||
handlers.add(handler);
|
||||
|
||||
if (end != null)
|
||||
{
|
||||
// Anything else which jumps to here instead needs to return.
|
||||
insertReturn(ins, jump, end);
|
||||
}
|
||||
|
||||
logger.info("Found packet handler {} opcode {}", handler, handler.getOpcode());
|
||||
}
|
||||
|
||||
return handlers;
|
||||
}
|
||||
|
||||
private void removeDuplicates(PacketHandlers handlers)
|
||||
{
|
||||
// remove handlers which have multiple opcodes
|
||||
Multimap<Instruction, PacketHandler> i2h = HashMultimap.create();
|
||||
|
||||
for (PacketHandler handler : handlers.getHandlers())
|
||||
{
|
||||
i2h.put(handler.getStart(), handler);
|
||||
}
|
||||
|
||||
for (Instruction i : i2h.keySet())
|
||||
{
|
||||
int sz = i2h.get(i).size();
|
||||
|
||||
if (sz == 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// this is part of if (opcode == 1 || opcode == 2 || ...) func();
|
||||
for (PacketHandler ph : i2h.get(i))
|
||||
{
|
||||
handlers.getHandlers().remove(ph);
|
||||
logger.debug("Removed duplicate handler {}", ph);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareFrame(Execution e, PacketHandlers handlers)
|
||||
{
|
||||
List<Method> methods = handlers.getHandlers().stream()
|
||||
.map(handler -> handler.getMethod())
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Method method : methods)
|
||||
{
|
||||
List<PacketHandler> phandlers = handlers.getHandlers().stream()
|
||||
.filter(handler -> handler.getMethod() == method)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
prepareFrame(e, method, phandlers);
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareFrame(Execution e, Method method, List<PacketHandler> handlers)
|
||||
{
|
||||
e.step = true;
|
||||
|
||||
Frame f = new Frame(e, method);
|
||||
f.initialize();
|
||||
|
||||
e.addFrame(f);
|
||||
|
||||
while (e.frames.isEmpty() == false)
|
||||
{
|
||||
f = e.frames.get(0);
|
||||
|
||||
if (!f.isExecuting())
|
||||
{
|
||||
e.frames.remove(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
assert f.isExecuting();
|
||||
f.execute();
|
||||
|
||||
e.paused = false;
|
||||
|
||||
InstructionContext ctx = f.getInstructions().get(f.getInstructions().size() - 1);
|
||||
|
||||
for (PacketHandler handler : handlers)
|
||||
{
|
||||
if (handler.getAfterRead() == ctx.getInstruction())
|
||||
{
|
||||
// frame is stopped at jump prior to handler
|
||||
handler.frame = f.dup();
|
||||
e.frames.remove(handler.frame);
|
||||
logger.info("Found frame for {}: {}", handler, handler.frame);
|
||||
}
|
||||
if (handler.getJump() == ctx.getInstruction())
|
||||
{
|
||||
handler.jumpFrame = f.dup();
|
||||
e.frames.remove(handler.jumpFrame);
|
||||
assert handler.jumpFrame.isExecuting();
|
||||
logger.info("Found jump frame for {}: {}", handler, handler.jumpFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void insertReturn(Instructions ins, Instruction start, Instruction end)
|
||||
{
|
||||
assert end instanceof Label;
|
||||
int idx = ins.getInstructions().indexOf(end);
|
||||
assert idx != -1;
|
||||
|
||||
Instruction before = ins.getInstructions().get(idx - 1);
|
||||
if (before.getType() == InstructionType.RETURN) //XXX check isTerminal?
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// insert return before end
|
||||
logger.info("Inserting return before {}", end);
|
||||
|
||||
Instruction ret = new VReturn(ins);
|
||||
ins.addInstruction(idx, ret);
|
||||
|
||||
Label label = ins.createLabelFor(ret);
|
||||
|
||||
// Change jumps which go to the next handler to instead go to return
|
||||
for (Instruction i : ins.getInstructions())
|
||||
{
|
||||
if (i instanceof JumpingInstruction)
|
||||
{
|
||||
JumpingInstruction j = (JumpingInstruction) i;
|
||||
|
||||
if (i == start)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (j.getJumps().size() == 1 && j.getJumps().get(0) == end)
|
||||
{
|
||||
j.setJumps(Collections.singletonList(label));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.s2c;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.code.Instruction;
|
||||
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
|
||||
import net.runelite.asm.execution.Frame;
|
||||
import net.runelite.deob.deobfuscators.packethandler.PacketRead;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class PacketHandler implements Cloneable
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(PacketHandler.class);
|
||||
|
||||
private final Method method;
|
||||
private final Instruction jump; // jump for the packet handler
|
||||
private final Instruction start; // first instruction in the handler
|
||||
private final Instruction push; // instruction which pushes opcode constant
|
||||
private final int opcode;
|
||||
|
||||
public Frame frame; // after read
|
||||
public Frame jumpFrame;
|
||||
|
||||
public List<Instruction> mappable = new ArrayList<>(); // mappable instructions
|
||||
public List<PacketRead> reads = new ArrayList<>(); // instructions which read packet data
|
||||
public List<PacketRead> sortedReads;
|
||||
public Map<Integer, Integer> lvtOrder = new HashMap<>(); // access order of lvt, lvt index -> order
|
||||
|
||||
public Set<Field> fieldRead = new HashSet<>();
|
||||
public Set<Field> fieldWrite = new HashSet<>();
|
||||
public Set<Method> methodInvokes = new HashSet<>();
|
||||
public List<Object> constants = new ArrayList<>();
|
||||
|
||||
public PacketHandler(Method method, Instruction jump, Instruction start, Instruction push, int opcode)
|
||||
{
|
||||
this.method = method;
|
||||
this.jump = jump;
|
||||
this.start = start;
|
||||
this.push = push;
|
||||
this.opcode = opcode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "PacketHandler{" + "start=" + start + ", opcode=" + opcode + '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketHandler clone()
|
||||
{
|
||||
try
|
||||
{
|
||||
return (PacketHandler) super.clone();
|
||||
}
|
||||
catch (CloneNotSupportedException ex)
|
||||
{
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public Method getMethod()
|
||||
{
|
||||
return method;
|
||||
}
|
||||
|
||||
public Instruction getJump()
|
||||
{
|
||||
return jump;
|
||||
}
|
||||
|
||||
public Instruction getStart()
|
||||
{
|
||||
return start;
|
||||
}
|
||||
|
||||
public Instruction getPush()
|
||||
{
|
||||
return push;
|
||||
}
|
||||
|
||||
public Instruction getAfterRead()
|
||||
{
|
||||
if (reads.isEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
PacketRead last = reads.get(reads.size() - 1);
|
||||
if (last.getStore() == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Instruction> ins = method.getCode().getInstructions().getInstructions();
|
||||
int idx = ins.indexOf(last.getStore());
|
||||
if (idx == -1)
|
||||
{
|
||||
return null; // can be a read in not this function
|
||||
}
|
||||
|
||||
return ins.get(idx + 1);
|
||||
}
|
||||
|
||||
public int getOpcode()
|
||||
{
|
||||
return opcode;
|
||||
}
|
||||
|
||||
public boolean hasPacketRead(Instruction i)
|
||||
{
|
||||
for (PacketRead pr : reads)
|
||||
{
|
||||
if (pr.getInvoke() == i)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void findReorderableReads()
|
||||
{
|
||||
for (PacketRead pr : reads)
|
||||
{
|
||||
//InstructionContext invokeCtx = pr.getInvokeCtx();
|
||||
List<Instruction> instructions = pr.getInvoke().getInstructions().getInstructions();
|
||||
|
||||
// look for an lvt store immediately after
|
||||
int invokeIdx = instructions.indexOf(pr.getInvoke());
|
||||
assert invokeIdx != -1;
|
||||
|
||||
Instruction next = instructions.get(invokeIdx + 1);
|
||||
if (next instanceof LVTInstruction)
|
||||
{
|
||||
LVTInstruction lvt = (LVTInstruction) next;
|
||||
if (lvt.store())
|
||||
{
|
||||
logger.info("Found lvt store {} for {}", next, pr.getInvoke());
|
||||
pr.setStore(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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 net.runelite.deob.s2c;
|
||||
|
||||
import java.util.List;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
|
||||
public class PacketHandlers
|
||||
{
|
||||
private final ClassGroup group;
|
||||
private final Field packetType;
|
||||
private final List<PacketHandler> handlers;
|
||||
|
||||
public PacketHandlers(ClassGroup group, Field packetType, List<PacketHandler> handlers)
|
||||
{
|
||||
this.group = group;
|
||||
this.packetType = packetType;
|
||||
this.handlers = handlers;
|
||||
}
|
||||
|
||||
public PacketHandler find(int opcode)
|
||||
{
|
||||
for (PacketHandler handler : handlers)
|
||||
{
|
||||
if (handler.getOpcode() == opcode)
|
||||
{
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ClassGroup getGroup()
|
||||
{
|
||||
return group;
|
||||
}
|
||||
|
||||
public Field getPacketType()
|
||||
{
|
||||
return packetType;
|
||||
}
|
||||
|
||||
public List<PacketHandler> getHandlers()
|
||||
{
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.updater;
|
||||
|
||||
import java.util.Arrays;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.Type;
|
||||
import net.runelite.asm.attributes.Annotations;
|
||||
import net.runelite.asm.attributes.annotation.Annotation;
|
||||
import net.runelite.asm.attributes.annotation.Element;
|
||||
|
||||
public class AnnotationCopier
|
||||
{
|
||||
private final ClassGroup group1, group2;
|
||||
private final Type[] types;
|
||||
|
||||
public AnnotationCopier(ClassGroup group1, ClassGroup group2, Type... types)
|
||||
{
|
||||
this.group1 = group1;
|
||||
this.group2 = group2;
|
||||
this.types = types;
|
||||
}
|
||||
|
||||
public void copy()
|
||||
{
|
||||
for (ClassFile cf1 : group1.getClasses())
|
||||
{
|
||||
ClassFile cf2 = group2.findClass(cf1.getName());
|
||||
|
||||
assert cf2 != null;
|
||||
|
||||
copy(cf1.getAnnotations(), cf2.getAnnotations());
|
||||
|
||||
for (Field f : cf1.getFields())
|
||||
{
|
||||
Field f2 = cf2.findField(f.getName(), f.getType());
|
||||
|
||||
assert f2 != null || f.getAnnotations() == null;
|
||||
|
||||
if (f2 == null)
|
||||
continue;
|
||||
|
||||
copy(f.getAnnotations(), f2.getAnnotations());
|
||||
}
|
||||
|
||||
for (Method m : cf1.getMethods())
|
||||
{
|
||||
Method m2 = cf2.findMethod(m.getName(), m.getDescriptor());
|
||||
|
||||
assert m2 != null || m.getAnnotations() == null;
|
||||
|
||||
if (m2 == null)
|
||||
continue;
|
||||
|
||||
copy(m.getAnnotations(), m2.getAnnotations());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void copy(Annotations an, Annotations an2)
|
||||
{
|
||||
for (Annotation a : an2.getAnnotations())
|
||||
{
|
||||
if (isType(a.getType()))
|
||||
{
|
||||
an2.removeAnnotation(a);
|
||||
}
|
||||
}
|
||||
|
||||
for (Annotation a : an.getAnnotations())
|
||||
{
|
||||
if (!isType(a.getType()))
|
||||
continue;
|
||||
|
||||
Annotation a2 = new Annotation(an2);
|
||||
a2.setType(a.getType());
|
||||
|
||||
for (Element element : a.getElements())
|
||||
{
|
||||
Element element2 = new Element(a2);
|
||||
element2.setName(element.getName());
|
||||
element2.setValue(element.getValue());
|
||||
a2.addElement(element2);
|
||||
}
|
||||
|
||||
an2.addAnnotation(a2);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isType(Type type)
|
||||
{
|
||||
return Arrays.asList(types).contains(type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.updater;
|
||||
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Field;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.asm.attributes.Annotations;
|
||||
import net.runelite.asm.attributes.annotation.Annotation;
|
||||
import net.runelite.deob.DeobAnnotations;
|
||||
import net.runelite.deob.deobfuscators.Renamer;
|
||||
import net.runelite.deob.util.NameMappings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AnnotationRenamer
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(AnnotationRenamer.class);
|
||||
|
||||
private ClassGroup group;
|
||||
|
||||
public AnnotationRenamer(ClassGroup group)
|
||||
{
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
NameMappings mappings = buildMappings();
|
||||
|
||||
Renamer renamer = new Renamer(mappings);
|
||||
renamer.run(group);
|
||||
}
|
||||
|
||||
private NameMappings buildMappings()
|
||||
{
|
||||
NameMappings mappings = new NameMappings();
|
||||
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
String name = getImplements(cf.getAnnotations());
|
||||
if (name != null)
|
||||
mappings.map(cf.getPoolClass(), name);
|
||||
|
||||
for (Field f : cf.getFields())
|
||||
{
|
||||
name = DeobAnnotations.getExportedName(f.getAnnotations());
|
||||
if (name != null)
|
||||
mappings.map(f.getPoolField(), name);
|
||||
}
|
||||
|
||||
for (Method m : cf.getMethods())
|
||||
{
|
||||
name = DeobAnnotations.getExportedName(m.getAnnotations());
|
||||
if (name != null)
|
||||
mappings.map(m.getPoolMethod(), name);
|
||||
}
|
||||
}
|
||||
|
||||
return mappings;
|
||||
}
|
||||
|
||||
private String getImplements(Annotations annotations)
|
||||
{
|
||||
Annotation an = annotations.find(DeobAnnotations.IMPLEMENTS);
|
||||
return an != null ? an.getElement().getString() : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Morgan Lewis <http://github.com/MESLewis>
|
||||
* 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 net.runelite.deob.updater;
|
||||
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.Method;
|
||||
import net.runelite.deob.deobfuscators.mapping.ParallelExecutorMapping;
|
||||
|
||||
public class ParameterRenamer
|
||||
{
|
||||
private final ClassGroup source;
|
||||
private final ClassGroup dest;
|
||||
private final ParallelExecutorMapping mapping;
|
||||
|
||||
public ParameterRenamer(ClassGroup source, ClassGroup dest, ParallelExecutorMapping mapping)
|
||||
{
|
||||
this.source = source;
|
||||
this.dest = dest;
|
||||
this.mapping = mapping;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
for (ClassFile sourceCF : source.getClasses())
|
||||
{
|
||||
for (Method sourceM : sourceCF.getMethods())
|
||||
{
|
||||
Method destM = (Method) mapping.get(sourceM);
|
||||
if (destM != null)
|
||||
{
|
||||
destM.setParameters(sourceM.getParameters());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.updater;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.deob.deobfuscators.mapping.AnnotationIntegrityChecker;
|
||||
import net.runelite.deob.deobfuscators.mapping.AnnotationMapper;
|
||||
import net.runelite.deob.deobfuscators.mapping.Mapper;
|
||||
import net.runelite.deob.deobfuscators.mapping.ParallelExecutorMapping;
|
||||
import net.runelite.deob.util.JarUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class UpdateMappings
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(UpdateMappings.class);
|
||||
|
||||
private final ClassGroup group1, group2;
|
||||
|
||||
public UpdateMappings(ClassGroup group1, ClassGroup group2)
|
||||
{
|
||||
this.group1 = group1;
|
||||
this.group2 = group2;
|
||||
}
|
||||
|
||||
public void update()
|
||||
{
|
||||
Mapper mapper = new Mapper(group1, group2);
|
||||
mapper.run();
|
||||
ParallelExecutorMapping mapping = mapper.getMapping();
|
||||
|
||||
AnnotationMapper amapper = new AnnotationMapper(group1, group2, mapping);
|
||||
amapper.run();
|
||||
|
||||
AnnotationIntegrityChecker aic = new AnnotationIntegrityChecker(group1, group2, mapping);
|
||||
aic.run();
|
||||
|
||||
int errors = aic.getErrors();
|
||||
|
||||
if (errors > 0)
|
||||
{
|
||||
logger.warn("Errors in annotation checker, exiting");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
AnnotationRenamer an = new AnnotationRenamer(group2);
|
||||
an.run();
|
||||
|
||||
ParameterRenamer pr = new ParameterRenamer(group1, group2, mapping);
|
||||
pr.run();
|
||||
}
|
||||
|
||||
public void save(File out) throws IOException
|
||||
{
|
||||
JarUtil.saveJar(group2, out);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException
|
||||
{
|
||||
if (args.length < 3)
|
||||
{
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
UpdateMappings u = new UpdateMappings(
|
||||
JarUtil.loadJar(new File(args[0])),
|
||||
JarUtil.loadJar(new File(args[1]))
|
||||
);
|
||||
u.update();
|
||||
u.save(new File(args[2]));
|
||||
}
|
||||
}
|
||||
36
deobfuscator/src/main/java/net/runelite/deob/util/IdGen.java
Normal file
36
deobfuscator/src/main/java/net/runelite/deob/util/IdGen.java
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.util;
|
||||
|
||||
public class IdGen
|
||||
{
|
||||
private int cur = 1;
|
||||
|
||||
public synchronized int get()
|
||||
{
|
||||
return cur++;
|
||||
}
|
||||
}
|
||||
127
deobfuscator/src/main/java/net/runelite/deob/util/JarUtil.java
Normal file
127
deobfuscator/src/main/java/net/runelite/deob/util/JarUtil.java
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Enumeration;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Manifest;
|
||||
import net.runelite.asm.ClassFile;
|
||||
import net.runelite.asm.ClassGroup;
|
||||
import net.runelite.asm.objectwebasm.NonloadingClassWriter;
|
||||
import net.runelite.asm.visitors.ClassFileVisitor;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.util.CheckClassAdapter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class JarUtil
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(JarUtil.class);
|
||||
|
||||
public static ClassGroup loadJar(File jarfile) throws IOException
|
||||
{
|
||||
ClassGroup group = new ClassGroup();
|
||||
|
||||
try (JarFile jar = new JarFile(jarfile))
|
||||
{
|
||||
for (Enumeration<JarEntry> it = jar.entries(); it.hasMoreElements();)
|
||||
{
|
||||
JarEntry entry = it.nextElement();
|
||||
|
||||
if (!entry.getName().endsWith(".class"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
InputStream is = jar.getInputStream(entry);
|
||||
|
||||
ClassReader reader = new ClassReader(is);
|
||||
ClassFileVisitor cv = new ClassFileVisitor();
|
||||
|
||||
reader.accept(cv, ClassReader.SKIP_FRAMES);
|
||||
|
||||
group.addClass(cv.getClassFile());
|
||||
}
|
||||
}
|
||||
|
||||
group.initialize();
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
public static void saveJar(ClassGroup group, File jarfile) throws IOException
|
||||
{
|
||||
try (JarOutputStream jout = new JarOutputStream(new FileOutputStream(jarfile), new Manifest()))
|
||||
{
|
||||
for (ClassFile cf : group.getClasses())
|
||||
{
|
||||
JarEntry entry = new JarEntry(cf.getName() + ".class");
|
||||
jout.putNextEntry(entry);
|
||||
|
||||
byte[] data = writeClass(group, cf);
|
||||
|
||||
jout.write(data);
|
||||
jout.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] writeClass(ClassGroup group, ClassFile cf)
|
||||
{
|
||||
ClassWriter writer = new NonloadingClassWriter(group, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
|
||||
CheckClassAdapter cca = new CheckClassAdapter(writer, false);
|
||||
|
||||
cf.accept(cca);
|
||||
|
||||
byte[] data = writer.toByteArray();
|
||||
|
||||
validateDataFlow(cf.getName(), data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private static void validateDataFlow(String name, byte[] data)
|
||||
{
|
||||
try
|
||||
{
|
||||
ClassReader cr = new ClassReader(data);
|
||||
ClassWriter cw = new ClassWriter(cr, 0);
|
||||
ClassVisitor cv = new CheckClassAdapter(cw, true);
|
||||
cr.accept(cv, 0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.warn("Class {} failed validation", name, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.util;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import net.runelite.asm.pool.Class;
|
||||
import net.runelite.asm.pool.Field;
|
||||
import net.runelite.asm.pool.Method;
|
||||
|
||||
public class NameMappings
|
||||
{
|
||||
private final Map<Object, String> map = new HashMap<>();
|
||||
|
||||
private final Map<Object, String[]> paramMap = new HashMap<>();
|
||||
|
||||
public void map(Class cf, String name)
|
||||
{
|
||||
map.put(cf, name);
|
||||
}
|
||||
|
||||
public void map(Field field, String name)
|
||||
{
|
||||
map.put(field, name);
|
||||
}
|
||||
|
||||
public void map(Method method, String name)
|
||||
{
|
||||
map.put(method, name);
|
||||
}
|
||||
|
||||
public void map(Method method, String[] params)
|
||||
{
|
||||
paramMap.put(method, params);
|
||||
}
|
||||
|
||||
public String get(Object object)
|
||||
{
|
||||
return map.get(object);
|
||||
}
|
||||
|
||||
public String[] getP(Method method)
|
||||
{
|
||||
return paramMap.get(method);
|
||||
}
|
||||
|
||||
public Map<Object, String> getMap()
|
||||
{
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 2016-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 net.runelite.deob.util;
|
||||
|
||||
public class PrimitiveUtils
|
||||
{
|
||||
public static Class<?> unbox(Class<?> c)
|
||||
{
|
||||
if (c == int.class)
|
||||
return Integer.class;
|
||||
else if (c == long.class)
|
||||
return Long.class;
|
||||
else if (c == byte.class)
|
||||
return Byte.class;
|
||||
else if (c == char.class)
|
||||
return Character.class;
|
||||
else if (c == short.class)
|
||||
return Short.class;
|
||||
else if (c == boolean.class)
|
||||
return Boolean.class;
|
||||
else if (c == float.class)
|
||||
return Float.class;
|
||||
else if (c == double.class)
|
||||
return Double.class;
|
||||
else if (c == void.class)
|
||||
return Void.class;
|
||||
else
|
||||
return c;
|
||||
}
|
||||
|
||||
public static Object convert(Number n, Class<?> c)
|
||||
{
|
||||
c = unbox(c);
|
||||
|
||||
if (c == Integer.class)
|
||||
return n.intValue();
|
||||
else if (c == Long.class)
|
||||
return n.longValue();
|
||||
else if (c == Byte.class)
|
||||
return n.byteValue();
|
||||
else if (c == Short.class)
|
||||
return n.shortValue();
|
||||
else if (c == Float.class)
|
||||
return n.floatValue();
|
||||
else if (c == Double.class)
|
||||
return n.doubleValue();
|
||||
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user