From a88175ffc95792b88a8724f66db6dda2b8cc32ee Mon Sep 17 00:00:00 2001 From: gegy1000 Date: Tue, 17 Jul 2018 19:14:08 +0200 Subject: ASM Based Class Translator (#1) * Initial port to ASM * Package updates * Annotation + inner class translation * Fix inner class mapping * More bytecode translation * Signature refactoring * Fix highlighting of mapped names * Fix parameter name offset * Fix anonymous class generation * Fix issues with inner class signature transformation * Fix bridged method detection * Fix compile issues * Resolve all failed tests * Apply deobfuscated name to transformed classes * Fix class signatures not being translated * Fix frame array type translation * Fix frame array type translation * Fix array translation in method calls * Fix method reference and bridge detection * Fix handling of null deobf mappings * Parameter translation in interfaces * Fix enum parameter index offset * Fix parsed local variable indexing * Fix stackoverflow on rebuilding method names * Ignore invalid decompiled variable indices * basic source jar * Output directly to file on source export * Make decompile parallel * fix incorrect super calls * Use previous save state to delete old mapping files * Fix old mappings not properly being removed * Fix old mappings not properly being removed * make isMethodProvider public (cherry picked from commit ebad6a9) * speed up Deobfuscator's getSources by using a single TranslatingTypeloader and caching the ClassLoaderTypeloader * ignore .idea project folders * move SynchronizedTypeLoader to a non-inner * fix signature remap of inners for now * index & resolve method/field references for usages view * Allow reader/writer subclasses to provide the underlying file operations * fix giving obf classes a name not removing them from the panel * buffer the ParsedJar class entry inputstream, allow use with a jarinputstream * make CachingClasspathTypeLoader public * make CachingClasspathTypeLoader public * support enum switches with obfuscated SwitchMaps --- .../java/cuchaz/enigma/mapping/ArgumentEntry.java | 110 ------ .../cuchaz/enigma/mapping/ArgumentMapping.java | 50 --- .../java/cuchaz/enigma/mapping/BehaviorEntry.java | 16 - .../java/cuchaz/enigma/mapping/ClassEntry.java | 167 ---------- .../java/cuchaz/enigma/mapping/ClassMapping.java | 210 +++++++----- .../cuchaz/enigma/mapping/ClassNameReplacer.java | 16 - .../cuchaz/enigma/mapping/ConstructorEntry.java | 105 ------ .../enigma/mapping/DirectionalTranslator.java | 370 +++++++++++++++++++++ src/main/java/cuchaz/enigma/mapping/Entry.java | 22 -- .../java/cuchaz/enigma/mapping/EntryFactory.java | 132 -------- .../java/cuchaz/enigma/mapping/FieldEntry.java | 87 ----- .../java/cuchaz/enigma/mapping/FieldMapping.java | 38 +-- .../cuchaz/enigma/mapping/LocalVariableEntry.java | 102 ------ .../enigma/mapping/LocalVariableMapping.java | 53 +++ src/main/java/cuchaz/enigma/mapping/Mappings.java | 58 +++- .../cuchaz/enigma/mapping/MappingsChecker.java | 14 +- .../enigma/mapping/MappingsEnigmaReader.java | 167 +++++----- .../enigma/mapping/MappingsEnigmaWriter.java | 124 +++---- .../cuchaz/enigma/mapping/MappingsRenamer.java | 164 +++++---- .../cuchaz/enigma/mapping/MappingsSRGWriter.java | 7 +- .../cuchaz/enigma/mapping/MappingsTinyReader.java | 7 +- .../java/cuchaz/enigma/mapping/MemberMapping.java | 3 + .../cuchaz/enigma/mapping/MethodDescriptor.java | 114 +++++++ .../java/cuchaz/enigma/mapping/MethodEntry.java | 90 ----- .../java/cuchaz/enigma/mapping/MethodMapping.java | 139 ++++---- .../java/cuchaz/enigma/mapping/NameValidator.java | 16 +- .../cuchaz/enigma/mapping/ProcyonEntryFactory.java | 67 ---- src/main/java/cuchaz/enigma/mapping/Signature.java | 128 +++---- .../enigma/mapping/TranslationDirection.java | 10 +- .../java/cuchaz/enigma/mapping/Translator.java | 365 ++++---------------- src/main/java/cuchaz/enigma/mapping/Type.java | 235 ------------- .../java/cuchaz/enigma/mapping/TypeDescriptor.java | 244 ++++++++++++++ .../cuchaz/enigma/mapping/entry/ClassDefEntry.java | 37 +++ .../cuchaz/enigma/mapping/entry/ClassEntry.java | 175 ++++++++++ .../java/cuchaz/enigma/mapping/entry/Entry.java | 22 ++ .../cuchaz/enigma/mapping/entry/EntryFactory.java | 49 +++ .../cuchaz/enigma/mapping/entry/FieldDefEntry.java | 43 +++ .../cuchaz/enigma/mapping/entry/FieldEntry.java | 77 +++++ .../mapping/entry/LocalVariableDefEntry.java | 57 ++++ .../enigma/mapping/entry/LocalVariableEntry.java | 82 +++++ .../enigma/mapping/entry/MethodDefEntry.java | 54 +++ .../cuchaz/enigma/mapping/entry/MethodEntry.java | 80 +++++ .../enigma/mapping/entry/ProcyonEntryFactory.java | 48 +++ .../enigma/mapping/entry/ReferencedEntryPool.java | 53 +++ 44 files changed, 2203 insertions(+), 2004 deletions(-) delete mode 100644 src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/ClassEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/Entry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/EntryFactory.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/FieldEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/LocalVariableEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java create mode 100644 src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/MethodEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/Type.java create mode 100644 src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/ClassDefEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/ClassEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/Entry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/EntryFactory.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/FieldDefEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/FieldEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/LocalVariableDefEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/LocalVariableEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/MethodEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/ProcyonEntryFactory.java create mode 100644 src/main/java/cuchaz/enigma/mapping/entry/ReferencedEntryPool.java (limited to 'src/main/java/cuchaz/enigma/mapping') diff --git a/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java b/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java deleted file mode 100644 index 9154cc2..0000000 --- a/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java +++ /dev/null @@ -1,110 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -import cuchaz.enigma.utils.Utils; - -public class ArgumentEntry implements Entry { - - private BehaviorEntry behaviorEntry; - private int index; - private String name; - - public ArgumentEntry(BehaviorEntry behaviorEntry, int index, String name) { - if (behaviorEntry == null) { - throw new IllegalArgumentException("Behavior cannot be null!"); - } - if (index < 0) { - throw new IllegalArgumentException("Index must be non-negative!"); - } - if (name == null) { - throw new IllegalArgumentException("Argument name cannot be null!"); - } - - this.behaviorEntry = behaviorEntry; - this.index = index; - this.name = name; - } - - public ArgumentEntry(ArgumentEntry other) { - this.behaviorEntry = other.getBehaviorEntry(); - this.index = other.index; - this.name = other.name; - } - - public ArgumentEntry(ArgumentEntry other, String newClassName) { - this.behaviorEntry = (BehaviorEntry) other.behaviorEntry.cloneToNewClass(new ClassEntry(newClassName)); - this.index = other.index; - this.name = other.name; - } - - public ArgumentEntry(ArgumentEntry other, BehaviorEntry entry) { - this.behaviorEntry = entry; - this.index = other.index; - this.name = other.name; - } - - public BehaviorEntry getBehaviorEntry() { - return this.behaviorEntry; - } - - public int getIndex() { - return this.index; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public ClassEntry getClassEntry() { - return this.behaviorEntry.getClassEntry(); - } - - @Override - public String getClassName() { - return this.behaviorEntry.getClassName(); - } - - @Override - public ArgumentEntry cloneToNewClass(ClassEntry classEntry) { - return new ArgumentEntry(this, classEntry.getName()); - } - - public String getMethodName() { - return this.behaviorEntry.getName(); - } - - public Signature getMethodSignature() { - return this.behaviorEntry.getSignature(); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.behaviorEntry, Integer.valueOf(this.index).hashCode(), this.name.hashCode()); - } - - @Override - public boolean equals(Object other) { - return other instanceof ArgumentEntry && equals((ArgumentEntry) other); - } - - public boolean equals(ArgumentEntry other) { - return this.behaviorEntry.equals(other.behaviorEntry) && this.index == other.index && this.name.equals(other.name); - } - - @Override - public String toString() { - return this.behaviorEntry + "(" + this.index + ":" + this.name + ")"; - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java b/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java deleted file mode 100644 index 91ecd10..0000000 --- a/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java +++ /dev/null @@ -1,50 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -public class ArgumentMapping implements Comparable { - - private int index; - private String name; - - // NOTE: this argument order is important for the MethodReader/MethodWriter - public ArgumentMapping(int index, String name) { - this.index = index; - this.name = NameValidator.validateArgumentName(name); - } - - public ArgumentMapping(ArgumentMapping other) { - this.index = other.index; - this.name = other.name; - } - - public int getIndex() { - return this.index; - } - - public String getName() { - return this.name; - } - - public void setName(String val) { - this.name = NameValidator.validateArgumentName(val); - } - - public ArgumentEntry getObfEntry(BehaviorEntry behaviorEntry) { - return new ArgumentEntry(behaviorEntry, index, name); - } - - @Override - public int compareTo(ArgumentMapping other) { - return Integer.compare(this.index, other.index); - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java b/src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java deleted file mode 100644 index 04b4ebc..0000000 --- a/src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java +++ /dev/null @@ -1,16 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -public interface BehaviorEntry extends Entry { - Signature getSignature(); -} diff --git a/src/main/java/cuchaz/enigma/mapping/ClassEntry.java b/src/main/java/cuchaz/enigma/mapping/ClassEntry.java deleted file mode 100644 index 788811f..0000000 --- a/src/main/java/cuchaz/enigma/mapping/ClassEntry.java +++ /dev/null @@ -1,167 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -import com.google.common.collect.Lists; - -import java.util.List; - -public class ClassEntry implements Entry { - - private String name; - - public ClassEntry(String className) { - if (className == null) { - throw new IllegalArgumentException("Class name cannot be null!"); - } - if (className.indexOf('.') >= 0) { - throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className); - } - - this.name = className; - - if (isInnerClass() && getInnermostClassName().indexOf('/') >= 0) { - throw new IllegalArgumentException("Inner class must not have a package: " + className); - } - } - - public ClassEntry(ClassEntry other) { - this.name = other.name; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public String getClassName() { - return this.name; - } - - @Override - public ClassEntry getClassEntry() { - return this; - } - - @Override - public ClassEntry cloneToNewClass(ClassEntry classEntry) { - return classEntry; - } - - @Override - public int hashCode() { - return this.name.hashCode(); - } - - @Override - public boolean equals(Object other) { - return other instanceof ClassEntry && equals((ClassEntry) other); - } - - public boolean equals(ClassEntry other) { - return other != null && this.name.equals(other.name); - } - - @Override - public String toString() { - return this.name; - } - - public boolean isInnerClass() { - return this.name.lastIndexOf('$') >= 0; - } - - public List getClassChainNames() { - return Lists.newArrayList(this.name.split("\\$")); - } - - public List getClassChain() { - List entries = Lists.newArrayList(); - StringBuilder buf = new StringBuilder(); - for (String name : getClassChainNames()) { - if (buf.length() > 0) { - buf.append("$"); - } - buf.append(name); - entries.add(new ClassEntry(buf.toString())); - } - return entries; - } - - public String getOutermostClassName() { - if (isInnerClass()) { - return this.name.substring(0, this.name.indexOf('$')); - } - return this.name; - } - - public ClassEntry getOutermostClassEntry() { - return new ClassEntry(getOutermostClassName()); - } - - public String getOuterClassName() { - if (!isInnerClass()) { - throw new Error("This is not an inner class!"); - } - return this.name.substring(0, this.name.lastIndexOf('$')); - } - - public ClassEntry getOuterClassEntry() { - return new ClassEntry(getOuterClassName()); - } - - public String getInnermostClassName() { - if (!isInnerClass()) { - throw new Error("This is not an inner class!"); - } - return this.name.substring(this.name.lastIndexOf('$') + 1); - } - - public boolean isInDefaultPackage() { - return this.name.indexOf('/') < 0; - } - - public String getPackageName() { - int pos = this.name.lastIndexOf('/'); - if (pos > 0) { - return this.name.substring(0, pos); - } - return null; - } - - public String getSimpleName() { - int pos = this.name.lastIndexOf('/'); - if (pos > 0) { - return this.name.substring(pos + 1); - } - return this.name; - } - - public ClassEntry buildClassEntry(List classChain) { - assert (classChain.contains(this)); - StringBuilder buf = new StringBuilder(); - for (ClassEntry chainEntry : classChain) { - if (buf.length() == 0) { - buf.append(chainEntry.getName()); - } else { - buf.append("$"); - buf.append(chainEntry.isInnerClass() ? chainEntry.getInnermostClassName() : chainEntry.getSimpleName()); - } - - if (chainEntry == this) { - break; - } - } - return new ClassEntry(buf.toString()); - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/ClassMapping.java b/src/main/java/cuchaz/enigma/mapping/ClassMapping.java index 51751ca..8f3f2b2 100644 --- a/src/main/java/cuchaz/enigma/mapping/ClassMapping.java +++ b/src/main/java/cuchaz/enigma/mapping/ClassMapping.java @@ -12,6 +12,9 @@ package cuchaz.enigma.mapping; import com.google.common.collect.Maps; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.FieldEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; import cuchaz.enigma.throwables.MappingConflict; import java.util.ArrayList; @@ -34,7 +37,6 @@ public class ClassMapping implements Comparable { private Map methodsByDeobf; private boolean isDirty; private Mappings.EntryModifier modifier; - private boolean deobfInner; public ClassMapping(String obfFullName) { this(obfFullName, null, Mappings.EntryModifier.UNCHANGED); @@ -81,6 +83,10 @@ public class ClassMapping implements Comparable { return deobfName; } + public String getTranslatedName(TranslationDirection direction) { + return direction.choose(deobfName, obfFullName); + } + //// INNER CLASSES //////// public void setDeobfName(String val) { @@ -191,21 +197,21 @@ public class ClassMapping implements Comparable { return fieldsByObf.values(); } - public boolean containsObfField(String obfName, Type obfType) { - return fieldsByObf.containsKey(getFieldKey(obfName, obfType)); + public boolean containsObfField(String obfName, TypeDescriptor obfDesc) { + return fieldsByObf.containsKey(getFieldKey(obfName, obfDesc)); } - public boolean containsDeobfField(String deobfName, Type deobfType) { - return fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfType)); + public boolean containsDeobfField(String deobfName, TypeDescriptor deobfDesc) { + return fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfDesc)); } public void addFieldMapping(FieldMapping fieldMapping) { - String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()); + String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc()); if (fieldsByObf.containsKey(obfKey)) { throw new Error("Already have mapping for " + obfFullName + "." + obfKey); } if (fieldMapping.getDeobfName() != null) { - String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType()); + String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfDesc()); if (fieldsByDeobf.containsKey(deobfKey)) { throw new Error("Already have mapping for " + deobfName + "." + deobfKey); } @@ -218,63 +224,67 @@ public class ClassMapping implements Comparable { } public void removeFieldMapping(FieldMapping fieldMapping) { - boolean obfWasRemoved = fieldsByObf.remove(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType())) != null; + boolean obfWasRemoved = fieldsByObf.remove(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc())) != null; assert (obfWasRemoved); if (fieldMapping.getDeobfName() != null) { - boolean deobfWasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType())) != null; + boolean deobfWasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfDesc())) != null; assert (deobfWasRemoved); } this.isDirty = true; } - public FieldMapping getFieldByObf(String obfName, Type obfType) { - return fieldsByObf.get(getFieldKey(obfName, obfType)); + public FieldMapping getFieldByObf(String obfName, TypeDescriptor obfDesc) { + return fieldsByObf.get(getFieldKey(obfName, obfDesc)); + } + + public FieldMapping getFieldByObf(FieldEntry field) { + return getFieldByObf(field.getName(), field.getDesc()); } - public FieldMapping getFieldByDeobf(String deobfName, Type obfType) { - return fieldsByDeobf.get(getFieldKey(deobfName, obfType)); + public FieldMapping getFieldByDeobf(String deobfName, TypeDescriptor obfDesc) { + return fieldsByDeobf.get(getFieldKey(deobfName, obfDesc)); } - public String getObfFieldName(String deobfName, Type obfType) { - FieldMapping fieldMapping = fieldsByDeobf.get(getFieldKey(deobfName, obfType)); + public String getObfFieldName(String deobfName, TypeDescriptor obfDesc) { + FieldMapping fieldMapping = fieldsByDeobf.get(getFieldKey(deobfName, obfDesc)); if (fieldMapping != null) { return fieldMapping.getObfName(); } return null; } - public String getDeobfFieldName(String obfName, Type obfType) { - FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfType)); + public String getDeobfFieldName(String obfName, TypeDescriptor obfDesc) { + FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfDesc)); if (fieldMapping != null) { return fieldMapping.getDeobfName(); } return null; } - private String getFieldKey(String name, Type type) { + private String getFieldKey(String name, TypeDescriptor desc) { if (name == null) { throw new IllegalArgumentException("name cannot be null!"); } - if (type == null) { - throw new IllegalArgumentException("type cannot be null!"); + if (desc == null) { + throw new IllegalArgumentException("desc cannot be null!"); } - return name + ":" + type; + return name + ":" + desc; } - public void setFieldName(String obfName, Type obfType, String deobfName) { + public void setFieldName(String obfName, TypeDescriptor obfDesc, String deobfName) { assert (deobfName != null); - FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfType)); + FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfDesc)); if (fieldMapping == null) { - fieldMapping = new FieldMapping(obfName, obfType, deobfName, Mappings.EntryModifier.UNCHANGED); - boolean obfWasAdded = fieldsByObf.put(getFieldKey(obfName, obfType), fieldMapping) == null; + fieldMapping = new FieldMapping(obfName, obfDesc, deobfName, Mappings.EntryModifier.UNCHANGED); + boolean obfWasAdded = fieldsByObf.put(getFieldKey(obfName, obfDesc), fieldMapping) == null; assert (obfWasAdded); } else { - boolean wasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), obfType)) != null; + boolean wasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), obfDesc)) != null; assert (wasRemoved); } fieldMapping.setDeobfName(deobfName); if (deobfName != null) { - boolean wasAdded = fieldsByDeobf.put(getFieldKey(deobfName, obfType), fieldMapping) == null; + boolean wasAdded = fieldsByDeobf.put(getFieldKey(deobfName, obfDesc), fieldMapping) == null; assert (wasAdded); } this.isDirty = true; @@ -282,13 +292,13 @@ public class ClassMapping implements Comparable { //// METHODS //////// - public void setFieldObfNameAndType(String oldObfName, Type obfType, String newObfName, Type newObfType) { + public void setFieldObfNameAndType(String oldObfName, TypeDescriptor obfDesc, String newObfName, TypeDescriptor newObfDesc) { assert (newObfName != null); - FieldMapping fieldMapping = fieldsByObf.remove(getFieldKey(oldObfName, obfType)); + FieldMapping fieldMapping = fieldsByObf.remove(getFieldKey(oldObfName, obfDesc)); assert (fieldMapping != null); fieldMapping.setObfName(newObfName); - fieldMapping.setObfType(newObfType); - boolean obfWasAdded = fieldsByObf.put(getFieldKey(newObfName, newObfType), fieldMapping) == null; + fieldMapping.setObfDesc(newObfDesc); + boolean obfWasAdded = fieldsByObf.put(getFieldKey(newObfName, newObfDesc), fieldMapping) == null; assert (obfWasAdded); this.isDirty = true; } @@ -298,23 +308,23 @@ public class ClassMapping implements Comparable { return methodsByObf.values(); } - public boolean containsObfMethod(String obfName, Signature obfSignature) { - return methodsByObf.containsKey(getMethodKey(obfName, obfSignature)); + public boolean containsObfMethod(String obfName, MethodDescriptor obfDescriptor) { + return methodsByObf.containsKey(getMethodKey(obfName, obfDescriptor)); } - public boolean containsDeobfMethod(String deobfName, Signature obfSignature) { - return methodsByDeobf.containsKey(getMethodKey(deobfName, obfSignature)); + public boolean containsDeobfMethod(String deobfName, MethodDescriptor obfDescriptor) { + return methodsByDeobf.containsKey(getMethodKey(deobfName, obfDescriptor)); } public void addMethodMapping(MethodMapping methodMapping) { - String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); + String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc()); if (methodsByObf.containsKey(obfKey)) { throw new Error("Already have mapping for " + obfFullName + "." + obfKey); } boolean wasAdded = methodsByObf.put(obfKey, methodMapping) == null; assert (wasAdded); - if (methodMapping.getDeobfName() != null) { - String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature()); + if (!methodMapping.isObfuscated()) { + String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfDesc()); if (methodsByDeobf.containsKey(deobfKey)) { throw new Error("Already have mapping for " + deobfName + "." + deobfKey); } @@ -326,44 +336,48 @@ public class ClassMapping implements Comparable { } public void removeMethodMapping(MethodMapping methodMapping) { - boolean obfWasRemoved = methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature())) != null; + boolean obfWasRemoved = methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc())) != null; assert (obfWasRemoved); - if (methodMapping.getDeobfName() != null) { - boolean deobfWasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null; + if (!methodMapping.isObfuscated()) { + boolean deobfWasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfDesc())) != null; assert (deobfWasRemoved); } this.isDirty = true; } - public MethodMapping getMethodByObf(String obfName, Signature obfSignature) { - return methodsByObf.get(getMethodKey(obfName, obfSignature)); + public MethodMapping getMethodByObf(String obfName, MethodDescriptor obfDescriptor) { + return methodsByObf.get(getMethodKey(obfName, obfDescriptor)); + } + + public MethodMapping getMethodByObf(MethodEntry method) { + return getMethodByObf(method.getName(), method.getDesc()); } - public MethodMapping getMethodByDeobf(String deobfName, Signature obfSignature) { - return methodsByDeobf.get(getMethodKey(deobfName, obfSignature)); + public MethodMapping getMethodByDeobf(String deobfName, MethodDescriptor obfDescriptor) { + return methodsByDeobf.get(getMethodKey(deobfName, obfDescriptor)); } - private String getMethodKey(String name, Signature signature) { + private String getMethodKey(String name, MethodDescriptor descriptor) { if (name == null) { throw new IllegalArgumentException("name cannot be null!"); } - if (signature == null) { - throw new IllegalArgumentException("signature cannot be null!"); + if (descriptor == null) { + throw new IllegalArgumentException("descriptor cannot be null!"); } - return name + signature; + return name + descriptor; } - public void setMethodName(String obfName, Signature obfSignature, String deobfName) { - MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfName, obfSignature)); + public void setMethodName(String obfName, MethodDescriptor obfDescriptor, String deobfName) { + MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfName, obfDescriptor)); if (methodMapping == null) { - methodMapping = createMethodMapping(obfName, obfSignature); - } else if (methodMapping.getDeobfName() != null) { - boolean wasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null; + methodMapping = createMethodMapping(obfName, obfDescriptor); + } else if (!methodMapping.isObfuscated()) { + boolean wasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfDesc())) != null; assert (wasRemoved); } methodMapping.setDeobfName(deobfName); if (deobfName != null) { - boolean wasAdded = methodsByDeobf.put(getMethodKey(deobfName, obfSignature), methodMapping) == null; + boolean wasAdded = methodsByDeobf.put(getMethodKey(deobfName, obfDescriptor), methodMapping) == null; assert (wasAdded); } this.isDirty = true; @@ -371,35 +385,35 @@ public class ClassMapping implements Comparable { //// ARGUMENTS //////// - public void setMethodObfNameAndSignature(String oldObfName, Signature obfSignature, String newObfName, Signature newObfSignature) { + public void setMethodObfNameAndSignature(String oldObfName, MethodDescriptor obfDescriptor, String newObfName, MethodDescriptor newObfDescriptor) { assert (newObfName != null); - MethodMapping methodMapping = methodsByObf.remove(getMethodKey(oldObfName, obfSignature)); + MethodMapping methodMapping = methodsByObf.remove(getMethodKey(oldObfName, obfDescriptor)); assert (methodMapping != null); methodMapping.setObfName(newObfName); - methodMapping.setObfSignature(newObfSignature); - boolean obfWasAdded = methodsByObf.put(getMethodKey(newObfName, newObfSignature), methodMapping) == null; + methodMapping.setObfDescriptor(newObfDescriptor); + boolean obfWasAdded = methodsByObf.put(getMethodKey(newObfName, newObfDescriptor), methodMapping) == null; assert (obfWasAdded); this.isDirty = true; } - public void setArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex, String argumentName) { + public void setArgumentName(String obfMethodName, MethodDescriptor obfMethodDescriptor, int argumentIndex, String argumentName) { assert (argumentName != null); - MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)); + MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfMethodName, obfMethodDescriptor)); if (methodMapping == null) { - methodMapping = createMethodMapping(obfMethodName, obfMethodSignature); + methodMapping = createMethodMapping(obfMethodName, obfMethodDescriptor); } - methodMapping.setArgumentName(argumentIndex, argumentName); + methodMapping.setLocalVariableName(argumentIndex, argumentName); this.isDirty = true; } - public void removeArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex) { - methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)).removeArgumentName(argumentIndex); + public void removeArgumentName(String obfMethodName, MethodDescriptor obfMethodDescriptor, int argumentIndex) { + methodsByObf.get(getMethodKey(obfMethodName, obfMethodDescriptor)).removeLocalVariableName(argumentIndex); this.isDirty = true; } - private MethodMapping createMethodMapping(String obfName, Signature obfSignature) { - MethodMapping methodMapping = new MethodMapping(obfName, obfSignature); - boolean wasAdded = methodsByObf.put(getMethodKey(obfName, obfSignature), methodMapping) == null; + private MethodMapping createMethodMapping(String obfName, MethodDescriptor obfDescriptor) { + MethodMapping methodMapping = new MethodMapping(obfName, obfDescriptor); + boolean wasAdded = methodsByObf.put(getMethodKey(obfName, obfDescriptor), methodMapping) == null; assert (wasAdded); this.isDirty = true; return methodMapping; @@ -459,24 +473,24 @@ public class ClassMapping implements Comparable { // rename field types for (FieldMapping fieldMapping : new ArrayList<>(fieldsByObf.values())) { - String oldFieldKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()); + String oldFieldKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc()); if (fieldMapping.renameObfClass(oldObfClassName, newObfClassName)) { boolean wasRemoved = fieldsByObf.remove(oldFieldKey) != null; assert (wasRemoved); boolean wasAdded = fieldsByObf - .put(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()), fieldMapping) == null; + .put(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc()), fieldMapping) == null; assert (wasAdded); } } // rename method signatures for (MethodMapping methodMapping : new ArrayList<>(methodsByObf.values())) { - String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); + String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc()); if (methodMapping.renameObfClass(oldObfClassName, newObfClassName)) { boolean wasRemoved = methodsByObf.remove(oldMethodKey) != null; assert (wasRemoved); boolean wasAdded = methodsByObf - .put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()), methodMapping) == null; + .put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc()), methodMapping) == null; assert (wasAdded); } } @@ -490,9 +504,9 @@ public class ClassMapping implements Comparable { return false; } - public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) { - MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfBehaviorEntry.getName(), obfBehaviorEntry.getSignature())); - return methodMapping != null && methodMapping.containsArgument(name); + public boolean containsArgument(MethodEntry obfMethodEntry, String name) { + MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfMethodEntry.getName(), obfMethodEntry.getDesc())); + return methodMapping != null && methodMapping.containsLocalVariable(name); } public ClassEntry getObfEntry() { @@ -503,6 +517,14 @@ public class ClassMapping implements Comparable { return deobfFullName != null ? new ClassEntry(deobfFullName) : null; } + public boolean isObfuscated() { + return this.deobfName == null || this.deobfName.equals(this.obfFullName); + } + + public String getSaveName() { + return this.isObfuscated() ? this.obfFullName : this.deobfName; + } + public boolean isDirty() { return isDirty; } @@ -511,6 +533,10 @@ public class ClassMapping implements Comparable { this.isDirty = false; } + public void markDirty() { + this.isDirty = true; + } + public Mappings.EntryModifier getModifier() { return modifier; } @@ -521,9 +547,9 @@ public class ClassMapping implements Comparable { this.modifier = modifier; } - public void setFieldModifier(String obfName, Type obfType, Mappings.EntryModifier modifier) { - FieldMapping fieldMapping = fieldsByObf.computeIfAbsent(getFieldKey(obfName, obfType), - k -> new FieldMapping(obfName, obfType, null, Mappings.EntryModifier.UNCHANGED)); + public void setFieldModifier(String obfName, TypeDescriptor obfDesc, Mappings.EntryModifier modifier) { + FieldMapping fieldMapping = fieldsByObf.computeIfAbsent(getFieldKey(obfName, obfDesc), + k -> new FieldMapping(obfName, obfDesc, null, Mappings.EntryModifier.UNCHANGED)); if (fieldMapping.getModifier() != modifier) { fieldMapping.setModifier(modifier); @@ -531,7 +557,7 @@ public class ClassMapping implements Comparable { } } - public void setMethodModifier(String obfName, Signature sig, Mappings.EntryModifier modifier) { + public void setMethodModifier(String obfName, MethodDescriptor sig, Mappings.EntryModifier modifier) { MethodMapping methodMapping = methodsByObf.computeIfAbsent(getMethodKey(obfName, sig), k -> new MethodMapping(obfName, sig, null, Mappings.EntryModifier.UNCHANGED)); @@ -546,4 +572,30 @@ public class ClassMapping implements Comparable { this.deobfFullName = deobName; return this; } + + public ClassMapping copy() { + ClassMapping copied = new ClassMapping(this.obfFullName); + copied.obfSimpleName= this.obfSimpleName; + copied.modifier = this.modifier; + copied.deobfFullName = this.deobfFullName; + copied.deobfName = this.deobfName; + copied.innerClassesByDeobf = this.innerClassesByDeobf; + copied.innerClassesByObfFull = this.innerClassesByObfFull; + copied.innerClassesByObfSimple = this.innerClassesByObfSimple; + copied.fieldsByObf = this.fieldsByObf; + copied.fieldsByDeobf = this.fieldsByDeobf; + copied.methodsByObf = this.methodsByObf; + copied.methodsByDeobf = this.methodsByDeobf; + return copied; + } + + @Override + public int hashCode() { + return this.obfFullName.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof ClassMapping && ((ClassMapping) obj).obfFullName.equals(this.obfFullName); + } } diff --git a/src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java b/src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java deleted file mode 100644 index 801c410..0000000 --- a/src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java +++ /dev/null @@ -1,16 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -public interface ClassNameReplacer { - String replace(String className); -} diff --git a/src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java b/src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java deleted file mode 100644 index 20e5113..0000000 --- a/src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java +++ /dev/null @@ -1,105 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -import cuchaz.enigma.utils.Utils; - -public class ConstructorEntry implements BehaviorEntry { - - private ClassEntry classEntry; - private Signature signature; - - public ConstructorEntry(ClassEntry classEntry) { - this(classEntry, null); - } - - public ConstructorEntry(ClassEntry classEntry, Signature signature) { - if (classEntry == null) { - throw new IllegalArgumentException("Class cannot be null!"); - } - - this.classEntry = classEntry; - this.signature = signature; - } - - public ConstructorEntry(ConstructorEntry other, String newClassName) { - this.classEntry = new ClassEntry(newClassName); - this.signature = other.signature; - } - - @Override - public ClassEntry getClassEntry() { - return this.classEntry; - } - - @Override - public String getName() { - if (isStatic()) { - return ""; - } - return ""; - } - - public boolean isStatic() { - return this.signature == null; - } - - @Override - public Signature getSignature() { - return this.signature; - } - - @Override - public String getClassName() { - return this.classEntry.getName(); - } - - @Override - public ConstructorEntry cloneToNewClass(ClassEntry classEntry) { - return new ConstructorEntry(this, classEntry.getName()); - } - - @Override - public int hashCode() { - if (isStatic()) { - return Utils.combineHashesOrdered(this.classEntry); - } else { - return Utils.combineHashesOrdered(this.classEntry, this.signature); - } - } - - @Override - public boolean equals(Object other) { - return other instanceof ConstructorEntry && equals((ConstructorEntry) other); - } - - public boolean equals(ConstructorEntry other) { - if (isStatic() != other.isStatic()) { - return false; - } - - if (isStatic()) { - return this.classEntry.equals(other.classEntry); - } else { - return this.classEntry.equals(other.classEntry) && this.signature.equals(other.signature); - } - } - - @Override - public String toString() { - if (isStatic()) { - return this.classEntry.getName() + "." + getName(); - } else { - return this.classEntry.getName() + "." + getName() + this.signature; - } - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java b/src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java new file mode 100644 index 0000000..b0bb129 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java @@ -0,0 +1,370 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import cuchaz.enigma.analysis.TranslationIndex; +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.entry.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class DirectionalTranslator implements Translator { + private final TranslationDirection direction; + private final Map classes; + private final TranslationIndex index; + + public DirectionalTranslator(ReferencedEntryPool entryPool) { + this.direction = null; + this.classes = Maps.newHashMap(); + this.index = new TranslationIndex(entryPool); + } + + public DirectionalTranslator(TranslationDirection direction, Map classes, TranslationIndex index) { + this.direction = direction; + this.classes = classes; + this.index = index; + } + + public TranslationDirection getDirection() { + return direction; + } + + public TranslationIndex getTranslationIndex() { + return index; + } + + @Override + public ClassEntry getTranslatedClass(ClassEntry entry) { + String className; + if (entry.isArray()) { + className = this.getTranslatedTypeDesc(new TypeDescriptor(entry.getName())).toString(); + } else { + className = entry.isInnerClass() ? translateInnerClassName(entry) : translateClassName(entry); + } + return new ClassEntry(className); + } + + @Override + public ClassDefEntry getTranslatedClassDef(ClassDefEntry entry) { + String className; + if (entry.isArray()) { + className = this.getTranslatedTypeDesc(new TypeDescriptor(entry.getName())).toString(); + } else { + className = entry.isInnerClass() ? translateInnerClassName(entry) : translateClassName(entry); + } + Signature translatedSignature = this.getTranslatedSignature(entry.getSignature()); + return new ClassDefEntry(className, translatedSignature, getClassModifier(entry).transform(entry.getAccess())); + } + + private String translateClassName(ClassEntry entry) { + // normal classes are easy + ClassMapping classMapping = this.classes.get(entry.getName()); + if (classMapping == null) { + return entry.getName(); + } + return classMapping.getTranslatedName(direction); + } + + private String translateInnerClassName(ClassEntry entry) { + // translate as much of the class chain as we can + List mappingsChain = getClassMappingChain(entry); + String[] obfClassNames = entry.getName().split("\\$"); + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < obfClassNames.length; i++) { + boolean isFirstClass = buf.length() == 0; + String className = null; + ClassMapping classMapping = mappingsChain.get(i); + if (classMapping != null) { + className = this.direction.choose( + classMapping.getDeobfName(), + isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName() + ); + } + if (className == null) { + className = obfClassNames[i]; + } + if (!isFirstClass) { + buf.append("$"); + } + buf.append(className); + } + return buf.toString(); + } + + @Override + public FieldDefEntry getTranslatedFieldDef(FieldDefEntry entry) { + String translatedName = translateFieldName(entry); + if (translatedName == null) { + translatedName = entry.getName(); + } + ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry()); + TypeDescriptor translatedDesc = getTranslatedTypeDesc(entry.getDesc()); + Signature translatedSignature = getTranslatedSignature(entry.getSignature()); + AccessFlags translatedAccess = getFieldModifier(entry).transform(entry.getAccess()); + return new FieldDefEntry(translatedOwner, translatedName, translatedDesc, translatedSignature, translatedAccess); + } + + @Override + public FieldEntry getTranslatedField(FieldEntry entry) { + String translatedName = translateFieldName(entry); + if (translatedName == null) { + translatedName = entry.getName(); + } + ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry()); + TypeDescriptor translatedDesc = getTranslatedTypeDesc(entry.getDesc()); + return new FieldEntry(translatedOwner, translatedName, translatedDesc); + } + + private String translateFieldName(FieldEntry entry) { + // resolve the class entry + ClassEntry resolvedClassEntry = this.index.resolveEntryOwner(entry, true); + if (resolvedClassEntry != null) { + // look for the class + ClassMapping classMapping = findClassMapping(resolvedClassEntry); + if (classMapping != null) { + // look for the field + FieldMapping mapping = this.direction.choose( + classMapping.getFieldByObf(entry.getName(), entry.getDesc()), + classMapping.getFieldByDeobf(entry.getName(), getTranslatedTypeDesc(entry.getDesc())) + ); + if (mapping != null) { + return this.direction.choose(mapping.getDeobfName(), mapping.getObfName()); + } + } + } + return null; + } + + @Override + public MethodDefEntry getTranslatedMethodDef(MethodDefEntry entry) { + String translatedName = translateMethodName(entry); + if (translatedName == null) { + translatedName = entry.getName(); + } + ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry()); + MethodDescriptor translatedDesc = getTranslatedMethodDesc(entry.getDesc()); + Signature translatedSignature = getTranslatedSignature(entry.getSignature()); + AccessFlags access = getMethodModifier(entry).transform(entry.getAccess()); + return new MethodDefEntry(translatedOwner, translatedName, translatedDesc, translatedSignature, access); + } + + @Override + public MethodEntry getTranslatedMethod(MethodEntry entry) { + String translatedName = translateMethodName(entry); + if (translatedName == null) { + translatedName = entry.getName(); + } + ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry()); + MethodDescriptor translatedDesc = getTranslatedMethodDesc(entry.getDesc()); + return new MethodEntry(translatedOwner, translatedName, translatedDesc); + } + + private String translateMethodName(MethodEntry entry) { + // resolve the class entry + ClassEntry resolvedOwner = this.index.resolveEntryOwner(entry, true); + if (resolvedOwner != null) { + // look for class + ClassMapping classMapping = findClassMapping(resolvedOwner); + if (classMapping != null) { + // look for the method + MethodMapping mapping = this.direction.choose( + classMapping.getMethodByObf(entry.getName(), entry.getDesc()), + classMapping.getMethodByDeobf(entry.getName(), getTranslatedMethodDesc(entry.getDesc())) + ); + if (mapping != null) { + return this.direction.choose(mapping.getDeobfName(), mapping.getObfName()); + } + } + } + return null; + } + + @Override + public LocalVariableEntry getTranslatedVariable(LocalVariableEntry entry) { + String translatedArgumentName = translateLocalVariableName(entry); + if (translatedArgumentName == null) { + translatedArgumentName = inheritLocalVariableName(entry); + } + if (translatedArgumentName == null) { + translatedArgumentName = entry.getName(); + } + // TODO: Translating arguments calls method translation.. Can we refactor the code in such a way that we don't need this? + MethodEntry translatedOwner = getTranslatedMethod(entry.getOwnerEntry()); + return new LocalVariableEntry(translatedOwner, entry.getIndex(), translatedArgumentName); + } + + @Override + public LocalVariableDefEntry getTranslatedVariableDef(LocalVariableDefEntry entry) { + String translatedArgumentName = translateLocalVariableName(entry); + if (translatedArgumentName == null) { + translatedArgumentName = inheritLocalVariableName(entry); + } + // TODO: Translating arguments calls method translation.. Can we refactor the code in such a way that we don't need this? + MethodDefEntry translatedOwner = getTranslatedMethodDef(entry.getOwnerEntry()); + TypeDescriptor translatedTypeDesc = getTranslatedTypeDesc(entry.getDesc()); + return new LocalVariableDefEntry(translatedOwner, entry.getIndex(), translatedArgumentName != null ? translatedArgumentName : entry.getName(), translatedTypeDesc); + } + + @Override + public boolean hasClassMapping(ClassEntry entry) { + return classes.containsKey(entry.getName()); + } + + @Override + public boolean hasFieldMapping(FieldEntry entry) { + return translateFieldName(entry) != null; + } + + @Override + public boolean hasMethodMapping(MethodEntry entry) { + return translateMethodName(entry) != null; + } + + @Override + public boolean hasLocalVariableMapping(LocalVariableEntry entry) { + return translateLocalVariableName(entry) != null || inheritLocalVariableName(entry) != null; + } + + // TODO: support not identical behavior (specific to constructor) + private String translateLocalVariableName(LocalVariableEntry entry) { + // look for identical behavior in superclasses + ClassEntry ownerEntry = entry.getOwnerClassEntry(); + if (ownerEntry != null) { + // look for the class + ClassMapping classMapping = findClassMapping(ownerEntry); + if (classMapping != null) { + // look for the method + MethodMapping methodMapping = this.direction.choose( + classMapping.getMethodByObf(entry.getMethodName(), entry.getMethodDesc()), + classMapping.getMethodByDeobf(entry.getMethodName(), getTranslatedMethodDesc(entry.getMethodDesc())) + ); + if (methodMapping != null) { + int index = entry.getIndex(); + return this.direction.choose( + methodMapping.getDeobfLocalVariableName(index), + methodMapping.getObfLocalVariableName(index) + ); + } + } + } + return null; + } + + private String inheritLocalVariableName(LocalVariableEntry entry) { + List ancestry = this.index.getAncestry(entry.getOwnerClassEntry()); + // Check in mother class for the arg + for (ClassEntry ancestorEntry : ancestry) { + LocalVariableEntry motherArg = entry.updateOwnership(ancestorEntry); + if (this.index.entryExists(motherArg)) { + String result = translateLocalVariableName(motherArg); + if (result != null) { + return result; + } + } + } + return null; + } + + @Override + public TypeDescriptor getTranslatedTypeDesc(TypeDescriptor desc) { + return desc.remap(this::remapClass); + } + + @Override + public MethodDescriptor getTranslatedMethodDesc(MethodDescriptor descriptor) { + List arguments = descriptor.getArgumentDescs(); + List translatedArguments = new ArrayList<>(arguments.size()); + for (TypeDescriptor argument : arguments) { + translatedArguments.add(getTranslatedTypeDesc(argument)); + } + return new MethodDescriptor(translatedArguments, getTranslatedTypeDesc(descriptor.getReturnDesc())); + } + + @Override + public Signature getTranslatedSignature(Signature signature) { + if (signature == null) { + return null; + } + return signature.remap(this::remapClass); + } + + private ClassMapping findClassMapping(ClassEntry entry) { + List mappingChain = getClassMappingChain(entry); + return mappingChain.get(mappingChain.size() - 1); + } + + private List getClassMappingChain(ClassEntry entry) { + + // get a list of all the classes in the hierarchy + String[] parts = entry.getName().split("\\$"); + List mappingsChain = Lists.newArrayList(); + + // get mappings for the outer class + ClassMapping outerClassMapping = this.classes.get(parts[0]); + mappingsChain.add(outerClassMapping); + + for (int i = 1; i < parts.length; i++) { + + // get mappings for the inner class + ClassMapping innerClassMapping = null; + if (outerClassMapping != null) { + innerClassMapping = this.direction.choose( + outerClassMapping.getInnerClassByObfSimple(parts[i]), + outerClassMapping.getInnerClassByDeobfThenObfSimple(parts[i]) + ); + } + mappingsChain.add(innerClassMapping); + outerClassMapping = innerClassMapping; + } + + assert (mappingsChain.size() == parts.length); + return mappingsChain; + } + + private Mappings.EntryModifier getClassModifier(ClassEntry entry) { + ClassMapping classMapping = findClassMapping(entry); + if (classMapping != null) { + return classMapping.getModifier(); + } + return Mappings.EntryModifier.UNCHANGED; + } + + private Mappings.EntryModifier getFieldModifier(FieldEntry entry) { + ClassMapping classMapping = findClassMapping(entry.getOwnerClassEntry()); + if (classMapping != null) { + FieldMapping fieldMapping = classMapping.getFieldByObf(entry); + if (fieldMapping != null) { + return fieldMapping.getModifier(); + } + } + return Mappings.EntryModifier.UNCHANGED; + } + + private Mappings.EntryModifier getMethodModifier(MethodEntry entry) { + ClassMapping classMapping = findClassMapping(entry.getOwnerClassEntry()); + if (classMapping != null) { + MethodMapping methodMapping = classMapping.getMethodByObf(entry); + if (methodMapping != null) { + return methodMapping.getModifier(); + } + } + return Mappings.EntryModifier.UNCHANGED; + } + + private String remapClass(String name) { + return getTranslatedClass(new ClassEntry(name)).getName(); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/Entry.java b/src/main/java/cuchaz/enigma/mapping/Entry.java deleted file mode 100644 index c79510b..0000000 --- a/src/main/java/cuchaz/enigma/mapping/Entry.java +++ /dev/null @@ -1,22 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -public interface Entry { - String getName(); - - String getClassName(); - - ClassEntry getClassEntry(); - - Entry cloneToNewClass(ClassEntry classEntry); -} diff --git a/src/main/java/cuchaz/enigma/mapping/EntryFactory.java b/src/main/java/cuchaz/enigma/mapping/EntryFactory.java deleted file mode 100644 index 993bb64..0000000 --- a/src/main/java/cuchaz/enigma/mapping/EntryFactory.java +++ /dev/null @@ -1,132 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -import cuchaz.enigma.analysis.JarIndex; -import javassist.*; -import javassist.bytecode.Descriptor; -import javassist.expr.ConstructorCall; -import javassist.expr.FieldAccess; -import javassist.expr.MethodCall; -import javassist.expr.NewExpr; - -public class EntryFactory { - - public static ClassEntry getClassEntry(CtClass c) { - return new ClassEntry(Descriptor.toJvmName(c.getName())); - } - - public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) { - ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfFullName()); - return obfClassEntry.buildClassEntry(jarIndex.getObfClassChain(obfClassEntry)); - } - - private static ClassEntry getObfClassEntry(ClassMapping classMapping) { - return new ClassEntry(classMapping.getObfFullName()); - } - - public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) { - return new ClassEntry(classMapping.getDeobfName()); - } - - public static ClassEntry getSuperclassEntry(CtClass c) { - return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass())); - } - - public static FieldEntry getFieldEntry(CtField field) { - return new FieldEntry(getClassEntry(field.getDeclaringClass()), field.getName(), new Type(field.getFieldInfo().getDescriptor())); - } - - public static FieldEntry getFieldEntry(FieldAccess call) { - return new FieldEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), call.getFieldName(), new Type(call.getSignature())); - } - - public static FieldEntry getFieldEntry(String className, String name, String type) { - return new FieldEntry(new ClassEntry(className), name, new Type(type)); - } - - public static FieldEntry getObfFieldEntry(ClassMapping classMapping, FieldMapping fieldMapping) { - return new FieldEntry(getObfClassEntry(classMapping), fieldMapping.getObfName(), fieldMapping.getObfType()); - } - - public static MethodEntry getMethodEntry(CtMethod method) { - return new MethodEntry(getClassEntry(method.getDeclaringClass()), method.getName(), new Signature(method.getMethodInfo().getDescriptor())); - } - - public static MethodEntry getMethodEntry(MethodCall call) { - return new MethodEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), call.getMethodName(), new Signature(call.getSignature())); - } - - public static ConstructorEntry getConstructorEntry(CtConstructor constructor) { - if (constructor.isClassInitializer()) { - return new ConstructorEntry(getClassEntry(constructor.getDeclaringClass())); - } else { - return new ConstructorEntry(getClassEntry(constructor.getDeclaringClass()), new Signature(constructor.getMethodInfo().getDescriptor())); - } - } - - public static ConstructorEntry getConstructorEntry(ConstructorCall call) { - return new ConstructorEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), new Signature(call.getSignature())); - } - - public static ConstructorEntry getConstructorEntry(NewExpr call) { - return new ConstructorEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), new Signature(call.getSignature())); - } - - public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) { - if (behavior instanceof CtMethod) { - return getMethodEntry((CtMethod) behavior); - } else if (behavior instanceof CtConstructor) { - return getConstructorEntry((CtConstructor) behavior); - } - throw new Error("behavior is neither Method nor Constructor!"); - } - - public static BehaviorEntry getBehaviorEntry(String className, String behaviorName, String behaviorSignature) { - return getBehaviorEntry(new ClassEntry(className), behaviorName, new Signature(behaviorSignature)); - } - - public static BehaviorEntry getBehaviorEntry(String className, String behaviorName) { - return getBehaviorEntry(new ClassEntry(className), behaviorName); - } - - public static BehaviorEntry getBehaviorEntry(String className) { - return new ConstructorEntry(new ClassEntry(className)); - } - - public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName, Signature behaviorSignature) { - switch (behaviorName) { - case "": - return new ConstructorEntry(classEntry, behaviorSignature); - case "": - return new ConstructorEntry(classEntry); - default: - return new MethodEntry(classEntry, behaviorName, behaviorSignature); - } - } - - public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName) { - if (behaviorName.equals("")) { - return new ConstructorEntry(classEntry); - } else { - throw new IllegalArgumentException("Only class initializers don't have signatures"); - } - } - - public static BehaviorEntry getObfBehaviorEntry(ClassEntry classEntry, MethodMapping methodMapping) { - return getBehaviorEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature()); - } - - public static BehaviorEntry getObfBehaviorEntry(ClassMapping classMapping, MethodMapping methodMapping) { - return getObfBehaviorEntry(getObfClassEntry(classMapping), methodMapping); - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/FieldEntry.java b/src/main/java/cuchaz/enigma/mapping/FieldEntry.java deleted file mode 100644 index 0f1f506..0000000 --- a/src/main/java/cuchaz/enigma/mapping/FieldEntry.java +++ /dev/null @@ -1,87 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -import cuchaz.enigma.utils.Utils; - -public class FieldEntry implements Entry { - - private ClassEntry classEntry; - private String name; - private Type type; - - // NOTE: this argument order is important for the MethodReader/MethodWriter - public FieldEntry(ClassEntry classEntry, String name, Type type) { - if (classEntry == null) { - throw new IllegalArgumentException("Class cannot be null!"); - } - if (name == null) { - throw new IllegalArgumentException("Field name cannot be null!"); - } - if (type == null) { - throw new IllegalArgumentException("Field type cannot be null!"); - } - - this.classEntry = classEntry; - this.name = name; - this.type = type; - } - - public FieldEntry(FieldEntry other, ClassEntry newClassEntry) { - this.classEntry = newClassEntry; - this.name = other.name; - this.type = other.type; - } - - @Override - public ClassEntry getClassEntry() { - return this.classEntry; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public String getClassName() { - return this.classEntry.getName(); - } - - public Type getType() { - return this.type; - } - - @Override - public FieldEntry cloneToNewClass(ClassEntry classEntry) { - return new FieldEntry(this, classEntry); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.classEntry, this.name, this.type); - } - - @Override - public boolean equals(Object other) { - return other instanceof FieldEntry && equals((FieldEntry) other); - } - - public boolean equals(FieldEntry other) { - return this.classEntry.equals(other.classEntry) && this.name.equals(other.name) && this.type.equals(other.type); - } - - @Override - public String toString() { - return this.classEntry.getName() + "." + this.name + ":" + this.type; - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/FieldMapping.java b/src/main/java/cuchaz/enigma/mapping/FieldMapping.java index cd761b4..8fbe095 100644 --- a/src/main/java/cuchaz/enigma/mapping/FieldMapping.java +++ b/src/main/java/cuchaz/enigma/mapping/FieldMapping.java @@ -11,32 +11,27 @@ package cuchaz.enigma.mapping; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.FieldEntry; import cuchaz.enigma.throwables.IllegalNameException; public class FieldMapping implements Comparable, MemberMapping { private String obfName; private String deobfName; - private Type obfType; + private TypeDescriptor obfDesc; private Mappings.EntryModifier modifier; - public FieldMapping(String obfName, Type obfType, String deobfName, Mappings.EntryModifier modifier) { + public FieldMapping(String obfName, TypeDescriptor obfDesc, String deobfName, Mappings.EntryModifier modifier) { this.obfName = obfName; this.deobfName = NameValidator.validateFieldName(deobfName); - this.obfType = obfType; + this.obfDesc = obfDesc; this.modifier = modifier; } - public FieldMapping(FieldMapping other, ClassNameReplacer obfClassNameReplacer) { - this.obfName = other.obfName; - this.deobfName = other.deobfName; - this.modifier = other.modifier; - this.obfType = new Type(other.obfType, obfClassNameReplacer); - } - @Override public FieldEntry getObfEntry(ClassEntry classEntry) { - return new FieldEntry(classEntry, this.obfName, this.obfType); + return new FieldEntry(classEntry, this.obfName, this.obfDesc); } @Override @@ -65,12 +60,12 @@ public class FieldMapping implements Comparable, MemberMapping, MemberMapping - { + // rename obf classes in the desc + TypeDescriptor newDesc = this.obfDesc.remap(className -> { if (className.equals(oldObfClassName)) { return newObfClassName; } - return null; + return className; }); - if (!newType.equals(this.obfType)) { - this.obfType = newType; + if (!newDesc.equals(this.obfDesc)) { + this.obfDesc = newDesc; return true; } return false; diff --git a/src/main/java/cuchaz/enigma/mapping/LocalVariableEntry.java b/src/main/java/cuchaz/enigma/mapping/LocalVariableEntry.java deleted file mode 100644 index 2bb5e3f..0000000 --- a/src/main/java/cuchaz/enigma/mapping/LocalVariableEntry.java +++ /dev/null @@ -1,102 +0,0 @@ -package cuchaz.enigma.mapping; - -import cuchaz.enigma.utils.Utils; - -/** - * Desc... - * Created by Thog - * 19/10/2016 - */ -public class LocalVariableEntry implements Entry { - - protected final BehaviorEntry behaviorEntry; - protected final String name; - protected final Type type; - protected final int index; - - public LocalVariableEntry(BehaviorEntry behaviorEntry, int index, String name, Type type) { - if (behaviorEntry == null) { - throw new IllegalArgumentException("Behavior cannot be null!"); - } - if (index < 0) { - throw new IllegalArgumentException("Index must be non-negative!"); - } - if (name == null) { - throw new IllegalArgumentException("Variable name cannot be null!"); - } - if (type == null) { - throw new IllegalArgumentException("Variable type cannot be null!"); - } - - this.behaviorEntry = behaviorEntry; - this.name = name; - this.type = type; - this.index = index; - } - - public LocalVariableEntry(LocalVariableEntry other, ClassEntry newClassEntry) { - this.behaviorEntry = (BehaviorEntry) other.behaviorEntry.cloneToNewClass(newClassEntry); - this.name = other.name; - this.type = other.type; - this.index = other.index; - } - - public BehaviorEntry getBehaviorEntry() { - return this.behaviorEntry; - } - - public Type getType() { - return type; - } - - public int getIndex() { - return index; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public ClassEntry getClassEntry() { - return this.behaviorEntry.getClassEntry(); - } - - @Override - public String getClassName() { - return this.behaviorEntry.getClassName(); - } - - @Override - public LocalVariableEntry cloneToNewClass(ClassEntry classEntry) { - return new LocalVariableEntry(this, classEntry); - } - - public String getMethodName() { - return this.behaviorEntry.getName(); - } - - public Signature getMethodSignature() { - return this.behaviorEntry.getSignature(); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.behaviorEntry, this.type.hashCode(), this.name.hashCode(), Integer.hashCode(this.index)); - } - - @Override - public boolean equals(Object other) { - return other instanceof LocalVariableEntry && equals((LocalVariableEntry) other); - } - - public boolean equals(LocalVariableEntry other) { - return this.behaviorEntry.equals(other.behaviorEntry) && this.type.equals(other.type) && this.name.equals(other.name) && this.index == other.index; - } - - @Override - public String toString() { - return this.behaviorEntry + "(" + this.index + ":" + this.name + ":" + this.type + ")"; - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java b/src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java new file mode 100644 index 0000000..62dbcf3 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping; + +import cuchaz.enigma.mapping.entry.LocalVariableEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; + +public class LocalVariableMapping implements Comparable { + + private int index; + private String name; + + // NOTE: this argument order is important for the MethodReader/MethodWriter + public LocalVariableMapping(int index, String name) { + this.index = index; + this.name = NameValidator.validateArgumentName(name); + } + + public LocalVariableMapping(LocalVariableMapping other) { + this.index = other.index; + this.name = other.name; + } + + public int getIndex() { + return this.index; + } + + public String getName() { + return this.name; + } + + public void setName(String val) { + this.name = NameValidator.validateArgumentName(val); + } + + public LocalVariableEntry getObfEntry(MethodEntry methodEntry) { + return new LocalVariableEntry(methodEntry, index, name); + } + + @Override + public int compareTo(LocalVariableMapping other) { + return Integer.compare(this.index, other.index); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/Mappings.java b/src/main/java/cuchaz/enigma/mapping/Mappings.java index cf78ca3..3ef1be5 100644 --- a/src/main/java/cuchaz/enigma/mapping/Mappings.java +++ b/src/main/java/cuchaz/enigma/mapping/Mappings.java @@ -15,11 +15,15 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import cuchaz.enigma.analysis.TranslationIndex; +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; import cuchaz.enigma.throwables.MappingConflict; import java.io.File; import java.io.IOException; import java.util.*; +import java.util.stream.Collectors; public class Mappings { @@ -96,11 +100,11 @@ public class Mappings { public Translator getTranslator(TranslationDirection direction, TranslationIndex index) { switch (direction) { - case Deobfuscating: + case DEOBFUSCATING: - return new Translator(direction, this.classesByObf, index); + return new DirectionalTranslator(direction, this.classesByObf, index); - case Obfuscating: + case OBFUSCATING: // fill in the missing deobf class entries with obf entries Map classes = Maps.newHashMap(); @@ -114,9 +118,9 @@ public class Mappings { // translate the translation index // NOTE: this isn't actually recursive - TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.Deobfuscating, index)); + TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.DEOBFUSCATING, index)); - return new Translator(direction, classes, deobfIndex); + return new DirectionalTranslator(direction, classes, deobfIndex); default: throw new Error("Invalid translation direction!"); @@ -151,9 +155,9 @@ public class Mappings { // add classes from method signatures for (MethodMapping methodMapping : classMapping.methods()) { - for (Type type : methodMapping.getObfSignature().types()) { - if (type.hasClass()) { - classNames.add(type.getClassEntry().getClassName()); + for (TypeDescriptor desc : methodMapping.getObfDesc().types()) { + if (desc.containsType()) { + classNames.add(desc.getTypeEntry().getClassName()); } } } @@ -165,9 +169,9 @@ public class Mappings { return this.classesByDeobf.containsKey(deobfName); } - public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName, Type obfType) { + public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName, TypeDescriptor obfDesc) { ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName()); - return classMapping != null && classMapping.containsDeobfField(deobfName, obfType); + return classMapping != null && classMapping.containsDeobfField(deobfName, obfDesc); } public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName) { @@ -180,14 +184,14 @@ public class Mappings { return false; } - public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, Signature obfSignature) { + public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, MethodDescriptor obfDescriptor) { ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName()); - return classMapping != null && classMapping.containsDeobfMethod(deobfName, obfSignature); + return classMapping != null && classMapping.containsDeobfMethod(deobfName, obfDescriptor); } - public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) { - ClassMapping classMapping = this.classesByObf.get(obfBehaviorEntry.getClassName()); - return classMapping != null && classMapping.containsArgument(obfBehaviorEntry, name); + public boolean containsArgument(MethodEntry obfMethodEntry, String name) { + ClassMapping classMapping = this.classesByObf.get(obfMethodEntry.getClassName()); + return classMapping != null && classMapping.containsArgument(obfMethodEntry, name); } public List getClassMappingChain(ClassEntry obfClass) { @@ -210,8 +214,14 @@ public class Mappings { public void savePreviousState() { this.previousState = new Mappings(this.originMapping); - this.previousState.classesByDeobf = Maps.newHashMap(this.classesByDeobf); - this.previousState.classesByObf = Maps.newHashMap(this.classesByObf); + this.previousState.classesByDeobf = new HashMap<>(); + for (Map.Entry entry : this.classesByDeobf.entrySet()) { + this.previousState.classesByDeobf.put(entry.getKey(), entry.getValue().copy()); + } + this.previousState.classesByObf = new HashMap<>(); + for (Map.Entry entry : this.classesByObf.entrySet()) { + this.previousState.classesByObf.put(entry.getKey(), entry.getValue().copy()); + } classesByDeobf.values().forEach(ClassMapping::resetDirty); classesByObf.values().forEach(ClassMapping::resetDirty); } @@ -239,5 +249,19 @@ public class Mappings { public String getFormattedName() { return " ACC:" + super.toString(); } + + public AccessFlags transform(AccessFlags access) { + switch (this) { + case PUBLIC: + return access.setPublic(); + case PROTECTED: + return access.setProtected(); + case PRIVATE: + return access.setPrivate(); + case UNCHANGED: + default: + return access; + } + } } } diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java b/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java index 172641b..a42f255 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java @@ -14,6 +14,10 @@ package cuchaz.enigma.mapping; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.EntryFactory; +import cuchaz.enigma.mapping.entry.FieldEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; import java.util.Map; @@ -23,7 +27,7 @@ public class MappingsChecker { private Map droppedClassMappings; private Map droppedInnerClassMappings; private Map droppedFieldMappings; - private Map droppedMethodMappings; + private Map droppedMethodMappings; public MappingsChecker(JarIndex index) { this.index = index; @@ -45,7 +49,7 @@ public class MappingsChecker { return this.droppedFieldMappings; } - public Map getDroppedMethodMappings() { + public Map getDroppedMethodMappings() { return this.droppedMethodMappings; } @@ -77,10 +81,10 @@ public class MappingsChecker { // check methods for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { - BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping); - if (!this.index.containsObfBehavior(obfBehaviorEntry)) { + MethodEntry obfMethodEntry = EntryFactory.getObfMethodEntry(classEntry, methodMapping); + if (!this.index.containsObfMethod(obfMethodEntry)) { classMapping.removeMethodMapping(methodMapping); - this.droppedMethodMappings.put(obfBehaviorEntry, methodMapping); + this.droppedMethodMappings.put(obfMethodEntry, methodMapping); } } diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java b/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java index a0d4313..ddbee76 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java @@ -5,8 +5,14 @@ import com.google.common.collect.Queues; import cuchaz.enigma.throwables.MappingConflict; import cuchaz.enigma.throwables.MappingParseException; -import java.io.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.util.Deque; +import java.util.function.Supplier; public class MappingsEnigmaReader { @@ -39,92 +45,95 @@ public class MappingsEnigmaReader { } public Mappings readFile(Mappings mappings, File file) throws IOException, MappingParseException { + return readFileStream(mappings, new FileInputStream(file), file::getAbsolutePath); + } - BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charsets.UTF_8)); - Deque mappingStack = Queues.newArrayDeque(); + public Mappings readFileStream(Mappings mappings, InputStream stream, Supplier filenameSupplier) throws IOException, MappingParseException { + try (BufferedReader in = new BufferedReader(new InputStreamReader(stream, Charsets.UTF_8))) { + Deque mappingStack = Queues.newArrayDeque(); - int lineNumber = 0; - String line; - while ((line = in.readLine()) != null) { - lineNumber++; + int lineNumber = 0; + String line; + while ((line = in.readLine()) != null) { + lineNumber++; - // strip comments - int commentPos = line.indexOf('#'); - if (commentPos >= 0) { - line = line.substring(0, commentPos); - } + // strip comments + int commentPos = line.indexOf('#'); + if (commentPos >= 0) { + line = line.substring(0, commentPos); + } - // skip blank lines - if (line.trim().length() <= 0) { - continue; - } + // skip blank lines + if (line.trim().length() <= 0) { + continue; + } - // get the indent of this line - int indent = 0; - for (int i = 0; i < line.length(); i++) { - if (line.charAt(i) != '\t') { - break; + // get the indent of this line + int indent = 0; + for (int i = 0; i < line.length(); i++) { + if (line.charAt(i) != '\t') { + break; + } + indent++; } - indent++; - } - // handle stack pops - while (indent < mappingStack.size()) { - mappingStack.pop(); - } + // handle stack pops + while (indent < mappingStack.size()) { + mappingStack.pop(); + } - String[] parts = line.trim().split("\\s"); - try { - // read the first token - String token = parts[0]; - - if (token.equalsIgnoreCase("CLASS")) { - ClassMapping classMapping; - if (indent <= 0) { - // outer class - classMapping = readClass(parts, false); - mappings.addClassMapping(classMapping); - } else { - - // inner class - if (!(mappingStack.peek() instanceof ClassMapping)) { - throw new MappingParseException(file, lineNumber, "Unexpected CLASS entry here!"); + String[] parts = line.trim().split("\\s"); + try { + // read the first token + String token = parts[0]; + + if (token.equalsIgnoreCase("CLASS")) { + ClassMapping classMapping; + if (indent <= 0) { + // outer class + classMapping = readClass(parts, false); + mappings.addClassMapping(classMapping); + } else { + + // inner class + if (!(mappingStack.peek() instanceof ClassMapping)) { + throw new MappingParseException(filenameSupplier, lineNumber, "Unexpected CLASS entry here!"); + } + + classMapping = readClass(parts, true); + ((ClassMapping) mappingStack.peek()).addInnerClassMapping(classMapping); } - - classMapping = readClass(parts, true); - ((ClassMapping) mappingStack.peek()).addInnerClassMapping(classMapping); - } - mappingStack.push(classMapping); - } else if (token.equalsIgnoreCase("FIELD")) { - if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) { - throw new MappingParseException(file, lineNumber, "Unexpected FIELD entry here!"); - } - ((ClassMapping) mappingStack.peek()).addFieldMapping(readField(parts)); - } else if (token.equalsIgnoreCase("METHOD")) { - if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) { - throw new MappingParseException(file, lineNumber, "Unexpected METHOD entry here!"); - } - MethodMapping methodMapping = readMethod(parts); - ((ClassMapping) mappingStack.peek()).addMethodMapping(methodMapping); - mappingStack.push(methodMapping); - } else if (token.equalsIgnoreCase("ARG")) { - if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof MethodMapping)) { - throw new MappingParseException(file, lineNumber, "Unexpected ARG entry here!"); + mappingStack.push(classMapping); + } else if (token.equalsIgnoreCase("FIELD")) { + if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) { + throw new MappingParseException(filenameSupplier, lineNumber, "Unexpected FIELD entry here!"); + } + ((ClassMapping) mappingStack.peek()).addFieldMapping(readField(parts)); + } else if (token.equalsIgnoreCase("METHOD")) { + if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) { + throw new MappingParseException(filenameSupplier, lineNumber, "Unexpected METHOD entry here!"); + } + MethodMapping methodMapping = readMethod(parts); + ((ClassMapping) mappingStack.peek()).addMethodMapping(methodMapping); + mappingStack.push(methodMapping); + } else if (token.equalsIgnoreCase("ARG")) { + if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof MethodMapping)) { + throw new MappingParseException(filenameSupplier, lineNumber, "Unexpected ARG entry here!"); + } + ((MethodMapping) mappingStack.peek()).addArgumentMapping(readArgument(parts)); } - ((MethodMapping) mappingStack.peek()).addArgumentMapping(readArgument(parts)); + } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) { + throw new MappingParseException(filenameSupplier, lineNumber, "Malformed line:\n" + line); + } catch (MappingConflict e) { + throw new MappingParseException(filenameSupplier, lineNumber, e.getMessage()); } - } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) { - throw new MappingParseException(file, lineNumber, "Malformed line:\n" + line); - } catch (MappingConflict e) { - throw new MappingParseException(file, lineNumber, e.getMessage()); } + return mappings; } - in.close(); - return mappings; } - private ArgumentMapping readArgument(String[] parts) { - return new ArgumentMapping(Integer.parseInt(parts[1]), parts[2]); + private LocalVariableMapping readArgument(String[] parts) { + return new LocalVariableMapping(Integer.parseInt(parts[1]), parts[2]); } private ClassMapping readClass(String[] parts, boolean makeSimple) { @@ -150,27 +159,27 @@ public class MappingsEnigmaReader { if (parts.length == 4) { boolean access = parts[3].startsWith("ACC:"); if (access) - mapping = new FieldMapping(parts[1], new Type(parts[2]), null, + mapping = new FieldMapping(parts[1], new TypeDescriptor(parts[2]), null, Mappings.EntryModifier.valueOf(parts[3].substring(4))); else - mapping = new FieldMapping(parts[1], new Type(parts[3]), parts[2], Mappings.EntryModifier.UNCHANGED); + mapping = new FieldMapping(parts[1], new TypeDescriptor(parts[3]), parts[2], Mappings.EntryModifier.UNCHANGED); } else if (parts.length == 5) - mapping = new FieldMapping(parts[1], new Type(parts[3]), parts[2], Mappings.EntryModifier.valueOf(parts[4].substring(4))); + mapping = new FieldMapping(parts[1], new TypeDescriptor(parts[3]), parts[2], Mappings.EntryModifier.valueOf(parts[4].substring(4))); return mapping; } private MethodMapping readMethod(String[] parts) { MethodMapping mapping = null; if (parts.length == 3) - mapping = new MethodMapping(parts[1], new Signature(parts[2])); + mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[2])); else if (parts.length == 4) { boolean access = parts[3].startsWith("ACC:"); if (access) - mapping = new MethodMapping(parts[1], new Signature(parts[2]), null, Mappings.EntryModifier.valueOf(parts[3].substring(4))); + mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[2]), null, Mappings.EntryModifier.valueOf(parts[3].substring(4))); else - mapping = new MethodMapping(parts[1], new Signature(parts[3]), parts[2]); + mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[3]), parts[2]); } else if (parts.length == 5) - mapping = new MethodMapping(parts[1], new Signature(parts[3]), parts[2], + mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[3]), parts[2], Mappings.EntryModifier.valueOf(parts[4].substring(4))); return mapping; } diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java b/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java index ba1b258..b29990f 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java @@ -14,9 +14,7 @@ package cuchaz.enigma.mapping; import com.google.common.base.Charsets; import java.io.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; public class MappingsEnigmaWriter { @@ -33,83 +31,67 @@ public class MappingsEnigmaWriter { if (!target.exists() && !target.mkdirs()) throw new IOException("Cannot create mapping directory!"); + Mappings previousState = mappings.getPreviousState(); for (ClassMapping classMapping : sorted(mappings.classes())) { - if (!classMapping.isDirty()) + if (!classMapping.isDirty()) { continue; - this.deletePreviousClassMapping(target, classMapping); - File obFile = new File(target, classMapping.getObfFullName() + ".mapping"); - File result; - if (classMapping.getDeobfName() == null) - result = obFile; - else { - // Make sure that old version of the file doesn't exist - if (obFile.exists()) - obFile.delete(); - result = new File(target, classMapping.getDeobfName() + ".mapping"); } - if (!result.getParentFile().exists()) - result.getParentFile().mkdirs(); + if (previousState != null) { + ClassMapping previousClass = previousState.classesByObf.get(classMapping.getObfFullName()); + File previousFile; + if (previousClass != null) { + previousFile = new File(target, previousClass.getSaveName() + ".mapping"); + } else { + previousFile = new File(target, classMapping.getObfFullName() + ".mapping"); + } + if (previousFile.exists() && !previousFile.delete()) { + System.err.println("Failed to delete old class mapping " + previousFile.getName()); + } + } + + File result = new File(target, classMapping.getSaveName() + ".mapping"); + + File packageFile = result.getParentFile(); + if (!packageFile.exists()) { + packageFile.mkdirs(); + } result.createNewFile(); - PrintWriter outputWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(result), Charsets.UTF_8)); - write(outputWriter, classMapping, 0); - outputWriter.close(); + + try (PrintWriter outputWriter = new PrintWriter(new BufferedWriter(new FileWriter(result)))) { + write(outputWriter, classMapping, 0); + } } // Remove dropped mappings - if (mappings.getPreviousState() != null) { - List droppedClassMappings = new ArrayList<>(mappings.getPreviousState().classes()); - List classMappings = new ArrayList<>(mappings.classes()); - droppedClassMappings.removeAll(classMappings); - for (ClassMapping classMapping : droppedClassMappings) { - File obFile = new File(target, classMapping.getObfFullName() + ".mapping"); - File result; - if (classMapping.getDeobfName() == null) - result = obFile; - else { - // Make sure that old version of the file doesn't exist - if (obFile.exists()) - obFile.delete(); - result = new File(target, classMapping.getDeobfName() + ".mapping"); + if (previousState != null) { + Set droppedClassMappings = new HashSet<>(previousState.classes()); + droppedClassMappings.removeAll(mappings.classes()); + for (ClassMapping droppedMapping : droppedClassMappings) { + File result = new File(target, droppedMapping.getSaveName() + ".mapping"); + if (!result.exists()) { + continue; + } + if (!result.delete()) { + System.err.println("Failed to delete dropped class mapping " + result.getName()); } - if (result.exists()) - result.delete(); } } } - private void deletePreviousClassMapping(File target, ClassMapping classMapping) { - File prevFile = null; - // Deob rename - if (classMapping.getDeobfName() != null && classMapping.getPreviousDeobfName() != null && !classMapping.getPreviousDeobfName().equals(classMapping.getDeobfName())) { - prevFile = new File(target, classMapping.getPreviousDeobfName() + ".mapping"); - } - // Deob to ob rename - else if (classMapping.getDeobfName() == null && classMapping.getPreviousDeobfName() != null) { - prevFile = new File(target, classMapping.getPreviousDeobfName() + ".mapping"); - } - // Ob to Deob rename - else if (classMapping.getDeobfName() != null && classMapping.getPreviousDeobfName() == null) { - prevFile = new File(target, classMapping.getObfFullName() + ".mapping"); - } - - if (prevFile != null && prevFile.exists()) - prevFile.delete(); - } - public void write(PrintWriter out, Mappings mappings) throws IOException { for (ClassMapping classMapping : sorted(mappings.classes())) { write(out, classMapping, 0); } } - private void write(PrintWriter out, ClassMapping classMapping, int depth) throws IOException { + protected void write(PrintWriter out, ClassMapping classMapping, int depth) throws IOException { if (classMapping.getDeobfName() == null) { out.format("%sCLASS %s%s\n", getIndent(depth), classMapping.getObfFullName(), - classMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : classMapping.getModifier().getFormattedName()); + classMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : classMapping.getModifier().getFormattedName()); } else { out.format("%sCLASS %s %s%s\n", getIndent(depth), classMapping.getObfFullName(), classMapping.getDeobfName(), - classMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : classMapping.getModifier().getFormattedName()); + classMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : classMapping.getModifier().getFormattedName()); } for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) { @@ -127,32 +109,32 @@ public class MappingsEnigmaWriter { private void write(PrintWriter out, FieldMapping fieldMapping, int depth) { if (fieldMapping.getDeobfName() == null) - out.format("%sFIELD %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getObfType().toString(), - fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName()); + out.format("%sFIELD %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getObfDesc().toString(), + fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName()); else - out.format("%sFIELD %s %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName(), fieldMapping.getObfType().toString(), - fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName()); + out.format("%sFIELD %s %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName(), fieldMapping.getObfDesc().toString(), + fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName()); } private void write(PrintWriter out, MethodMapping methodMapping, int depth) throws IOException { - if (methodMapping.getDeobfName() == null) { - out.format("%sMETHOD %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfSignature(), - methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName()); + if (methodMapping.isObfuscated()) { + out.format("%sMETHOD %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfDesc(), + methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName()); } else { - out.format("%sMETHOD %s %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfSignature(), - methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName()); + out.format("%sMETHOD %s %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfDesc(), + methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName()); } - for (ArgumentMapping argumentMapping : sorted(methodMapping.arguments())) { - write(out, argumentMapping, depth + 1); + for (LocalVariableMapping localVariableMapping : sorted(methodMapping.arguments())) { + write(out, localVariableMapping, depth + 1); } } - private void write(PrintWriter out, ArgumentMapping argumentMapping, int depth) { - out.format("%sARG %d %s\n", getIndent(depth), argumentMapping.getIndex(), argumentMapping.getName()); + private void write(PrintWriter out, LocalVariableMapping localVariableMapping, int depth) { + out.format("%sARG %d %s\n", getIndent(depth), localVariableMapping.getIndex(), localVariableMapping.getName()); } - private > List sorted(Iterable classes) { + protected > List sorted(Iterable classes) { List out = new ArrayList<>(); for (T t : classes) { out.add(t); diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java b/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java index 7126d2b..85b6d2a 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java @@ -13,6 +13,7 @@ package cuchaz.enigma.mapping; import com.google.common.collect.Lists; import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.mapping.entry.*; import cuchaz.enigma.throwables.IllegalNameException; import cuchaz.enigma.throwables.MappingConflict; @@ -25,12 +26,14 @@ import java.util.zip.GZIPOutputStream; public class MappingsRenamer { - private JarIndex index; + private final JarIndex index; + private final ReferencedEntryPool entryPool; private Mappings mappings; - public MappingsRenamer(JarIndex index, Mappings mappings) { + public MappingsRenamer(JarIndex index, Mappings mappings, ReferencedEntryPool entryPool) { this.index = index; this.mappings = mappings; + this.entryPool = entryPool; } public void setMappings(Mappings mappings) { @@ -46,7 +49,7 @@ public class MappingsRenamer { if (deobfName != null) { // make sure we don't rename to an existing obf or deobf class - if (mappings.containsDeobfClass(deobfName) || index.containsObfClass(new ClassEntry(deobfName))) { + if (mappings.containsDeobfClass(deobfName) || index.containsObfClass(entryPool.getClass(deobfName))) { throw new IllegalNameException(deobfName, "There is already a class with that name"); } } @@ -87,13 +90,13 @@ public class MappingsRenamer { public void setFieldName(FieldEntry obf, String deobfName) { deobfName = NameValidator.validateFieldName(deobfName); - FieldEntry targetEntry = new FieldEntry(obf.getClassEntry(), deobfName, obf.getType()); + FieldEntry targetEntry = entryPool.getField(obf.getOwnerClassEntry(), deobfName, obf.getDesc()); ClassEntry definedClass = null; - if (mappings.containsDeobfField(obf.getClassEntry(), deobfName) || index.containsEntryWithSameName(targetEntry)) - definedClass = obf.getClassEntry(); + if (mappings.containsDeobfField(obf.getOwnerClassEntry(), deobfName) || index.containsEntryWithSameName(targetEntry)) + definedClass = obf.getOwnerClassEntry(); else { - for (ClassEntry ancestorEntry : this.index.getTranslationIndex().getAncestry(obf.getClassEntry())) { - if (mappings.containsDeobfField(ancestorEntry, deobfName) || index.containsEntryWithSameName(targetEntry.cloneToNewClass(ancestorEntry))) { + for (ClassEntry ancestorEntry : this.index.getTranslationIndex().getAncestry(obf.getOwnerClassEntry())) { + if (mappings.containsDeobfField(ancestorEntry, deobfName) || index.containsEntryWithSameName(targetEntry.updateOwnership(ancestorEntry))) { definedClass = ancestorEntry; break; } @@ -101,42 +104,44 @@ public class MappingsRenamer { } if (definedClass != null) { - String className = mappings.getTranslator(TranslationDirection.Deobfuscating, index.getTranslationIndex()).translateClass(definedClass.getClassName()); + Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex()); + String className = translator.getTranslatedClass(entryPool.getClass(definedClass.getClassName())).getName(); if (className == null) className = definedClass.getClassName(); throw new IllegalNameException(deobfName, "There is already a field with that name in " + className); } - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setFieldName(obf.getName(), obf.getType(), deobfName); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.setFieldName(obf.getName(), obf.getDesc(), deobfName); } public void removeFieldMapping(FieldEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getType())); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getDesc())); } public void markFieldAsDeobfuscated(FieldEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setFieldName(obf.getName(), obf.getType(), obf.getName()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.setFieldName(obf.getName(), obf.getDesc(), obf.getName()); } private void validateMethodTreeName(MethodEntry entry, String deobfName) { - MethodEntry targetEntry = new MethodEntry(entry.getClassEntry(), deobfName, entry.getSignature()); + MethodEntry targetEntry = entryPool.getMethod(entry.getOwnerClassEntry(), deobfName, entry.getDesc()); // TODO: Verify if I don't break things - ClassMapping classMapping = mappings.getClassByObf(entry.getClassEntry()); - if ((classMapping != null && classMapping.containsDeobfMethod(deobfName, entry.getSignature()) && classMapping.getMethodByObf(entry.getName(), entry.getSignature()) != classMapping.getMethodByDeobf(deobfName, entry.getSignature())) - || index.containsObfBehavior(targetEntry)) { - String deobfClassName = mappings.getTranslator(TranslationDirection.Deobfuscating, index.getTranslationIndex()).translateClass(entry.getClassName()); + ClassMapping classMapping = mappings.getClassByObf(entry.getOwnerClassEntry()); + if ((classMapping != null && classMapping.containsDeobfMethod(deobfName, entry.getDesc()) && classMapping.getMethodByObf(entry.getName(), entry.getDesc()) != classMapping.getMethodByDeobf(deobfName, entry.getDesc())) + || index.containsObfMethod(targetEntry)) { + Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex()); + String deobfClassName = translator.getTranslatedClass(entryPool.getClass(entry.getClassName())).getClassName(); if (deobfClassName == null) { deobfClassName = entry.getClassName(); } throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); } - for (ClassEntry child : index.getTranslationIndex().getSubclass(entry.getClassEntry())) { - validateMethodTreeName(entry.cloneToNewClass(child), deobfName); + for (ClassEntry child : index.getTranslationIndex().getSubclass(entry.getOwnerClassEntry())) { + validateMethodTreeName(entry.updateOwnership(child), deobfName); } } @@ -155,20 +160,21 @@ public class MappingsRenamer { public void setMethodName(MethodEntry obf, String deobfName) { deobfName = NameValidator.validateMethodName(deobfName); - MethodEntry targetEntry = new MethodEntry(obf.getClassEntry(), deobfName, obf.getSignature()); - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); + MethodEntry targetEntry = entryPool.getMethod(obf.getOwnerClassEntry(), deobfName, obf.getDesc()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); // TODO: Verify if I don't break things - if ((mappings.containsDeobfMethod(obf.getClassEntry(), deobfName, obf.getSignature()) && classMapping.getMethodByObf(obf.getName(), obf.getSignature()) != classMapping.getMethodByDeobf(deobfName, obf.getSignature())) - || index.containsObfBehavior(targetEntry)) { - String deobfClassName = mappings.getTranslator(TranslationDirection.Deobfuscating, index.getTranslationIndex()).translateClass(obf.getClassName()); + if ((mappings.containsDeobfMethod(obf.getOwnerClassEntry(), deobfName, obf.getDesc()) && classMapping.getMethodByObf(obf.getName(), obf.getDesc()) != classMapping.getMethodByDeobf(deobfName, obf.getDesc())) + || index.containsObfMethod(targetEntry)) { + Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex()); + String deobfClassName = translator.getTranslatedClass(entryPool.getClass(obf.getClassName())).getClassName(); if (deobfClassName == null) { deobfClassName = obf.getClassName(); } throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); } - classMapping.setMethodName(obf.getName(), obf.getSignature(), deobfName); + classMapping.setMethodName(obf.getName(), obf.getDesc(), deobfName); } public void removeMethodTreeMapping(MethodEntry obf) { @@ -176,8 +182,8 @@ public class MappingsRenamer { } public void removeMethodMapping(MethodEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setMethodName(obf.getName(), obf.getSignature(), null); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.setMethodName(obf.getName(), obf.getDesc(), null); } public void markMethodTreeAsDeobfuscated(MethodEntry obf) { @@ -185,30 +191,25 @@ public class MappingsRenamer { } public void markMethodAsDeobfuscated(MethodEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setMethodName(obf.getName(), obf.getSignature(), obf.getName()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.setMethodName(obf.getName(), obf.getDesc(), obf.getName()); } - public void setArgumentTreeName(ArgumentEntry obf, String deobfName) { - if (!(obf.getBehaviorEntry() instanceof MethodEntry)) { - setArgumentName(obf, deobfName); - return; - } - - MethodEntry obfMethod = (MethodEntry) obf.getBehaviorEntry(); + public void setLocalVariableTreeName(LocalVariableEntry obf, String deobfName) { + MethodEntry obfMethod = obf.getOwnerEntry(); Set implementations = index.getRelatedMethodImplementations(obfMethod); for (MethodEntry entry : implementations) { - ClassMapping classMapping = mappings.getClassByObf(entry.getClassEntry()); + ClassMapping classMapping = mappings.getClassByObf(entry.getOwnerClassEntry()); if (classMapping != null) { - MethodMapping mapping = classMapping.getMethodByObf(entry.getName(), entry.getSignature()); + MethodMapping mapping = classMapping.getMethodByObf(entry.getName(), entry.getDesc()); // NOTE: don't need to check arguments for name collisions with names determined by Procyon // TODO: Verify if I don't break things if (mapping != null) { - for (ArgumentMapping argumentMapping : Lists.newArrayList(mapping.arguments())) { - if (argumentMapping.getIndex() != obf.getIndex()) { - if (mapping.getDeobfArgumentName(argumentMapping.getIndex()).equals(deobfName) - || argumentMapping.getName().equals(deobfName)) { + for (LocalVariableMapping localVariableMapping : Lists.newArrayList(mapping.arguments())) { + if (localVariableMapping.getIndex() != obf.getIndex()) { + if (mapping.getDeobfLocalVariableName(localVariableMapping.getIndex()).equals(deobfName) + || localVariableMapping.getName().equals(deobfName)) { throw new IllegalNameException(deobfName, "There is already an argument with that name"); } } @@ -218,45 +219,45 @@ public class MappingsRenamer { } for (MethodEntry entry : implementations) { - setArgumentName(new ArgumentEntry(obf, entry), deobfName); + setLocalVariableName(new LocalVariableEntry(entry, obf.getIndex(), obf.getName()), deobfName); } } - public void setArgumentName(ArgumentEntry obf, String deobfName) { + public void setLocalVariableName(LocalVariableEntry obf, String deobfName) { deobfName = NameValidator.validateArgumentName(deobfName); - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - MethodMapping mapping = classMapping.getMethodByObf(obf.getMethodName(), obf.getMethodSignature()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + MethodMapping mapping = classMapping.getMethodByObf(obf.getMethodName(), obf.getMethodDesc()); // NOTE: don't need to check arguments for name collisions with names determined by Procyon // TODO: Verify if I don't break things if (mapping != null) { - for (ArgumentMapping argumentMapping : Lists.newArrayList(mapping.arguments())) { - if (argumentMapping.getIndex() != obf.getIndex()) { - if (mapping.getDeobfArgumentName(argumentMapping.getIndex()).equals(deobfName) - || argumentMapping.getName().equals(deobfName)) { + for (LocalVariableMapping localVariableMapping : Lists.newArrayList(mapping.arguments())) { + if (localVariableMapping.getIndex() != obf.getIndex()) { + if (mapping.getDeobfLocalVariableName(localVariableMapping.getIndex()).equals(deobfName) + || localVariableMapping.getName().equals(deobfName)) { throw new IllegalNameException(deobfName, "There is already an argument with that name"); } } } } - classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), deobfName); + classMapping.setArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex(), deobfName); } - public void removeArgumentMapping(ArgumentEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex()); + public void removeLocalVariableMapping(LocalVariableEntry obf) { + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex()); } - public void markArgumentAsDeobfuscated(ArgumentEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), obf.getName()); + public void markArgumentAsDeobfuscated(LocalVariableEntry obf) { + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.setArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex(), obf.getName()); } public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) { classMapping.removeFieldMapping(fieldMapping); ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass); - if (!targetClassMapping.containsObfField(fieldMapping.getObfName(), fieldMapping.getObfType())) { - if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfType())) { + if (!targetClassMapping.containsObfField(fieldMapping.getObfName(), fieldMapping.getObfDesc())) { + if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfDesc())) { targetClassMapping.addFieldMapping(fieldMapping); return true; } else { @@ -269,12 +270,12 @@ public class MappingsRenamer { public boolean moveMethodToObfClass(ClassMapping classMapping, MethodMapping methodMapping, ClassEntry obfClass) { classMapping.removeMethodMapping(methodMapping); ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass); - if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfSignature())) { - if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfSignature())) { + if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfDesc())) { + if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfDesc())) { targetClassMapping.addMethodMapping(methodMapping); return true; } else { - System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfSignature()); + System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfDesc()); } } return false; @@ -326,12 +327,35 @@ public class MappingsRenamer { } public void setFieldModifier(FieldEntry obEntry, Mappings.EntryModifier modifier) { - ClassMapping classMapping = getOrCreateClassMapping(obEntry.getClassEntry()); - classMapping.setFieldModifier(obEntry.getName(), obEntry.getType(), modifier); + ClassMapping classMapping = getOrCreateClassMapping(obEntry.getOwnerClassEntry()); + classMapping.setFieldModifier(obEntry.getName(), obEntry.getDesc(), modifier); + } + + public void setMethodModifier(MethodEntry obEntry, Mappings.EntryModifier modifier) { + ClassMapping classMapping = getOrCreateClassMapping(obEntry.getOwnerClassEntry()); + classMapping.setMethodModifier(obEntry.getName(), obEntry.getDesc(), modifier); + } + + public Mappings.EntryModifier getClassModifier(ClassEntry obfEntry) { + ClassMapping classMapping = getOrCreateClassMapping(obfEntry); + return classMapping.getModifier(); } - public void setMethodModifier(BehaviorEntry obEntry, Mappings.EntryModifier modifier) { - ClassMapping classMapping = getOrCreateClassMapping(obEntry.getClassEntry()); - classMapping.setMethodModifier(obEntry.getName(), obEntry.getSignature(), modifier); + public Mappings.EntryModifier getFieldModifier(FieldEntry obfEntry) { + ClassMapping classMapping = getOrCreateClassMapping(obfEntry.getOwnerClassEntry()); + FieldMapping fieldMapping = classMapping.getFieldByObf(obfEntry); + if (fieldMapping == null) { + return Mappings.EntryModifier.UNCHANGED; + } + return fieldMapping.getModifier(); + } + + public Mappings.EntryModifier getMethodModfifier(MethodEntry obfEntry) { + ClassMapping classMapping = getOrCreateClassMapping(obfEntry.getOwnerClassEntry()); + MethodMapping methodMapping = classMapping.getMethodByObf(obfEntry); + if (methodMapping == null) { + return Mappings.EntryModifier.UNCHANGED; + } + return methodMapping.getModifier(); } } diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java b/src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java index b0eb826..32f0ee9 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java @@ -2,6 +2,7 @@ package cuchaz.enigma.mapping; import com.google.common.base.Charsets; import cuchaz.enigma.analysis.TranslationIndex; +import cuchaz.enigma.mapping.entry.ReferencedEntryPool; import java.io.*; import java.util.ArrayList; @@ -19,7 +20,7 @@ public class MappingsSRGWriter { } file.createNewFile(); - TranslationIndex index = new TranslationIndex(); + TranslationIndex index = new TranslationIndex(new ReferencedEntryPool()); PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8)); List fieldMappings = new ArrayList<>(); @@ -43,7 +44,7 @@ public class MappingsSRGWriter { } for (MethodMapping methodMapping : sorted(innerClassMapping.methods())) { - methodMappings.add("MD: " + innerClassName + "/" + methodMapping.getObfName() + " " + methodMapping.getObfSignature() + " " + innerDeobfClassName + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.Deobfuscating, index).translateSignature(methodMapping.getObfSignature())); + methodMappings.add("MD: " + innerClassName + "/" + methodMapping.getObfName() + " " + methodMapping.getObfDesc() + " " + innerDeobfClassName + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index).getTranslatedMethodDesc(methodMapping.getObfDesc())); } } @@ -52,7 +53,7 @@ public class MappingsSRGWriter { } for (MethodMapping methodMapping : sorted(classMapping.methods())) { - methodMappings.add("MD: " + classMapping.getObfFullName() + "/" + methodMapping.getObfName() + " " + methodMapping.getObfSignature() + " " + classMapping.getDeobfName() + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.Deobfuscating, index).translateSignature(methodMapping.getObfSignature())); + methodMappings.add("MD: " + classMapping.getObfFullName() + "/" + methodMapping.getObfName() + " " + methodMapping.getObfDesc() + " " + classMapping.getDeobfName() + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index).getTranslatedMethodDesc(methodMapping.getObfDesc())); } } for (String fd : fieldMappings) { diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsTinyReader.java b/src/main/java/cuchaz/enigma/mapping/MappingsTinyReader.java index befc92a..69d5684 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsTinyReader.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsTinyReader.java @@ -2,6 +2,7 @@ package cuchaz.enigma.mapping; import com.google.common.base.Charsets; import com.google.common.collect.Maps; +import cuchaz.enigma.mapping.entry.ClassEntry; import cuchaz.enigma.throwables.MappingConflict; import cuchaz.enigma.throwables.MappingParseException; @@ -20,11 +21,11 @@ public class MappingsTinyReader { } public FieldMapping readField(String[] parts) { - return new FieldMapping(parts[3], new Type(parts[2]), parts[4], Mappings.EntryModifier.UNCHANGED); + return new FieldMapping(parts[3], new TypeDescriptor(parts[2]), parts[4], Mappings.EntryModifier.UNCHANGED); } public MethodMapping readMethod(String[] parts) { - return new MethodMapping(parts[3], new Signature(parts[2]), parts[4]); + return new MethodMapping(parts[3], new MethodDescriptor(parts[2]), parts[4]); } public Mappings read(File file) throws IOException, MappingParseException { @@ -72,7 +73,7 @@ public class MappingsTinyReader { break; case "MTH-ARG": classMapping = classMappingMap.computeIfAbsent(parts[1], k -> new ClassMapping(parts[1])); - classMapping.setArgumentName(parts[3], new Signature(parts[2]), Integer.parseInt(parts[4]), parts[5]); + classMapping.setArgumentName(parts[3], new MethodDescriptor(parts[2]), Integer.parseInt(parts[4]), parts[5]); break; default: throw new MappingParseException(file, lineNumber, "Unknown token '" + token + "' !"); diff --git a/src/main/java/cuchaz/enigma/mapping/MemberMapping.java b/src/main/java/cuchaz/enigma/mapping/MemberMapping.java index d4514d4..6effb91 100644 --- a/src/main/java/cuchaz/enigma/mapping/MemberMapping.java +++ b/src/main/java/cuchaz/enigma/mapping/MemberMapping.java @@ -11,6 +11,9 @@ package cuchaz.enigma.mapping; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.Entry; + public interface MemberMapping { T getObfEntry(ClassEntry classEntry); diff --git a/src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java b/src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java new file mode 100644 index 0000000..0fc0351 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping; + +import com.google.common.collect.Lists; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.utils.Utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public class MethodDescriptor { + + private List argumentDescs; + private TypeDescriptor returnDesc; + + public MethodDescriptor(String desc) { + try { + this.argumentDescs = Lists.newArrayList(); + int i = 0; + while (i < desc.length()) { + char c = desc.charAt(i); + if (c == '(') { + assert (this.argumentDescs.isEmpty()); + assert (this.returnDesc == null); + i++; + } else if (c == ')') { + i++; + break; + } else { + String type = TypeDescriptor.parseFirst(desc.substring(i)); + this.argumentDescs.add(new TypeDescriptor(type)); + i += type.length(); + } + } + this.returnDesc = new TypeDescriptor(TypeDescriptor.parseFirst(desc.substring(i))); + } catch (Exception ex) { + throw new IllegalArgumentException("Unable to parse method descriptor: " + desc, ex); + } + } + + public MethodDescriptor(List argumentDescs, TypeDescriptor returnDesc) { + this.argumentDescs = argumentDescs; + this.returnDesc = returnDesc; + } + + public List getArgumentDescs() { + return this.argumentDescs; + } + + public TypeDescriptor getReturnDesc() { + return this.returnDesc; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("("); + for (TypeDescriptor desc : this.argumentDescs) { + buf.append(desc); + } + buf.append(")"); + buf.append(this.returnDesc); + return buf.toString(); + } + + public Iterable types() { + List descs = Lists.newArrayList(); + descs.addAll(this.argumentDescs); + descs.add(this.returnDesc); + return descs; + } + + @Override + public boolean equals(Object other) { + return other instanceof MethodDescriptor && equals((MethodDescriptor) other); + } + + public boolean equals(MethodDescriptor other) { + return this.argumentDescs.equals(other.argumentDescs) && this.returnDesc.equals(other.returnDesc); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.argumentDescs.hashCode(), this.returnDesc.hashCode()); + } + + public boolean hasClass(ClassEntry classEntry) { + for (TypeDescriptor desc : types()) { + if (desc.containsType() && desc.getTypeEntry().equals(classEntry)) { + return true; + } + } + return false; + } + + public MethodDescriptor remap(Function remapper) { + List argumentDescs = new ArrayList<>(this.argumentDescs.size()); + for (TypeDescriptor desc : this.argumentDescs) { + argumentDescs.add(desc.remap(remapper)); + } + return new MethodDescriptor(argumentDescs, returnDesc.remap(remapper)); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/MethodEntry.java b/src/main/java/cuchaz/enigma/mapping/MethodEntry.java deleted file mode 100644 index 9c3058c..0000000 --- a/src/main/java/cuchaz/enigma/mapping/MethodEntry.java +++ /dev/null @@ -1,90 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -import cuchaz.enigma.utils.Utils; - -public class MethodEntry implements BehaviorEntry { - - private ClassEntry classEntry; - private String name; - private Signature signature; - - public MethodEntry(ClassEntry classEntry, String name, Signature signature) { - if (classEntry == null) { - throw new IllegalArgumentException("Class cannot be null!"); - } - if (name == null) { - throw new IllegalArgumentException("Method name cannot be null!"); - } - if (signature == null) { - throw new IllegalArgumentException("Method signature cannot be null!"); - } - if (name.startsWith("<")) { - throw new IllegalArgumentException("Don't use MethodEntry for a constructor!"); - } - - this.classEntry = classEntry; - this.name = name; - this.signature = signature; - } - - public MethodEntry(MethodEntry other, String newClassName) { - this.classEntry = new ClassEntry(newClassName); - this.name = other.name; - this.signature = other.signature; - } - - @Override - public ClassEntry getClassEntry() { - return this.classEntry; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public Signature getSignature() { - return this.signature; - } - - @Override - public String getClassName() { - return this.classEntry.getName(); - } - - @Override - public MethodEntry cloneToNewClass(ClassEntry classEntry) { - return new MethodEntry(this, classEntry.getName()); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.classEntry, this.name, this.signature); - } - - @Override - public boolean equals(Object other) { - return other instanceof MethodEntry && equals((MethodEntry) other); - } - - public boolean equals(MethodEntry other) { - return this.classEntry.equals(other.classEntry) && this.name.equals(other.name) && this.signature.equals(other.signature); - } - - @Override - public String toString() { - return this.classEntry.getName() + "." + this.name + this.signature; - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/MethodMapping.java b/src/main/java/cuchaz/enigma/mapping/MethodMapping.java index 1524ce6..2f10144 100644 --- a/src/main/java/cuchaz/enigma/mapping/MethodMapping.java +++ b/src/main/java/cuchaz/enigma/mapping/MethodMapping.java @@ -11,50 +11,49 @@ package cuchaz.enigma.mapping; +import com.google.common.base.Preconditions; import com.google.common.collect.Maps; +import cuchaz.enigma.mapping.entry.ClassEntry; +import cuchaz.enigma.mapping.entry.MethodEntry; import cuchaz.enigma.throwables.IllegalNameException; import cuchaz.enigma.throwables.MappingConflict; import java.util.Map; -public class MethodMapping implements Comparable, MemberMapping { +public class MethodMapping implements Comparable, MemberMapping { private String obfName; private String deobfName; - private Signature obfSignature; - private Map arguments; + private MethodDescriptor obfDescriptor; + private Map localVariables; private Mappings.EntryModifier modifier; - public MethodMapping(String obfName, Signature obfSignature) { - this(obfName, obfSignature, null, Mappings.EntryModifier.UNCHANGED); + public MethodMapping(String obfName, MethodDescriptor obfDescriptor) { + this(obfName, obfDescriptor, null, Mappings.EntryModifier.UNCHANGED); } - public MethodMapping(String obfName, Signature obfSignature, String deobfName) { - this(obfName, obfSignature, deobfName, Mappings.EntryModifier.UNCHANGED); + public MethodMapping(String obfName, MethodDescriptor obfDescriptor, String deobfName) { + this(obfName, obfDescriptor, deobfName, Mappings.EntryModifier.UNCHANGED); } - public MethodMapping(String obfName, Signature obfSignature, String deobfName, Mappings.EntryModifier modifier) { - if (obfName == null) { - throw new IllegalArgumentException("obf name cannot be null!"); - } - if (obfSignature == null) { - throw new IllegalArgumentException("obf signature cannot be null!"); - } + public MethodMapping(String obfName, MethodDescriptor obfDescriptor, String deobfName, Mappings.EntryModifier modifier) { + Preconditions.checkNotNull(obfName, "Method obf name cannot be null"); + Preconditions.checkNotNull(obfDescriptor, "Method obf desc cannot be null"); this.obfName = obfName; this.deobfName = NameValidator.validateMethodName(deobfName); - this.obfSignature = obfSignature; - this.arguments = Maps.newTreeMap(); + this.obfDescriptor = obfDescriptor; + this.localVariables = Maps.newTreeMap(); this.modifier = modifier; } - public MethodMapping(MethodMapping other, ClassNameReplacer obfClassNameReplacer) { + public MethodMapping(MethodMapping other, Translator translator) { this.obfName = other.obfName; this.deobfName = other.deobfName; this.modifier = other.modifier; - this.obfSignature = new Signature(other.obfSignature, obfClassNameReplacer); - this.arguments = Maps.newTreeMap(); - for (Map.Entry entry : other.arguments.entrySet()) { - this.arguments.put(entry.getKey(), new ArgumentMapping(entry.getValue())); + this.obfDescriptor = translator.getTranslatedMethodDesc(other.obfDescriptor); + this.localVariables = Maps.newTreeMap(); + for (Map.Entry entry : other.localVariables.entrySet()) { + this.localVariables.put(entry.getKey(), new LocalVariableMapping(entry.getValue())); } } @@ -77,6 +76,9 @@ public class MethodMapping implements Comparable, MemberMapping, MemberMapping arguments() { - return this.arguments.values(); + public Iterable arguments() { + return this.localVariables.values(); } - public void addArgumentMapping(ArgumentMapping argumentMapping) throws MappingConflict { - if (this.arguments.containsKey(argumentMapping.getIndex())) { - throw new MappingConflict("argument", argumentMapping.getName(), this.arguments.get(argumentMapping.getIndex()).getName()); + public void addArgumentMapping(LocalVariableMapping localVariableMapping) throws MappingConflict { + if (this.localVariables.containsKey(localVariableMapping.getIndex())) { + throw new MappingConflict("argument", localVariableMapping.getName(), this.localVariables.get(localVariableMapping.getIndex()).getName()); } - this.arguments.put(argumentMapping.getIndex(), argumentMapping); + this.localVariables.put(localVariableMapping.getIndex(), localVariableMapping); } - public String getObfArgumentName(int index) { - ArgumentMapping argumentMapping = this.arguments.get(index); - if (argumentMapping != null) { - return argumentMapping.getName(); + public String getObfLocalVariableName(int index) { + LocalVariableMapping localVariableMapping = this.localVariables.get(index); + if (localVariableMapping != null) { + return localVariableMapping.getName(); } return null; } - public String getDeobfArgumentName(int index) { - ArgumentMapping argumentMapping = this.arguments.get(index); - if (argumentMapping != null) { - return argumentMapping.getName(); + public String getDeobfLocalVariableName(int index) { + LocalVariableMapping localVariableMapping = this.localVariables.get(index); + if (localVariableMapping != null) { + return localVariableMapping.getName(); } return null; } - public void setArgumentName(int index, String name) { - ArgumentMapping argumentMapping = this.arguments.get(index); - if (argumentMapping == null) { - argumentMapping = new ArgumentMapping(index, name); - boolean wasAdded = this.arguments.put(index, argumentMapping) == null; + public void setLocalVariableName(int index, String name) { + LocalVariableMapping localVariableMapping = this.localVariables.get(index); + if (localVariableMapping == null) { + localVariableMapping = new LocalVariableMapping(index, name); + boolean wasAdded = this.localVariables.put(index, localVariableMapping) == null; assert (wasAdded); } else { - argumentMapping.setName(name); + localVariableMapping.setName(name); } } - public void removeArgumentName(int index) { - boolean wasRemoved = this.arguments.remove(index) != null; + public void removeLocalVariableName(int index) { + boolean wasRemoved = this.localVariables.remove(index) != null; assert (wasRemoved); } @@ -146,14 +148,14 @@ public class MethodMapping implements Comparable, MemberMapping "); - buf.append(argumentMapping.getName()); + buf.append(localVariableMapping.getName()); buf.append("\n"); } return buf.toString(); @@ -161,12 +163,12 @@ public class MethodMapping implements Comparable, MemberMapping, MemberMapping - { + MethodDescriptor newDescriptor = obfDescriptor.remap(className -> { if (className.equals(oldObfClassName)) { return newObfClassName; } - return null; + return className; }); - if (!newSignature.equals(this.obfSignature)) { - this.obfSignature = newSignature; + if (!newDescriptor.equals(this.obfDescriptor)) { + this.obfDescriptor = newDescriptor; return true; } return false; } - public boolean isConstructor() { - return this.obfName.startsWith("<"); - } - @Override - public BehaviorEntry getObfEntry(ClassEntry classEntry) { - if (isConstructor()) { - return new ConstructorEntry(classEntry, this.obfSignature); - } else { - return new MethodEntry(classEntry, this.obfName, this.obfSignature); - } + public MethodEntry getObfEntry(ClassEntry classEntry) { + return new MethodEntry(classEntry, this.obfName, this.obfDescriptor); } public Mappings.EntryModifier getModifier() { @@ -210,4 +203,8 @@ public class MethodMapping implements Comparable, MemberMapping ReservedWords = Arrays.asList( - "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", - "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", - "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", - "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally", - "long", "strictfp", "volatile", "const", "float", "native", "super", "while" + "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", + "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", + "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", + "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally", + "long", "strictfp", "volatile", "const", "float", "native", "super", "while" ); static { @@ -43,10 +43,10 @@ public class NameValidator { if (!ClassPattern.matcher(name).matches() || ReservedWords.contains(name)) { throw new IllegalNameException(name, "This doesn't look like a legal class name"); } - if (packageRequired && new ClassEntry(name).getPackageName() == null) { + if (packageRequired && ClassEntry.getPackageName(name) == null) { throw new IllegalNameException(name, "Class must be in a package"); } - return Descriptor.toJvmName(name); + return name; } public static String validateFieldName(String name) { diff --git a/src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java b/src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java deleted file mode 100644 index 33d930d..0000000 --- a/src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java +++ /dev/null @@ -1,67 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -import com.strobel.assembler.metadata.*; - -import java.util.List; - -public class ProcyonEntryFactory { - - private static String getErasedSignature(MemberReference def) { - if (!(def instanceof MethodReference)) - return def.getErasedSignature(); - MethodReference methodReference = (MethodReference) def; - StringBuilder builder = new StringBuilder("("); - for (ParameterDefinition param : methodReference.getParameters()) { - TypeReference paramType = param.getParameterType(); - if (paramType.getErasedSignature().equals("Ljava/lang/Object;") && paramType.hasExtendsBound() && paramType.getExtendsBound() instanceof CompoundTypeReference) { - List interfaces = ((CompoundTypeReference) paramType.getExtendsBound()).getInterfaces(); - interfaces.forEach((inter) -> builder.append(inter.getErasedSignature())); - } else - builder.append(paramType.getErasedSignature()); - } - builder.append(")"); - - TypeReference returnType = methodReference.getReturnType(); - if (returnType.getErasedSignature().equals("Ljava/lang/Object;") && returnType.hasExtendsBound() && returnType.getExtendsBound() instanceof CompoundTypeReference) { - List interfaces = ((CompoundTypeReference) returnType.getExtendsBound()).getInterfaces(); - interfaces.forEach((inter) -> builder.append(inter.getErasedSignature())); - } else - builder.append(returnType.getErasedSignature()); - return builder.toString(); - } - - public static FieldEntry getFieldEntry(MemberReference def) { - return new FieldEntry(new ClassEntry(def.getDeclaringType().getInternalName()), def.getName(), new Type(def.getErasedSignature())); - } - - public static MethodEntry getMethodEntry(MemberReference def) { - return new MethodEntry(new ClassEntry(def.getDeclaringType().getInternalName()), def.getName(), new Signature(getErasedSignature(def))); - } - - public static ConstructorEntry getConstructorEntry(MethodReference def) { - if (def.isTypeInitializer()) { - return new ConstructorEntry(new ClassEntry(def.getDeclaringType().getInternalName())); - } else { - return new ConstructorEntry(new ClassEntry(def.getDeclaringType().getInternalName()), new Signature(def.getErasedSignature())); - } - } - - public static BehaviorEntry getBehaviorEntry(MethodReference def) { - if (def.isConstructor() || def.isTypeInitializer()) { - return getConstructorEntry(def); - } else { - return getMethodEntry(def); - } - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/Signature.java b/src/main/java/cuchaz/enigma/mapping/Signature.java index 78130d6..071e4af 100644 --- a/src/main/java/cuchaz/enigma/mapping/Signature.java +++ b/src/main/java/cuchaz/enigma/mapping/Signature.java @@ -1,106 +1,82 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - package cuchaz.enigma.mapping; -import com.google.common.collect.Lists; -import cuchaz.enigma.utils.Utils; +import cuchaz.enigma.bytecode.translators.TranslationSignatureVisitor; +import org.objectweb.asm.signature.SignatureReader; +import org.objectweb.asm.signature.SignatureVisitor; +import org.objectweb.asm.signature.SignatureWriter; -import java.util.List; +import java.util.function.Function; +import java.util.regex.Pattern; public class Signature { + private static final Pattern OBJECT_PATTERN = Pattern.compile(".*:Ljava/lang/Object;:.*"); - private List argumentTypes; - private Type returnType; + private final String signature; + private final boolean isType; - public Signature(String signature) { - try { - this.argumentTypes = Lists.newArrayList(); - int i = 0; - while (i < signature.length()) { - char c = signature.charAt(i); - if (c == '(') { - assert (this.argumentTypes.isEmpty()); - assert (this.returnType == null); - i++; - } else if (c == ')') { - i++; - break; - } else { - String type = Type.parseFirst(signature.substring(i)); - this.argumentTypes.add(new Type(type)); - i += type.length(); - } - } - this.returnType = new Type(Type.parseFirst(signature.substring(i))); - } catch (Exception ex) { - throw new IllegalArgumentException("Unable to parse signature: " + signature, ex); + private Signature(String signature, boolean isType) { + if (signature != null && OBJECT_PATTERN.matcher(signature).matches()) { + signature = signature.replaceAll(":Ljava/lang/Object;:", "::"); } + + this.signature = signature; + this.isType = isType; } - public Signature(Signature other, ClassNameReplacer replacer) { - this.argumentTypes = Lists.newArrayList(other.argumentTypes); - for (int i = 0; i < this.argumentTypes.size(); i++) { - this.argumentTypes.set(i, new Type(this.argumentTypes.get(i), replacer)); + public static Signature createTypedSignature(String signature) { + if (signature != null && !signature.isEmpty()) { + return new Signature(signature, true); } - this.returnType = new Type(other.returnType, replacer); + return new Signature(null, true); } - public List getArgumentTypes() { - return this.argumentTypes; + public static Signature createSignature(String signature) { + if (signature != null && !signature.isEmpty()) { + return new Signature(signature, false); + } + return new Signature(null, false); } - public Type getReturnType() { - return this.returnType; + public String getSignature() { + return signature; } - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - buf.append("("); - for (Type type : this.argumentTypes) { - buf.append(type); - } - buf.append(")"); - buf.append(this.returnType); - return buf.toString(); + public boolean isType() { + return isType; } - public Iterable types() { - List types = Lists.newArrayList(); - types.addAll(this.argumentTypes); - types.add(this.returnType); - return types; + public Signature remap(Function remapper) { + if (signature == null) { + return this; + } + SignatureWriter writer = new SignatureWriter(); + SignatureVisitor visitor = new TranslationSignatureVisitor(remapper, writer); + if (isType) { + new SignatureReader(signature).acceptType(visitor); + } else { + new SignatureReader(signature).accept(visitor); + } + return new Signature(writer.toString(), isType); } @Override - public boolean equals(Object other) { - return other instanceof Signature && equals((Signature) other); - } - - public boolean equals(Signature other) { - return this.argumentTypes.equals(other.argumentTypes) && this.returnType.equals(other.returnType); + public boolean equals(Object obj) { + if (obj instanceof Signature) { + Signature other = (Signature) obj; + return (other.signature == null && signature == null || other.signature != null + && signature != null && other.signature.equals(signature)) + && other.isType == this.isType; + } + return false; } @Override public int hashCode() { - return Utils.combineHashesOrdered(this.argumentTypes.hashCode(), this.returnType.hashCode()); + return signature.hashCode() | (isType ? 1 : 0) << 16; } - public boolean hasClass(ClassEntry classEntry) { - for (Type type : types()) { - if (type.hasClass() && type.getClassEntry().equals(classEntry)) { - return true; - } - } - return false; + @Override + public String toString() { + return signature; } } diff --git a/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java b/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java index 17e3187..4bbde54 100644 --- a/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java +++ b/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java @@ -13,15 +13,21 @@ package cuchaz.enigma.mapping; public enum TranslationDirection { - Deobfuscating { + DEOBFUSCATING { @Override public T choose(T deobfChoice, T obfChoice) { + if (deobfChoice == null) { + return obfChoice; + } return deobfChoice; } }, - Obfuscating { + OBFUSCATING { @Override public T choose(T deobfChoice, T obfChoice) { + if (obfChoice == null) { + return deobfChoice; + } return obfChoice; } }; diff --git a/src/main/java/cuchaz/enigma/mapping/Translator.java b/src/main/java/cuchaz/enigma/mapping/Translator.java index 8d464fc..a9ff1cb 100644 --- a/src/main/java/cuchaz/enigma/mapping/Translator.java +++ b/src/main/java/cuchaz/enigma/mapping/Translator.java @@ -11,332 +11,99 @@ package cuchaz.enigma.mapping; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import cuchaz.enigma.analysis.TranslationIndex; +import cuchaz.enigma.mapping.entry.*; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Type; -import java.util.List; -import java.util.Map; +public interface Translator { + ClassEntry getTranslatedClass(ClassEntry entry); -public class Translator { + ClassDefEntry getTranslatedClassDef(ClassDefEntry entry); - private TranslationDirection direction; - private Map classes; - private TranslationIndex index; + FieldEntry getTranslatedField(FieldEntry entry); - private ClassNameReplacer classNameReplacer = className -> translateEntry(new ClassEntry(className)).getName(); + FieldDefEntry getTranslatedFieldDef(FieldDefEntry entry); - public Translator() { - this.direction = null; - this.classes = Maps.newHashMap(); - this.index = new TranslationIndex(); - } + MethodEntry getTranslatedMethod(MethodEntry entry); - public Translator(TranslationDirection direction, Map classes, TranslationIndex index) { - this.direction = direction; - this.classes = classes; - this.index = index; - } + MethodDefEntry getTranslatedMethodDef(MethodDefEntry entry); - public TranslationDirection getDirection() { - return direction; - } + LocalVariableEntry getTranslatedVariable(LocalVariableEntry entry); - public TranslationIndex getTranslationIndex() { - return index; - } + LocalVariableDefEntry getTranslatedVariableDef(LocalVariableDefEntry entry); - @SuppressWarnings("unchecked") - public T translateEntry(T entry) { - if (entry instanceof ClassEntry) { - return (T) translateEntry((ClassEntry) entry); - } else if (entry instanceof FieldEntry) { - return (T) translateEntry((FieldEntry) entry); - } else if (entry instanceof MethodEntry) { - return (T) translateEntry((MethodEntry) entry); - } else if (entry instanceof ConstructorEntry) { - return (T) translateEntry((ConstructorEntry) entry); - } else if (entry instanceof ArgumentEntry) { - return (T) translateEntry((ArgumentEntry) entry); - } else if (entry instanceof LocalVariableEntry) { - return (T) translateEntry((LocalVariableEntry) entry); - } else { - throw new Error("Unknown entry type: " + entry.getClass().getName()); - } - } + boolean hasClassMapping(ClassEntry entry); - public String translate(T entry) { - if (entry instanceof ClassEntry) { - return translate((ClassEntry) entry); - } else if (entry instanceof FieldEntry) { - return translate((FieldEntry) entry); - } else if (entry instanceof MethodEntry) { - return translate((MethodEntry) entry); - } else if (entry instanceof ConstructorEntry) { - return translate(entry); - } else if (entry instanceof ArgumentEntry) { - return translate((ArgumentEntry) entry); - } else if (entry instanceof LocalVariableEntry) { - return translate((LocalVariableEntry) entry); - } else { - throw new Error("Unknown entry type: " + entry.getClass().getName()); - } - } + boolean hasFieldMapping(FieldEntry entry); - public String translate(LocalVariableEntry in) { - LocalVariableEntry translated = translateEntry(in); - if (translated.equals(in)) { - return null; - } - return translated.getName(); - } + boolean hasMethodMapping(MethodEntry entry); - public LocalVariableEntry translateEntry(LocalVariableEntry in) { - // TODO: Implement it - return in; - } + boolean hasLocalVariableMapping(LocalVariableEntry entry); - public String translate(ClassEntry in) { - ClassEntry translated = translateEntry(in); - if (translated.equals(in)) { - return null; - } - return translated.getName(); - } + TypeDescriptor getTranslatedTypeDesc(TypeDescriptor desc); - public String translateClass(String className) { - return translate(new ClassEntry(className)); - } - - public ClassEntry translateEntry(ClassEntry in) { + MethodDescriptor getTranslatedMethodDesc(MethodDescriptor descriptor); - if (in.isInnerClass()) { + Signature getTranslatedSignature(Signature signature); - // translate as much of the class chain as we can - List mappingsChain = getClassMappingChain(in); - String[] obfClassNames = in.getName().split("\\$"); - StringBuilder buf = new StringBuilder(); - for (int i = 0; i < obfClassNames.length; i++) { - boolean isFirstClass = buf.length() == 0; - String className = null; - ClassMapping classMapping = mappingsChain.get(i); - if (classMapping != null) { - className = this.direction.choose( - classMapping.getDeobfName(), - isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName() - ); - } - if (className == null) { - className = obfClassNames[i]; - } - if (!isFirstClass) { - buf.append("$"); - } - buf.append(className); + default Type getTranslatedType(Type type) { + String descString = type.getDescriptor(); + switch (type.getSort()) { + case Type.OBJECT: { + ClassEntry classEntry = new ClassEntry(type.getInternalName()); + return Type.getObjectType(getTranslatedClass(classEntry).getName()); } - return new ClassEntry(buf.toString()); - - } else { - - // normal classes are easy - ClassMapping classMapping = this.classes.get(in.getName()); - if (classMapping == null) { - return in; + case Type.ARRAY: { + TypeDescriptor descriptor = new TypeDescriptor(descString); + return Type.getType(getTranslatedTypeDesc(descriptor).toString()); } - return this.direction.choose( - classMapping.getDeobfName() != null ? new ClassEntry(classMapping.getDeobfName()) : in, - new ClassEntry(classMapping.getObfFullName()) - ); - } - } - - public String translate(FieldEntry in) { - - // resolve the class entry - ClassEntry resolvedClassEntry = this.index.resolveEntryClass(in); - if (resolvedClassEntry != null) { - - // look for the class - ClassMapping classMapping = findClassMapping(resolvedClassEntry); - if (classMapping != null) { - - // look for the field - String translatedName = this.direction.choose( - classMapping.getDeobfFieldName(in.getName(), in.getType()), - classMapping.getObfFieldName(in.getName(), translateType(in.getType())) - ); - if (translatedName != null) { - return translatedName; - } + case Type.METHOD: { + MethodDescriptor descriptor = new MethodDescriptor(descString); + return Type.getMethodType(getTranslatedMethodDesc(descriptor).toString()); } } - return null; + return type; } - public FieldEntry translateEntry(FieldEntry in) { - String name = translate(in); - if (name == null) { - name = in.getName(); - } - return new FieldEntry(translateEntry(in.getClassEntry()), name, translateType(in.getType())); + default Handle getTranslatedHandle(Handle handle) { + MethodEntry entry = new MethodEntry(new ClassEntry(handle.getOwner()), handle.getName(), new MethodDescriptor(handle.getDesc())); + MethodEntry translatedMethod = getTranslatedMethod(entry); + ClassEntry ownerClass = translatedMethod.getOwnerClassEntry(); + return new Handle(handle.getTag(), ownerClass.getName(), translatedMethod.getName(), translatedMethod.getDesc().toString(), handle.isInterface()); } - public String translate(MethodEntry in) { - // resolve the class entry - ClassEntry resolvedClassEntry = this.index.resolveEntryClass(in, true); - if (resolvedClassEntry != null) { - - // look for class - ClassMapping classMapping = findClassMapping(resolvedClassEntry); - if (classMapping != null) { - - // look for the method - MethodMapping methodMapping = this.direction.choose( - classMapping.getMethodByObf(in.getName(), in.getSignature()), - classMapping.getMethodByDeobf(in.getName(), translateSignature(in.getSignature())) - ); - if (methodMapping != null) { - return this.direction.choose(methodMapping.getDeobfName(), methodMapping.getObfName()); - } - } + default Object getTranslatedValue(Object value) { + if (value instanceof Type) { + return this.getTranslatedType((Type) value); + } else if (value instanceof Handle) { + return getTranslatedHandle((Handle) value); } - return null; + return value; } - public MethodEntry translateEntry(MethodEntry in) { - String name = translate(in); - if (name == null) { - name = in.getName(); - } - return new MethodEntry(translateEntry(in.getClassEntry()), name, translateSignature(in.getSignature())); - } - - public ConstructorEntry translateEntry(ConstructorEntry in) { - if (in.isStatic()) { - return new ConstructorEntry(translateEntry(in.getClassEntry())); - } else { - return new ConstructorEntry(translateEntry(in.getClassEntry()), translateSignature(in.getSignature())); - } - } - - public BehaviorEntry translateEntry(BehaviorEntry in) { - if (in instanceof MethodEntry) { - return translateEntry((MethodEntry) in); - } else if (in instanceof ConstructorEntry) { - return translateEntry((ConstructorEntry) in); - } - throw new Error("Wrong entry type!"); - } - - // TODO: support not identical behavior (specific to constructor) - public String translate(ArgumentEntry in) { - String classTranslate = translateArgument(in); - - // Not found in this class - if (classTranslate == null) { - List ancestry = this.index.getAncestry(in.getClassEntry()); - - // Check in mother class for the arg - for (ClassEntry entry : ancestry) { - ArgumentEntry motherArg = in.cloneToNewClass(entry); - if (this.index.entryExists(motherArg)) { - String result = translateArgument(motherArg); - if (result != null) - return result; - } - } - } - return classTranslate; - } - - public String translateArgument(ArgumentEntry in) { - // look for identical behavior in superclasses - ClassEntry entry = in.getClassEntry(); - - if (entry != null) { - // look for the class - ClassMapping classMapping = findClassMapping(entry); - if (classMapping != null) { - - // look for the method - MethodMapping methodMapping = this.direction.choose( - classMapping.getMethodByObf(in.getMethodName(), in.getMethodSignature()), - classMapping.getMethodByDeobf(in.getMethodName(), translateSignature(in.getMethodSignature())) - ); - if (methodMapping != null) { - return this.direction.choose( - methodMapping.getDeobfArgumentName(in.getIndex()), - methodMapping.getObfArgumentName(in.getIndex()) - ); - } - } - } - return null; - } - - public ArgumentEntry translateEntry(ArgumentEntry in) { - String name = translate(in); - if (name == null) { - name = in.getName(); - } - return new ArgumentEntry(translateEntry(in.getBehaviorEntry()), in.getIndex(), name); - } - - public Type translateType(Type type) { - return new Type(type, this.classNameReplacer); - } - - public Signature translateSignature(Signature signature) { - return new Signature(signature, this.classNameReplacer); - } - - private ClassMapping findClassMapping(ClassEntry in) { - List mappingChain = getClassMappingChain(in); - return mappingChain.get(mappingChain.size() - 1); - } - - private List getClassMappingChain(ClassEntry in) { - - // get a list of all the classes in the hierarchy - String[] parts = in.getName().split("\\$"); - List mappingsChain = Lists.newArrayList(); - - // get mappings for the outer class - ClassMapping outerClassMapping = this.classes.get(parts[0]); - mappingsChain.add(outerClassMapping); - - for (int i = 1; i < parts.length; i++) { - - // get mappings for the inner class - ClassMapping innerClassMapping = null; - if (outerClassMapping != null) { - innerClassMapping = this.direction.choose( - outerClassMapping.getInnerClassByObfSimple(parts[i]), - outerClassMapping.getInnerClassByDeobfThenObfSimple(parts[i]) - ); - } - mappingsChain.add(innerClassMapping); - outerClassMapping = innerClassMapping; - } - - assert (mappingsChain.size() == parts.length); - return mappingsChain; - } - - public Mappings.EntryModifier getModifier(Entry entry) { - ClassMapping classMapping = findClassMapping(entry.getClassEntry()); - if (classMapping != null && !entry.getName().equals("")) { - if (entry instanceof ClassEntry) - return classMapping.getModifier(); - else if (entry instanceof FieldEntry) { - FieldMapping fieldMapping = classMapping.getFieldByObf(entry.getName(), ((FieldEntry) entry).getType()); - return fieldMapping != null ? fieldMapping.getModifier() : Mappings.EntryModifier.UNCHANGED; - } else if (entry instanceof BehaviorEntry) { - MethodMapping methodMapping = classMapping.getMethodByObf(entry.getName(), ((BehaviorEntry) entry).getSignature()); - return methodMapping != null ? methodMapping.getModifier() : Mappings.EntryModifier.UNCHANGED; - } else - throw new Error("Unknown entry type: " + entry.getClass().getName()); + @SuppressWarnings("unchecked") + default T getTranslatedEntry(T entry) { + if (entry instanceof ClassDefEntry) { + return (T) getTranslatedClassDef((ClassDefEntry) entry); + } else if (entry instanceof ClassEntry) { + return (T) getTranslatedClass((ClassEntry) entry); + } else if (entry instanceof FieldDefEntry) { + return (T) getTranslatedFieldDef((FieldDefEntry) entry); + } else if (entry instanceof MethodDefEntry) { + return (T) getTranslatedMethodDef((MethodDefEntry) entry); + } else if (entry instanceof FieldEntry) { + return (T) getTranslatedField((FieldEntry) entry); + } else if (entry instanceof MethodEntry) { + return (T) getTranslatedMethod((MethodEntry) entry); + } else if (entry instanceof LocalVariableDefEntry) { + return (T) getTranslatedVariableDef((LocalVariableDefEntry) entry); + } else if (entry instanceof LocalVariableEntry) { + return (T) getTranslatedVariable((LocalVariableEntry) entry); + } else if (entry instanceof TypeDescriptor) { + return (T) getTranslatedTypeDesc((TypeDescriptor) entry); + } else if (entry instanceof MethodDescriptor) { + return (T) getTranslatedMethodDesc((MethodDescriptor) entry); } - return Mappings.EntryModifier.UNCHANGED; + throw new IllegalArgumentException("Cannot translate unknown entry type"); } } diff --git a/src/main/java/cuchaz/enigma/mapping/Type.java b/src/main/java/cuchaz/enigma/mapping/Type.java deleted file mode 100644 index 609bd64..0000000 --- a/src/main/java/cuchaz/enigma/mapping/Type.java +++ /dev/null @@ -1,235 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -import com.google.common.collect.Maps; - -import java.util.Map; - -public class Type { - - protected String name; - - public Type(String name) { - - // don't deal with generics - // this is just for raw jvm types - if (name.charAt(0) == 'T' || name.indexOf('<') >= 0 || name.indexOf('>') >= 0) { - throw new IllegalArgumentException("don't use with generic types or templates: " + name); - } - - this.name = name; - } - - public Type(Type other, ClassNameReplacer replacer) { - this.name = other.name; - if (other.isClass()) { - String replacedName = replacer.replace(other.getClassEntry().getClassName()); - if (replacedName != null) { - this.name = "L" + replacedName + ";"; - } - } else if (other.isArray() && other.hasClass()) { - String replacedName = replacer.replace(other.getClassEntry().getClassName()); - if (replacedName != null) { - this.name = Type.getArrayPrefix(other.getArrayDimension()) + "L" + replacedName + ";"; - } - } - } - - public static String parseFirst(String in) { - - if (in == null || in.length() <= 0) { - throw new IllegalArgumentException("No type to parse, input is empty!"); - } - - // read one type from the input - - char c = in.charAt(0); - - // first check for void - if (c == 'V') { - return "V"; - } - - // then check for primitives - Primitive primitive = Primitive.get(c); - if (primitive != null) { - return in.substring(0, 1); - } - - // then check for classes - if (c == 'L') { - return readClass(in); - } - - // then check for templates - if (c == 'T') { - return readClass(in); - } - - // then check for arrays - int dim = countArrayDimension(in); - if (dim > 0) { - String arrayType = Type.parseFirst(in.substring(dim)); - return in.substring(0, dim + arrayType.length()); - } - - throw new IllegalArgumentException("don't know how to parse: " + in); - } - - private static String getArrayPrefix(int dimension) { - StringBuilder buf = new StringBuilder(); - for (int i = 0; i < dimension; i++) { - buf.append("["); - } - return buf.toString(); - } - - private static int countArrayDimension(String in) { - int i = 0; - while (i < in.length() && in.charAt(i) == '[') - i++; - return i; - } - - private static String readClass(String in) { - // read all the characters in the buffer until we hit a ';' - // include the parameters too - StringBuilder buf = new StringBuilder(); - int depth = 0; - for (int i = 0; i < in.length(); i++) { - char c = in.charAt(i); - buf.append(c); - - if (c == '<') { - depth++; - } else if (c == '>') { - depth--; - } else if (depth == 0 && c == ';') { - return buf.toString(); - } - } - return null; - } - - @Override - public String toString() { - return this.name; - } - - public boolean isVoid() { - return this.name.length() == 1 && this.name.charAt(0) == 'V'; - } - - public boolean isPrimitive() { - return this.name.length() == 1 && Primitive.get(this.name.charAt(0)) != null; - } - - public Primitive getPrimitive() { - if (!isPrimitive()) { - throw new IllegalStateException("not a primitive"); - } - return Primitive.get(this.name.charAt(0)); - } - - public boolean isClass() { - return this.name.charAt(0) == 'L' && this.name.charAt(this.name.length() - 1) == ';'; - } - - public ClassEntry getClassEntry() { - if (isClass()) { - String name = this.name.substring(1, this.name.length() - 1); - - int pos = name.indexOf('<'); - if (pos >= 0) { - // remove the parameters from the class name - name = name.substring(0, pos); - } - - return new ClassEntry(name); - - } else if (isArray() && getArrayType().isClass()) { - return getArrayType().getClassEntry(); - } else { - throw new IllegalStateException("type doesn't have a class"); - } - } - - public boolean isArray() { - return this.name.charAt(0) == '['; - } - - public int getArrayDimension() { - if (!isArray()) { - throw new IllegalStateException("not an array"); - } - return countArrayDimension(this.name); - } - - public Type getArrayType() { - if (!isArray()) { - throw new IllegalStateException("not an array"); - } - return new Type(this.name.substring(getArrayDimension(), this.name.length())); - } - - public boolean hasClass() { - return isClass() || (isArray() && getArrayType().hasClass()); - } - - @Override - public boolean equals(Object other) { - return other instanceof Type && equals((Type) other); - } - - public boolean equals(Type other) { - return this.name.equals(other.name); - } - - public int hashCode() { - return this.name.hashCode(); - } - - public enum Primitive { - Byte('B'), - Character('C'), - Short('S'), - Integer('I'), - Long('J'), - Float('F'), - Double('D'), - Boolean('Z'); - - private static final Map lookup; - - static { - lookup = Maps.newTreeMap(); - for (Primitive val : values()) { - lookup.put(val.getCode(), val); - } - } - - private char code; - - Primitive(char code) { - this.code = code; - } - - public static Primitive get(char code) { - return lookup.get(code); - } - - public char getCode() { - return this.code; - } - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java b/src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java new file mode 100644 index 0000000..b7b1255 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java @@ -0,0 +1,244 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import cuchaz.enigma.mapping.entry.ClassEntry; + +import java.util.Map; +import java.util.function.Function; + +public class TypeDescriptor { + + protected final String desc; + + public TypeDescriptor(String desc) { + Preconditions.checkNotNull(desc, "Desc cannot be null"); + + // don't deal with generics + // this is just for raw jvm types + if (desc.charAt(0) == 'T' || desc.indexOf('<') >= 0 || desc.indexOf('>') >= 0) { + throw new IllegalArgumentException("don't use with generic types or templates: " + desc); + } + + this.desc = desc; + } + + public static String parseFirst(String in) { + + if (in == null || in.length() <= 0) { + throw new IllegalArgumentException("No desc to parse, input is empty!"); + } + + // read one desc from the input + + char c = in.charAt(0); + + // first check for void + if (c == 'V') { + return "V"; + } + + // then check for primitives + Primitive primitive = Primitive.get(c); + if (primitive != null) { + return in.substring(0, 1); + } + + // then check for classes + if (c == 'L') { + return readClass(in); + } + + // then check for templates + if (c == 'T') { + return readClass(in); + } + + // then check for arrays + int dim = countArrayDimension(in); + if (dim > 0) { + String arrayType = TypeDescriptor.parseFirst(in.substring(dim)); + return in.substring(0, dim + arrayType.length()); + } + + throw new IllegalArgumentException("don't know how to parse: " + in); + } + + private static int countArrayDimension(String in) { + int i = 0; + while (i < in.length() && in.charAt(i) == '[') + i++; + return i; + } + + private static String readClass(String in) { + // read all the characters in the buffer until we hit a ';' + // include the parameters too + StringBuilder buf = new StringBuilder(); + int depth = 0; + for (int i = 0; i < in.length(); i++) { + char c = in.charAt(i); + buf.append(c); + + if (c == '<') { + depth++; + } else if (c == '>') { + depth--; + } else if (depth == 0 && c == ';') { + return buf.toString(); + } + } + return null; + } + + public static TypeDescriptor of(String name) { + return new TypeDescriptor("L" + name + ";"); + } + + @Override + public String toString() { + return this.desc; + } + + public boolean isVoid() { + return this.desc.length() == 1 && this.desc.charAt(0) == 'V'; + } + + public boolean isPrimitive() { + return this.desc.length() == 1 && Primitive.get(this.desc.charAt(0)) != null; + } + + public Primitive getPrimitive() { + if (!isPrimitive()) { + throw new IllegalStateException("not a primitive"); + } + return Primitive.get(this.desc.charAt(0)); + } + + public boolean isType() { + return this.desc.charAt(0) == 'L' && this.desc.charAt(this.desc.length() - 1) == ';'; + } + + public ClassEntry getTypeEntry() { + if (isType()) { + String name = this.desc.substring(1, this.desc.length() - 1); + + int pos = name.indexOf('<'); + if (pos >= 0) { + // remove the parameters from the class name + name = name.substring(0, pos); + } + + return new ClassEntry(name); + + } else if (isArray() && getArrayType().isType()) { + return getArrayType().getTypeEntry(); + } else { + throw new IllegalStateException("desc doesn't have a class"); + } + } + + public boolean isArray() { + return this.desc.charAt(0) == '['; + } + + public int getArrayDimension() { + if (!isArray()) { + throw new IllegalStateException("not an array"); + } + return countArrayDimension(this.desc); + } + + public TypeDescriptor getArrayType() { + if (!isArray()) { + throw new IllegalStateException("not an array"); + } + return new TypeDescriptor(this.desc.substring(getArrayDimension(), this.desc.length())); + } + + public boolean containsType() { + return isType() || (isArray() && getArrayType().containsType()); + } + + @Override + public boolean equals(Object other) { + return other instanceof TypeDescriptor && equals((TypeDescriptor) other); + } + + public boolean equals(TypeDescriptor other) { + return this.desc.equals(other.desc); + } + + @Override + public int hashCode() { + return this.desc.hashCode(); + } + + public TypeDescriptor remap(Function remapper) { + String desc = this.desc; + if (isType() || (isArray() && containsType())) { + String replacedName = remapper.apply(this.getTypeEntry().getName()); + if (replacedName != null) { + if (this.isType()) { + desc = "L" + replacedName + ";"; + } else { + desc = getArrayPrefix(this.getArrayDimension()) + "L" + replacedName + ";"; + } + } + } + return new TypeDescriptor(desc); + } + + private static String getArrayPrefix(int dimension) { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < dimension; i++) { + buf.append("["); + } + return buf.toString(); + } + + public enum Primitive { + Byte('B'), + Character('C'), + Short('S'), + Integer('I'), + Long('J'), + Float('F'), + Double('D'), + Boolean('Z'); + + private static final Map lookup; + + static { + lookup = Maps.newTreeMap(); + for (Primitive val : values()) { + lookup.put(val.getCode(), val); + } + } + + private char code; + + Primitive(char code) { + this.code = code; + } + + public static Primitive get(char code) { + return lookup.get(code); + } + + public char getCode() { + return this.code; + } + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/ClassDefEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/ClassDefEntry.java new file mode 100644 index 0000000..ac1fe2a --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/ClassDefEntry.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.Signature; + +public class ClassDefEntry extends ClassEntry { + private final AccessFlags access; + private final Signature signature; + + public ClassDefEntry(String className, Signature signature, AccessFlags access) { + super(className); + Preconditions.checkNotNull(signature, "Class signature cannot be null"); + Preconditions.checkNotNull(access, "Class access cannot be null"); + this.signature = signature; + this.access = access; + } + + public Signature getSignature() { + return signature; + } + + public AccessFlags getAccess() { + return access; + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/ClassEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/ClassEntry.java new file mode 100644 index 0000000..c795825 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/ClassEntry.java @@ -0,0 +1,175 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; + +import java.util.List; + +public class ClassEntry implements Entry { + + private final String name; + + public ClassEntry(String className) { + Preconditions.checkNotNull(className, "Class name cannot be null"); + + if (className.indexOf('.') >= 0) { + throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className); + } + + this.name = className; + + if (isInnerClass() && getInnermostClassName().indexOf('/') >= 0) { + throw new IllegalArgumentException("Inner class must not have a package: " + className); + } + } + + public ClassEntry(ClassEntry other) { + this.name = other.name; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public String getClassName() { + return this.name; + } + + @Override + public ClassEntry getOwnerClassEntry() { + return this; + } + + @Override + public ClassEntry updateOwnership(ClassEntry classEntry) { + return classEntry; + } + + @Override + public int hashCode() { + return this.name.hashCode(); + } + + @Override + public boolean equals(Object other) { + return other instanceof ClassEntry && equals((ClassEntry) other); + } + + public boolean equals(ClassEntry other) { + return other != null && this.name.equals(other.name); + } + + @Override + public String toString() { + return this.name; + } + + public boolean isArray() { + return this.name.lastIndexOf('[') >= 0; + } + + public boolean isInnerClass() { + return this.name.lastIndexOf('$') >= 0; + } + + public List getClassChainNames() { + return Lists.newArrayList(this.name.split("\\$")); + } + + public List getClassChain() { + List entries = Lists.newArrayList(); + StringBuilder buf = new StringBuilder(); + for (String name : getClassChainNames()) { + if (buf.length() > 0) { + buf.append("$"); + } + buf.append(name); + entries.add(new ClassEntry(buf.toString())); + } + return entries; + } + + public String getOutermostClassName() { + if (isInnerClass()) { + return this.name.substring(0, this.name.indexOf('$')); + } + return this.name; + } + + public ClassEntry getOutermostClassEntry() { + return new ClassEntry(getOutermostClassName()); + } + + public String getOuterClassName() { + if (!isInnerClass()) { + throw new Error("This is not an inner class!"); + } + return this.name.substring(0, this.name.lastIndexOf('$')); + } + + public ClassEntry getOuterClassEntry() { + return new ClassEntry(getOuterClassName()); + } + + public String getInnermostClassName() { + if (!isInnerClass()) { + throw new Error("This is not an inner class!"); + } + return this.name.substring(this.name.lastIndexOf('$') + 1); + } + + public boolean isInDefaultPackage() { + return this.name.indexOf('/') < 0; + } + + public String getPackageName() { + return getPackageName(this.name); + } + + public String getSimpleName() { + int pos = this.name.lastIndexOf('/'); + if (pos > 0) { + return this.name.substring(pos + 1); + } + return this.name; + } + + public static String getPackageName(String name) { + int pos = name.lastIndexOf('/'); + if (pos > 0) { + return name.substring(0, pos); + } + return null; + } + + public ClassEntry buildClassEntry(List classChain) { + assert (classChain.contains(this)); + StringBuilder buf = new StringBuilder(); + for (ClassEntry chainEntry : classChain) { + if (buf.length() == 0) { + buf.append(chainEntry.getName()); + } else { + buf.append("$"); + buf.append(chainEntry.isInnerClass() ? chainEntry.getInnermostClassName() : chainEntry.getSimpleName()); + } + + if (chainEntry == this) { + break; + } + } + return new ClassEntry(buf.toString()); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/Entry.java b/src/main/java/cuchaz/enigma/mapping/entry/Entry.java new file mode 100644 index 0000000..b612140 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/Entry.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +public interface Entry { + String getName(); + + String getClassName(); + + ClassEntry getOwnerClassEntry(); + + Entry updateOwnership(ClassEntry classEntry); +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/EntryFactory.java b/src/main/java/cuchaz/enigma/mapping/entry/EntryFactory.java new file mode 100644 index 0000000..5bd159f --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/EntryFactory.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.mapping.ClassMapping; +import cuchaz.enigma.mapping.FieldMapping; +import cuchaz.enigma.mapping.MethodDescriptor; +import cuchaz.enigma.mapping.MethodMapping; + +public class EntryFactory { + public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) { + ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfFullName()); + return obfClassEntry.buildClassEntry(jarIndex.getObfClassChain(obfClassEntry)); + } + + private static ClassEntry getObfClassEntry(ClassMapping classMapping) { + return new ClassEntry(classMapping.getObfFullName()); + } + + public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) { + return new ClassEntry(classMapping.getDeobfName()); + } + + public static FieldEntry getObfFieldEntry(ClassMapping classMapping, FieldMapping fieldMapping) { + return new FieldEntry(getObfClassEntry(classMapping), fieldMapping.getObfName(), fieldMapping.getObfDesc()); + } + + public static MethodEntry getMethodEntry(ClassEntry classEntry, String name, MethodDescriptor desc) { + return new MethodEntry(classEntry, name, desc); + } + + public static MethodEntry getObfMethodEntry(ClassEntry classEntry, MethodMapping methodMapping) { + return getMethodEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfDesc()); + } + + public static MethodEntry getObfMethodEntry(ClassMapping classMapping, MethodMapping methodMapping) { + return getObfMethodEntry(getObfClassEntry(classMapping), methodMapping); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/FieldDefEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/FieldDefEntry.java new file mode 100644 index 0000000..d18115b --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/FieldDefEntry.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.Signature; +import cuchaz.enigma.mapping.TypeDescriptor; + +public class FieldDefEntry extends FieldEntry { + private final AccessFlags access; + private final Signature signature; + + public FieldDefEntry(ClassEntry ownerEntry, String name, TypeDescriptor desc, Signature signature, AccessFlags access) { + super(ownerEntry, name, desc); + Preconditions.checkNotNull(access, "Field access cannot be null"); + Preconditions.checkNotNull(signature, "Field signature cannot be null"); + this.access = access; + this.signature = signature; + } + + public AccessFlags getAccess() { + return access; + } + + public Signature getSignature() { + return signature; + } + + @Override + public FieldDefEntry updateOwnership(ClassEntry owner) { + return new FieldDefEntry(owner, this.name, this.desc, signature, access); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/FieldEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/FieldEntry.java new file mode 100644 index 0000000..b6e1554 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/FieldEntry.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.mapping.TypeDescriptor; +import cuchaz.enigma.utils.Utils; + +public class FieldEntry implements Entry { + + protected final ClassEntry ownerEntry; + protected final String name; + protected final TypeDescriptor desc; + + // NOTE: this argument order is important for the MethodReader/MethodWriter + public FieldEntry(ClassEntry ownerEntry, String name, TypeDescriptor desc) { + Preconditions.checkNotNull(ownerEntry, "Owner cannot be null"); + Preconditions.checkNotNull(name, "Field name cannot be null"); + Preconditions.checkNotNull(desc, "Field descriptor cannot be null"); + + this.ownerEntry = ownerEntry; + this.name = name; + this.desc = desc; + } + + @Override + public ClassEntry getOwnerClassEntry() { + return this.ownerEntry; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public String getClassName() { + return this.ownerEntry.getName(); + } + + public TypeDescriptor getDesc() { + return this.desc; + } + + @Override + public FieldEntry updateOwnership(ClassEntry owner) { + return new FieldEntry(owner, this.name, this.desc); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.ownerEntry, this.name, this.desc); + } + + @Override + public boolean equals(Object other) { + return other instanceof FieldEntry && equals((FieldEntry) other); + } + + public boolean equals(FieldEntry other) { + return this.ownerEntry.equals(other.ownerEntry) && this.name.equals(other.name) && this.desc.equals(other.desc); + } + + @Override + public String toString() { + return this.ownerEntry.getName() + "." + this.name + ":" + this.desc; + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/LocalVariableDefEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/LocalVariableDefEntry.java new file mode 100644 index 0000000..7742272 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/LocalVariableDefEntry.java @@ -0,0 +1,57 @@ +package cuchaz.enigma.mapping.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.mapping.TypeDescriptor; +import cuchaz.enigma.utils.Utils; + +/** + * TypeDescriptor... + * Created by Thog + * 19/10/2016 + */ +public class LocalVariableDefEntry extends LocalVariableEntry { + + protected final MethodDefEntry ownerEntry; + protected final TypeDescriptor desc; + + public LocalVariableDefEntry(MethodDefEntry ownerEntry, int index, String name, TypeDescriptor desc) { + super(ownerEntry, index, name); + Preconditions.checkNotNull(desc, "Variable desc cannot be null"); + + this.ownerEntry = ownerEntry; + this.desc = desc; + } + + @Override + public MethodDefEntry getOwnerEntry() { + return this.ownerEntry; + } + + public TypeDescriptor getDesc() { + return desc; + } + + @Override + public LocalVariableDefEntry updateOwnership(ClassEntry classEntry) { + return new LocalVariableDefEntry(ownerEntry.updateOwnership(classEntry), index, name, desc); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.ownerEntry, this.desc.hashCode(), this.name.hashCode(), Integer.hashCode(this.index)); + } + + @Override + public boolean equals(Object other) { + return other instanceof LocalVariableDefEntry && equals((LocalVariableDefEntry) other); + } + + public boolean equals(LocalVariableDefEntry other) { + return this.ownerEntry.equals(other.ownerEntry) && this.desc.equals(other.desc) && this.name.equals(other.name) && this.index == other.index; + } + + @Override + public String toString() { + return this.ownerEntry + "(" + this.index + ":" + this.name + ":" + this.desc + ")"; + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/LocalVariableEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/LocalVariableEntry.java new file mode 100644 index 0000000..a794d0a --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/LocalVariableEntry.java @@ -0,0 +1,82 @@ +package cuchaz.enigma.mapping.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.mapping.MethodDescriptor; +import cuchaz.enigma.utils.Utils; + +/** + * TypeDescriptor... + * Created by Thog + * 19/10/2016 + */ +public class LocalVariableEntry implements Entry { + + protected final MethodEntry ownerEntry; + protected final String name; + protected final int index; + + public LocalVariableEntry(MethodEntry ownerEntry, int index, String name) { + Preconditions.checkNotNull(ownerEntry, "Variable owner cannot be null"); + Preconditions.checkNotNull(name, "Variable name cannot be null"); + Preconditions.checkArgument(index >= 0, "Index must be positive"); + + this.ownerEntry = ownerEntry; + this.name = name; + this.index = index; + } + + public MethodEntry getOwnerEntry() { + return this.ownerEntry; + } + + public int getIndex() { + return index; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public ClassEntry getOwnerClassEntry() { + return this.ownerEntry.getOwnerClassEntry(); + } + + @Override + public String getClassName() { + return this.ownerEntry.getClassName(); + } + + @Override + public LocalVariableEntry updateOwnership(ClassEntry classEntry) { + return new LocalVariableEntry(ownerEntry.updateOwnership(classEntry), index, name); + } + + public String getMethodName() { + return this.ownerEntry.getName(); + } + + public MethodDescriptor getMethodDesc() { + return this.ownerEntry.getDesc(); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.ownerEntry, this.name.hashCode(), Integer.hashCode(this.index)); + } + + @Override + public boolean equals(Object other) { + return other instanceof LocalVariableEntry && equals((LocalVariableEntry) other); + } + + public boolean equals(LocalVariableEntry other) { + return this.ownerEntry.equals(other.ownerEntry) && this.name.equals(other.name) && this.index == other.index; + } + + @Override + public String toString() { + return this.ownerEntry + "(" + this.index + ":" + this.name + ")"; + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java new file mode 100644 index 0000000..bb7c85e --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/MethodDefEntry.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.MethodDescriptor; +import cuchaz.enigma.mapping.Signature; + +public class MethodDefEntry extends MethodEntry { + + private final AccessFlags access; + private final Signature signature; + + public MethodDefEntry(ClassEntry classEntry, String name, MethodDescriptor descriptor, Signature signature, AccessFlags access) { + super(classEntry, name, descriptor); + Preconditions.checkNotNull(access, "Method access cannot be null"); + Preconditions.checkNotNull(signature, "Method signature cannot be null"); + this.access = access; + this.signature = signature; + } + + public AccessFlags getAccess() { + return access; + } + + public Signature getSignature() { + return signature; + } + + public int getVariableOffset(ClassDefEntry ownerEntry) { + // Enum constructors have an implicit "name" and "ordinal" parameter as well as "this" + if (ownerEntry.getAccess().isEnum() && getName().startsWith("<")) { + return 3; + } else { + // If we're not static, "this" is bound to index 0 + return getAccess().isStatic() ? 0 : 1; + } + } + + @Override + public MethodDefEntry updateOwnership(ClassEntry classEntry) { + return new MethodDefEntry(new ClassEntry(classEntry.getName()), name, descriptor, signature, access); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/MethodEntry.java b/src/main/java/cuchaz/enigma/mapping/entry/MethodEntry.java new file mode 100644 index 0000000..1abc5b1 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/MethodEntry.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.mapping.MethodDescriptor; +import cuchaz.enigma.utils.Utils; + +public class MethodEntry implements Entry { + + protected final ClassEntry classEntry; + protected final String name; + protected final MethodDescriptor descriptor; + + public MethodEntry(ClassEntry classEntry, String name, MethodDescriptor descriptor) { + Preconditions.checkNotNull(classEntry, "Class cannot be null"); + Preconditions.checkNotNull(name, "Method name cannot be null"); + Preconditions.checkNotNull(descriptor, "Method descriptor cannot be null"); + + this.classEntry = classEntry; + this.name = name; + this.descriptor = descriptor; + } + + @Override + public ClassEntry getOwnerClassEntry() { + return this.classEntry; + } + + @Override + public String getName() { + return this.name; + } + + public MethodDescriptor getDesc() { + return this.descriptor; + } + + public boolean isConstructor() { + return name.equals("") || name.equals(""); + } + + @Override + public String getClassName() { + return this.classEntry.getName(); + } + + @Override + public MethodEntry updateOwnership(ClassEntry classEntry) { + return new MethodEntry(new ClassEntry(classEntry.getName()), name, descriptor); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.classEntry, this.name, this.descriptor); + } + + @Override + public boolean equals(Object other) { + return other instanceof MethodEntry && equals((MethodEntry) other); + } + + public boolean equals(MethodEntry other) { + return this.classEntry.equals(other.getOwnerClassEntry()) && this.name.equals(other.getName()) && this.descriptor.equals(other.getDesc()); + } + + @Override + public String toString() { + return this.classEntry.getName() + "." + this.name + this.descriptor; + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/ProcyonEntryFactory.java b/src/main/java/cuchaz/enigma/mapping/entry/ProcyonEntryFactory.java new file mode 100644 index 0000000..73770c5 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/ProcyonEntryFactory.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +import com.strobel.assembler.metadata.FieldDefinition; +import com.strobel.assembler.metadata.MemberReference; +import com.strobel.assembler.metadata.MethodDefinition; +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.MethodDescriptor; +import cuchaz.enigma.mapping.Signature; +import cuchaz.enigma.mapping.TypeDescriptor; + +public class ProcyonEntryFactory { + private final ReferencedEntryPool entryPool; + + public ProcyonEntryFactory(ReferencedEntryPool entryPool) { + this.entryPool = entryPool; + } + + public FieldEntry getFieldEntry(MemberReference def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return entryPool.getField(classEntry, def.getName(), def.getErasedSignature()); + } + + public FieldDefEntry getFieldDefEntry(FieldDefinition def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return new FieldDefEntry(classEntry, def.getName(), new TypeDescriptor(def.getErasedSignature()), Signature.createTypedSignature(def.getSignature()), new AccessFlags(def.getModifiers())); + } + + public MethodEntry getMethodEntry(MemberReference def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return entryPool.getMethod(classEntry, def.getName(), def.getErasedSignature()); + } + + public MethodDefEntry getMethodDefEntry(MethodDefinition def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return new MethodDefEntry(classEntry, def.getName(), new MethodDescriptor(def.getErasedSignature()), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers())); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/entry/ReferencedEntryPool.java b/src/main/java/cuchaz/enigma/mapping/entry/ReferencedEntryPool.java new file mode 100644 index 0000000..338d209 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/entry/ReferencedEntryPool.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.mapping.entry; + +import cuchaz.enigma.mapping.MethodDescriptor; +import cuchaz.enigma.mapping.TypeDescriptor; + +import java.util.HashMap; +import java.util.Map; + +public class ReferencedEntryPool { + private final Map classEntries = new HashMap<>(); + private final Map> methodEntries = new HashMap<>(); + private final Map> fieldEntries = new HashMap<>(); + + public ClassEntry getClass(String name) { + return this.classEntries.computeIfAbsent(name, s -> new ClassEntry(name)); + } + + public MethodEntry getMethod(ClassEntry ownerEntry, String name, String desc) { + return getMethod(ownerEntry, name, new MethodDescriptor(desc)); + } + + public MethodEntry getMethod(ClassEntry ownerEntry, String name, MethodDescriptor desc) { + String key = name + desc.toString(); + return getClassMethods(ownerEntry.getName()).computeIfAbsent(key, s -> new MethodEntry(ownerEntry, name, desc)); + } + + public FieldEntry getField(ClassEntry ownerEntry, String name, String desc) { + return getField(ownerEntry, name, new TypeDescriptor(desc)); + } + + public FieldEntry getField(ClassEntry ownerEntry, String name, TypeDescriptor desc) { + return getClassFields(ownerEntry.getName()).computeIfAbsent(name, s -> new FieldEntry(ownerEntry, name, desc)); + } + + private Map getClassMethods(String name) { + return methodEntries.computeIfAbsent(name, s -> new HashMap<>()); + } + + private Map getClassFields(String name) { + return fieldEntries.computeIfAbsent(name, s -> new HashMap<>()); + } +} -- cgit v1.2.3 From d0dfab41da9ba7ad5458287fa027a1ee4fd834e0 Mon Sep 17 00:00:00 2001 From: Thiakil Date: Fri, 20 Jul 2018 12:14:38 +0800 Subject: recursively check ClassMapping dirty state (cherry picked from commit 4a8ee4303ca1ab82da9499181122bfd7e3214a05) --- src/main/java/cuchaz/enigma/mapping/ClassMapping.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'src/main/java/cuchaz/enigma/mapping') diff --git a/src/main/java/cuchaz/enigma/mapping/ClassMapping.java b/src/main/java/cuchaz/enigma/mapping/ClassMapping.java index 8f3f2b2..369ba8c 100644 --- a/src/main/java/cuchaz/enigma/mapping/ClassMapping.java +++ b/src/main/java/cuchaz/enigma/mapping/ClassMapping.java @@ -526,7 +526,16 @@ public class ClassMapping implements Comparable { } public boolean isDirty() { - return isDirty; + return isDirty || areInnersDirty(); + } + + private boolean areInnersDirty(){ + for (ClassMapping c : this.innerClasses()){ + if (c.isDirty()){ + return true; + } + } + return false; } public void resetDirty() { -- cgit v1.2.3