mirror of
https://github.com/SuspiciousActivity/ProGuard-Unmapper.git
synced 2026-08-13 18:17:44 +00:00
Init
This commit is contained in:
1 parent
d1d71dd7f2
commit
46cd6de355
6 files changed
+781
No files matched your search
@@ -0,0 +1,5 @@
|
||||
# Unmapper
|
||||
This simple program allows you to input an obfuscated jar file together with the corresponding mapping.txt to deobfuscate the jar file.
|
||||
It uses ObjectWeb ASM (http://asm.ow2.org/)
|
||||
|
||||
ProGuard (https://www.guardsquare.com/en/products/proguard)
|
||||
@@ -0,0 +1,394 @@
|
||||
package me.Unmapper;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.objectweb.asm.Handle;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.FieldInsnNode;
|
||||
import org.objectweb.asm.tree.FieldNode;
|
||||
import org.objectweb.asm.tree.FrameNode;
|
||||
import org.objectweb.asm.tree.InnerClassNode;
|
||||
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
|
||||
import org.objectweb.asm.tree.LdcInsnNode;
|
||||
import org.objectweb.asm.tree.LocalVariableNode;
|
||||
import org.objectweb.asm.tree.MethodInsnNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
import org.objectweb.asm.tree.MultiANewArrayInsnNode;
|
||||
import org.objectweb.asm.tree.TryCatchBlockNode;
|
||||
import org.objectweb.asm.tree.TypeInsnNode;
|
||||
|
||||
public class ClassNodeRemapper {
|
||||
|
||||
private static final Pattern CLASS_NAME_PATTERN = Pattern.compile("(L[^;<]+[;<])");
|
||||
|
||||
private final HashMap<String, String> classMappings;
|
||||
private final HashMap<MemberData, String> memberMappings;
|
||||
|
||||
public ClassNodeRemapper(HashMap<String, String> classMappings, HashMap<MemberData, String> memberMappings) {
|
||||
this.classMappings = classMappings;
|
||||
this.memberMappings = memberMappings;
|
||||
}
|
||||
|
||||
public void remap(ClassNode cn) {
|
||||
remapMethods(cn);
|
||||
remapFields(cn);
|
||||
cn.name = getOldClassName(cn.name);
|
||||
cn.superName = getOldClassName(cn.superName);
|
||||
cn.signature = getOldSignature(cn.signature);
|
||||
if (!cn.interfaces.isEmpty()) {
|
||||
List<String> newInterfaces = new ArrayList<String>();
|
||||
for (String s : cn.interfaces) {
|
||||
newInterfaces.add(getOldClassName(s));
|
||||
}
|
||||
cn.interfaces = newInterfaces;
|
||||
}
|
||||
if (cn.innerClasses != null && !cn.innerClasses.isEmpty()) {
|
||||
for (InnerClassNode icn : cn.innerClasses) {
|
||||
String full = getOldClassName(icn.name);
|
||||
if (icn.outerName != null) {
|
||||
String[] split = full.split("\\$");
|
||||
icn.outerName = split[0];
|
||||
if (icn.innerName != null) {
|
||||
icn.innerName = split[1];
|
||||
}
|
||||
}
|
||||
icn.name = full;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final int ACC_STATIC = 0x0008;
|
||||
|
||||
private void remapMethods(ClassNode cn) {
|
||||
if (cn.methods == null)
|
||||
return;
|
||||
String clazz = cn.name;
|
||||
for (MethodNode mn : cn.methods) {
|
||||
MemberData data = new MemberData(cn.name, mn.desc, mn.name);
|
||||
mn.name = getOldMemberName(data);
|
||||
mn.desc = getOldMemberMethodDesc(mn.desc);
|
||||
mn.signature = getOldSignature(mn.signature);
|
||||
if (!mn.exceptions.isEmpty()) {
|
||||
List<String> newExceptions = new ArrayList<String>();
|
||||
for (String s : mn.exceptions) {
|
||||
newExceptions.add(getOldClassName(s));
|
||||
}
|
||||
mn.exceptions = newExceptions;
|
||||
}
|
||||
if (mn.localVariables != null && !mn.localVariables.isEmpty()) {
|
||||
Iterator<LocalVariableNode> lvns = mn.localVariables.iterator();
|
||||
while (lvns.hasNext()) {
|
||||
LocalVariableNode lvn = lvns.next();
|
||||
if (Main.removeLocals) {
|
||||
if ((mn.access & ACC_STATIC) == 0 && lvn.index == 0) { // "this" for non-static methods
|
||||
lvn.name = "this"; // should always be "this"
|
||||
} else if (!isAscii(lvn.name)) {
|
||||
lvns.remove();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
lvn.desc = getOldMemberFieldDesc(lvn.desc);
|
||||
lvn.signature = getOldSignature(lvn.signature);
|
||||
}
|
||||
}
|
||||
if (mn.tryCatchBlocks != null && !mn.tryCatchBlocks.isEmpty()) {
|
||||
for (TryCatchBlockNode tcbn : mn.tryCatchBlocks) {
|
||||
tcbn.type = getOldClassName(tcbn.type);
|
||||
}
|
||||
}
|
||||
int len = mn.instructions.size();
|
||||
for (int i = 0; i < len; i++) {
|
||||
remapInstruction(clazz, mn.instructions.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void remapFields(ClassNode cn) {
|
||||
if (cn.fields == null)
|
||||
return;
|
||||
for (FieldNode fn : cn.fields) {
|
||||
MemberData data = new MemberData(cn.name, fn.desc, fn.name);
|
||||
fn.name = getOldMemberName(data);
|
||||
fn.desc = getOldMemberFieldDesc(fn.desc);
|
||||
fn.signature = getOldSignature(fn.signature);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAscii(String s) {
|
||||
for (char c : s.toCharArray()) {
|
||||
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private String getOldClassName(String name) {
|
||||
if (name == null)
|
||||
return null;
|
||||
String old = classMappings.get(name);
|
||||
if (old != null)
|
||||
return old;
|
||||
return name;
|
||||
}
|
||||
|
||||
private String getOldMemberName(MemberData data) {
|
||||
if (data == null)
|
||||
return null;
|
||||
String old = memberMappings.get(data);
|
||||
if (old != null)
|
||||
return old;
|
||||
return data.getName();
|
||||
}
|
||||
|
||||
private String getOldMemberMethodDesc(String desc) {
|
||||
if (desc == null)
|
||||
return null;
|
||||
int argsEndIdx = desc.indexOf(')');
|
||||
String retType = getOldMemberFieldDesc(desc.substring(argsEndIdx + 1));
|
||||
String args = desc.substring(1, argsEndIdx);
|
||||
|
||||
int idx = 0;
|
||||
char[] arr = args.toCharArray();
|
||||
StringBuilder outerSb = new StringBuilder("(");
|
||||
|
||||
while (idx < arr.length) {
|
||||
int arrayCount = 0;
|
||||
for (; idx < arr.length && arr[idx] == '['; idx++)
|
||||
arrayCount++;
|
||||
|
||||
if (idx >= arr.length) // weird input?
|
||||
return desc;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
switch (arr[idx]) {
|
||||
case 'B':
|
||||
case 'Z':
|
||||
case 'I':
|
||||
case 'J':
|
||||
case 'C':
|
||||
case 'D':
|
||||
case 'F':
|
||||
case 'S':
|
||||
case 'V':
|
||||
sb.append(arr[idx]);
|
||||
idx++;
|
||||
break;
|
||||
case 'L': {
|
||||
idx++;
|
||||
int startIdx = idx;
|
||||
for (; idx < arr.length && arr[idx] != ';'; idx++)
|
||||
;
|
||||
sb.append('L');
|
||||
sb.append(getOldClassName(args.substring(startIdx, idx)));
|
||||
sb.append(';');
|
||||
idx++;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < arrayCount; i++)
|
||||
sb.insert(0, '[');
|
||||
|
||||
outerSb.append(sb);
|
||||
}
|
||||
|
||||
outerSb.append(')');
|
||||
outerSb.append(retType);
|
||||
|
||||
return outerSb.toString();
|
||||
}
|
||||
|
||||
private String getOldMemberFieldDesc(String desc) {
|
||||
if (desc == null)
|
||||
return null;
|
||||
int idx = 0;
|
||||
char[] arr = desc.toCharArray();
|
||||
|
||||
int arrayCount = 0;
|
||||
for (; idx < arr.length && arr[idx] == '['; idx++)
|
||||
arrayCount++;
|
||||
|
||||
if (idx >= arr.length) // weird input?
|
||||
return desc;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
switch (arr[idx]) {
|
||||
case 'B':
|
||||
case 'Z':
|
||||
case 'I':
|
||||
case 'J':
|
||||
case 'C':
|
||||
case 'D':
|
||||
case 'F':
|
||||
case 'S':
|
||||
case 'V':
|
||||
sb.append(arr[idx]);
|
||||
break;
|
||||
case 'L': {
|
||||
idx++;
|
||||
int startIdx = idx;
|
||||
for (; idx < arr.length && arr[idx] != ';'; idx++)
|
||||
;
|
||||
sb.append('L');
|
||||
sb.append(getOldClassName(desc.substring(startIdx, idx)));
|
||||
sb.append(';');
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < arrayCount; i++)
|
||||
sb.insert(0, '[');
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String getOldSignature(String s) {
|
||||
if (s == null || s.isEmpty())
|
||||
return s;
|
||||
if (s.startsWith("(")) { // Methods
|
||||
int startIdx = s.indexOf('(');
|
||||
int endIdx = s.indexOf(')');
|
||||
if (endIdx < 0) // Weird?
|
||||
return s;
|
||||
String typeType = s.substring(0, startIdx);
|
||||
String args = s.substring(startIdx + 1, endIdx);
|
||||
String retType = s.substring(endIdx + 1);
|
||||
return getOldSignature(typeType) + "(" + getOldSignature(args) + ")" + getOldSignature(retType);
|
||||
} else {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
Matcher m = CLASS_NAME_PATTERN.matcher(s);
|
||||
while (m.find()) {
|
||||
m.appendReplacement(sb, Matcher.quoteReplacement(getOldSignatureFromMatcher(m.group())));
|
||||
}
|
||||
m.appendTail(sb);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private String getOldSignatureFromMatcher(String s) {
|
||||
boolean endsWithBracket = s.endsWith("<");
|
||||
s = s.substring(1, s.length() - 1);
|
||||
return "L" + getOldClassName(s) + (endsWithBracket ? "<" : ";");
|
||||
}
|
||||
|
||||
private void remapInstruction(String clazz, AbstractInsnNode n) {
|
||||
if (FieldInsnNode.class.isInstance(n)) {
|
||||
FieldInsnNode node = (FieldInsnNode) n;
|
||||
node.name = getOldMemberName(new MemberData(clazz, node.desc, node.name));
|
||||
node.desc = getOldMemberFieldDesc(node.desc);
|
||||
node.owner = getOldClassName(node.owner);
|
||||
} else if (FrameNode.class.isInstance(n)) {
|
||||
FrameNode node = (FrameNode) n;
|
||||
if (node.local != null) {
|
||||
int len = node.local.size();
|
||||
for (int i = 0; i < len; i++) {
|
||||
Object o = node.local.get(i);
|
||||
if (o == null)
|
||||
continue;
|
||||
if (o instanceof String) {
|
||||
String s = (String) o;
|
||||
if (s.startsWith("["))
|
||||
s = getOldMemberFieldDesc(s);
|
||||
else
|
||||
s = getOldClassName(s);
|
||||
node.local.set(i, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.stack != null) {
|
||||
int len = node.stack.size();
|
||||
for (int i = 0; i < len; i++) {
|
||||
Object o = node.stack.get(i);
|
||||
if (o == null)
|
||||
continue;
|
||||
if (o instanceof String) {
|
||||
String s = (String) o;
|
||||
if (s.startsWith("["))
|
||||
s = getOldMemberFieldDesc(s);
|
||||
else
|
||||
s = getOldClassName(s);
|
||||
node.stack.set(i, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (InvokeDynamicInsnNode.class.isInstance(n)) {
|
||||
InvokeDynamicInsnNode node = (InvokeDynamicInsnNode) n;
|
||||
node.name = getOldMemberName(new MemberData(clazz, node.desc, node.name));
|
||||
node.desc = getOldMemberMethodDesc(node.desc);
|
||||
node.bsm = new Handle(node.bsm.getTag(), getOldClassName(node.bsm.getOwner()),
|
||||
getOldMemberName(new MemberData(node.bsm.getOwner(), node.bsm.getDesc(), node.bsm.getName())),
|
||||
getOldMemberMethodDesc(node.bsm.getDesc()), node.bsm.isInterface());
|
||||
int len = node.bsmArgs.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
Object o = node.bsmArgs[i];
|
||||
if (o instanceof Type) {
|
||||
Type type = (Type) o;
|
||||
try {
|
||||
Field f_off = Type.class.getDeclaredField("off");
|
||||
f_off.setAccessible(true);
|
||||
Field f_len = Type.class.getDeclaredField("len");
|
||||
f_len.setAccessible(true);
|
||||
Field f_buf = Type.class.getDeclaredField("buf");
|
||||
f_buf.setAccessible(true);
|
||||
String buf = new String((char[]) f_buf.get(type));
|
||||
String oldBuf = getOldClassName(buf);
|
||||
f_buf.set(type, oldBuf.toCharArray());
|
||||
f_len.set(type, oldBuf.length());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else if (o instanceof Handle) {
|
||||
Handle h = (Handle) o;
|
||||
node.bsmArgs[i] = new Handle(h.getTag(), getOldClassName(h.getOwner()),
|
||||
getOldMemberName(new MemberData(h.getOwner(), h.getDesc(), h.getName())),
|
||||
getOldMemberMethodDesc(h.getDesc()), h.isInterface());
|
||||
}
|
||||
}
|
||||
} else if (MethodInsnNode.class.isInstance(n)) {
|
||||
MethodInsnNode node = (MethodInsnNode) n;
|
||||
node.name = getOldMemberName(new MemberData(clazz, node.desc, node.name));
|
||||
node.desc = getOldMemberMethodDesc(node.desc);
|
||||
node.owner = getOldClassName(node.owner);
|
||||
} else if (TypeInsnNode.class.isInstance(n)) {
|
||||
TypeInsnNode node = (TypeInsnNode) n;
|
||||
node.desc = getOldClassName(node.desc);
|
||||
} else if (MultiANewArrayInsnNode.class.isInstance(n)) {
|
||||
MultiANewArrayInsnNode node = (MultiANewArrayInsnNode) n;
|
||||
node.desc = getOldClassName(node.desc);
|
||||
} else if (LdcInsnNode.class.isInstance(n)) {
|
||||
LdcInsnNode node = (LdcInsnNode) n;
|
||||
if (node.cst instanceof Type) {
|
||||
Type type = (Type) node.cst;
|
||||
try {
|
||||
Field f_off = Type.class.getDeclaredField("off");
|
||||
f_off.setAccessible(true);
|
||||
Field f_len = Type.class.getDeclaredField("len");
|
||||
f_len.setAccessible(true);
|
||||
Field f_buf = Type.class.getDeclaredField("buf");
|
||||
f_buf.setAccessible(true);
|
||||
String buf = new String((char[]) f_buf.get(type));
|
||||
String oldBuf = getOldClassName(buf);
|
||||
f_buf.set(type, oldBuf.toCharArray());
|
||||
f_len.set(type, oldBuf.length());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package me.Unmapper;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class ClassUtil {
|
||||
|
||||
public static String getSimpleDesc(String type) {
|
||||
String desc = "";
|
||||
for (int i = 0; i < countArraySize(type); i++) {
|
||||
desc += "[";
|
||||
}
|
||||
type = type.replace("[]", "");
|
||||
if (VARS.containsKey(type)) {
|
||||
desc += VARS.get(type);
|
||||
} else {
|
||||
desc += "L" + type + ";";
|
||||
}
|
||||
return desc;
|
||||
}
|
||||
|
||||
private static int countArraySize(String type) {
|
||||
int size = 0;
|
||||
for (byte b : type.getBytes()) {
|
||||
if (b == ']') {
|
||||
size++;
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
public static final HashMap<String, String> VARS = new HashMap<>();
|
||||
|
||||
static {
|
||||
VARS.put("byte", "B");
|
||||
VARS.put("char", "C");
|
||||
VARS.put("double", "D");
|
||||
VARS.put("float", "F");
|
||||
VARS.put("int", "I");
|
||||
VARS.put("long", "J");
|
||||
VARS.put("short", "S");
|
||||
VARS.put("boolean", "Z");
|
||||
VARS.put("void", "V");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package me.Unmapper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarOutputStream;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
|
||||
public class Main implements Opcodes {
|
||||
|
||||
private static HashMap<JarEntry, byte[]> OTHER_FILES = new HashMap<>();
|
||||
private static ArrayList<ClassNode> classNodes = new ArrayList<>();
|
||||
private static MappingReader mapping;
|
||||
|
||||
public static boolean removeLocals = false;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length < 3) {
|
||||
System.out.println("Syntax: input.jar mapping.txt output.jar (-l)");
|
||||
System.out.println("\t-l\tRemoves local variables which are not ASCII");
|
||||
System.out.println("\t\t\t(let the decompilers generate better names)");
|
||||
System.exit(1);
|
||||
}
|
||||
if (args.length > 3) {
|
||||
removeLocals = args[3].equalsIgnoreCase("-l");
|
||||
System.out.println("Enabled local variable removal.");
|
||||
}
|
||||
load(new File(args[0]));
|
||||
map(new File(args[1]));
|
||||
save(new File(args[2]));
|
||||
}
|
||||
|
||||
public static void map(File mappingFile) {
|
||||
System.out.println("Reading mapping...");
|
||||
mapping = new MappingReader(mappingFile);
|
||||
try {
|
||||
mapping.load();
|
||||
} catch (Exception ex) {
|
||||
new Exception("Error loading mapping", ex).printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
System.out.println("Read " + mapping.getClassMappings().size() + " class mappings and "
|
||||
+ mapping.getMemberMappings().size() + " member mappings.");
|
||||
System.out.println("Remapping...");
|
||||
|
||||
ClassNodeRemapper remapper = new ClassNodeRemapper(mapping.getClassMappings(), mapping.getMemberMappings());
|
||||
for (ClassNode cn : classNodes) {
|
||||
remapper.remap(cn);
|
||||
}
|
||||
}
|
||||
|
||||
public static void save(File jar) {
|
||||
System.out.println("Saving...");
|
||||
try {
|
||||
try (final JarOutputStream output = new JarOutputStream(new FileOutputStream(jar))) {
|
||||
for (Entry<JarEntry, byte[]> entry : OTHER_FILES.entrySet()) {
|
||||
output.putNextEntry(entry.getKey());
|
||||
output.write(entry.getValue());
|
||||
output.closeEntry();
|
||||
}
|
||||
for (ClassNode element : classNodes) {
|
||||
ClassWriter writer = new ClassWriter(0);
|
||||
element.accept(writer);
|
||||
output.putNextEntry(new JarEntry(element.name + ".class"));
|
||||
output.write(writer.toByteArray());
|
||||
output.closeEntry();
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
new Exception("Error saving jar file", ex).printStackTrace();
|
||||
}
|
||||
System.out.println("Saved!");
|
||||
}
|
||||
|
||||
public static void load(File file) {
|
||||
System.out.println("Loading...");
|
||||
try {
|
||||
JarFile jar = new JarFile(file);
|
||||
ArrayList<JarEntry> entrysLeft = new ArrayList<>();
|
||||
Enumeration<JarEntry> enumeration = jar.entries();
|
||||
while (enumeration.hasMoreElements()) {
|
||||
JarEntry next = enumeration.nextElement();
|
||||
byte[] data = IOUtils.toByteArray(jar.getInputStream(next));
|
||||
if (next.getName().endsWith(".class")) {
|
||||
ClassReader reader = new ClassReader(data);
|
||||
ClassNode node = new ClassNode();
|
||||
reader.accept(node, 0);
|
||||
|
||||
try {
|
||||
classNodes.add(node);
|
||||
} catch (NoClassDefFoundError e) {
|
||||
entrysLeft.add(next);
|
||||
}
|
||||
} else {
|
||||
OTHER_FILES.put(new JarEntry(next.getName()), data);
|
||||
}
|
||||
}
|
||||
while (!entrysLeft.isEmpty()) {
|
||||
for (JarEntry next : (ArrayList<JarEntry>) entrysLeft.clone()) {
|
||||
byte[] data = IOUtils.toByteArray(jar.getInputStream(next));
|
||||
if (next.getName().endsWith(".class")) {
|
||||
ClassReader reader = new ClassReader(data);
|
||||
ClassNode node = new ClassNode();
|
||||
reader.accept(node, 0);
|
||||
try {
|
||||
classNodes.add(node);
|
||||
entrysLeft.remove(next);
|
||||
} catch (NoClassDefFoundError e) {
|
||||
|
||||
}
|
||||
} else {
|
||||
OTHER_FILES.put(new JarEntry(next.getName()), data);
|
||||
}
|
||||
}
|
||||
}
|
||||
jar.close();
|
||||
} catch (Exception ex) {
|
||||
new Exception("Error loading jar file", ex).printStackTrace();
|
||||
}
|
||||
System.out.println("Loaded!");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package me.Unmapper;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class MappingReader {
|
||||
|
||||
private final File mappingFile;
|
||||
|
||||
private final HashMap<String, String> internalReversedClassMappings = new HashMap<>();
|
||||
private final HashMap<String, String> classMappings = new HashMap<>();
|
||||
private final HashMap<MemberData, String> memberMappings = new HashMap<>();
|
||||
|
||||
public MappingReader(File mappingFile) {
|
||||
this.mappingFile = mappingFile;
|
||||
}
|
||||
|
||||
public void load() throws IOException {
|
||||
try (BufferedReader read = new BufferedReader(new FileReader(mappingFile))) {
|
||||
String className = null;
|
||||
String s;
|
||||
|
||||
while ((s = read.readLine()) != null) {
|
||||
s = s.trim();
|
||||
|
||||
if (s.startsWith("#"))
|
||||
continue;
|
||||
|
||||
if (s.endsWith(":")) {
|
||||
className = parseClassMapping(s);
|
||||
} else if (className != null) {
|
||||
parseClassMemberMapping(className, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String parseClassMapping(String s) {
|
||||
int splitIdx = s.indexOf("->");
|
||||
if (splitIdx < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int colonIndex = s.indexOf(':', splitIdx + 2);
|
||||
if (colonIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String originalName = s.substring(0, splitIdx).trim().replace('.', '/');
|
||||
String obfuscatedName = s.substring(splitIdx + 2, colonIndex).trim().replace('.', '/');
|
||||
|
||||
classMappings.put(obfuscatedName, originalName);
|
||||
internalReversedClassMappings.put(originalName, obfuscatedName);
|
||||
|
||||
return originalName;
|
||||
}
|
||||
|
||||
private void parseClassMemberMapping(String className, String s) {
|
||||
int colonIdx1 = s.indexOf(':');
|
||||
int colonIdx2 = colonIdx1 < 0 ? -1 : s.indexOf(':', colonIdx1 + 1);
|
||||
int spaceIdx = s.indexOf(' ', colonIdx2 + 2);
|
||||
int argIdx1 = s.indexOf('(', spaceIdx + 1);
|
||||
int argIdx2 = argIdx1 < 0 ? -1 : s.indexOf(')', argIdx1 + 1);
|
||||
int colonIdx3 = argIdx2 < 0 ? -1 : s.indexOf(':', argIdx2 + 1);
|
||||
int colonIdx4 = colonIdx3 < 0 ? -1 : s.indexOf(':', colonIdx3 + 1);
|
||||
int arrowIdx = s.indexOf("->",
|
||||
(colonIdx4 >= 0 ? colonIdx4 : colonIdx3 >= 0 ? colonIdx3 : argIdx2 >= 0 ? argIdx2 : spaceIdx) + 1);
|
||||
|
||||
if (spaceIdx < 0 || arrowIdx < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
String type = s.substring(colonIdx2 + 1, spaceIdx).trim();
|
||||
String originalName = s.substring(spaceIdx + 1, argIdx1 >= 0 ? argIdx1 : arrowIdx).trim();
|
||||
String obfuscatedName = s.substring(arrowIdx + 2).trim();
|
||||
|
||||
String newClassName = className;
|
||||
int dotIndex = originalName.lastIndexOf('.');
|
||||
if (dotIndex >= 0) {
|
||||
className = originalName.substring(0, dotIndex);
|
||||
originalName = originalName.substring(dotIndex + 1);
|
||||
// How does this work?
|
||||
System.out.println("Don't know how to handle mapping " + className + " to " + newClassName);
|
||||
}
|
||||
|
||||
if (type.length() > 0 && originalName.length() > 0 && obfuscatedName.length() > 0) {
|
||||
if (argIdx2 < 0) {
|
||||
memberMappings.put(
|
||||
new MemberData(getOldClassName(className), convertTypeToByteCode(type), obfuscatedName),
|
||||
originalName);
|
||||
} else {
|
||||
String args = s.substring(argIdx1 + 1, argIdx2).trim();
|
||||
|
||||
memberMappings.put(new MemberData(getOldClassName(className),
|
||||
"(" + convertMultipleTypesToByteCode(args) + ")" + convertTypeToByteCode(type), obfuscatedName),
|
||||
originalName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HashMap<String, String> getClassMappings() {
|
||||
return classMappings;
|
||||
}
|
||||
|
||||
public HashMap<MemberData, String> getMemberMappings() {
|
||||
return memberMappings;
|
||||
}
|
||||
|
||||
private String getOldClassName(String name) {
|
||||
name = name.replace('.', '/');
|
||||
String old = internalReversedClassMappings.get(name);
|
||||
if (old != null)
|
||||
return old;
|
||||
return name;
|
||||
}
|
||||
|
||||
private String convertTypeToByteCode(String type) {
|
||||
if (type.isEmpty())
|
||||
return "";
|
||||
return ClassUtil.getSimpleDesc(getOldClassName(type));
|
||||
}
|
||||
|
||||
private String convertMultipleTypesToByteCode(String types) {
|
||||
if (types.isEmpty())
|
||||
return "";
|
||||
String ret = "";
|
||||
for (String s : types.split(",")) {
|
||||
ret += convertTypeToByteCode(s);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package me.Unmapper;
|
||||
|
||||
public class MemberData {
|
||||
|
||||
private final String clazz;
|
||||
private final String desc;
|
||||
private final String name;
|
||||
|
||||
public MemberData(String clazz, String desc, String name) {
|
||||
this.clazz = clazz;
|
||||
this.desc = desc;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getClazz() {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((clazz == null) ? 0 : clazz.hashCode());
|
||||
result = prime * result + ((desc == null) ? 0 : desc.hashCode());
|
||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
MemberData other = (MemberData) obj;
|
||||
if (clazz == null) {
|
||||
if (other.clazz != null)
|
||||
return false;
|
||||
} else if (!clazz.equals(other.clazz))
|
||||
return false;
|
||||
if (desc == null) {
|
||||
if (other.desc != null)
|
||||
return false;
|
||||
} else if (!desc.equals(other.desc))
|
||||
return false;
|
||||
if (name == null) {
|
||||
if (other.name != null)
|
||||
return false;
|
||||
} else if (!name.equals(other.name))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MemberData [clazz=" + clazz + ", desc=" + desc + ", name=" + name + "]";
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user