summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/convert
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/cuchaz/enigma/convert')
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassForest.java59
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassIdentifier.java54
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassIdentity.java439
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassMatch.java83
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassMatches.java158
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassMatching.java153
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassNamer.java56
-rw-r--r--src/main/java/cuchaz/enigma/convert/FieldMatches.java150
-rw-r--r--src/main/java/cuchaz/enigma/convert/MappingsConverter.java711
-rw-r--r--src/main/java/cuchaz/enigma/convert/MatchesReader.java105
-rw-r--r--src/main/java/cuchaz/enigma/convert/MatchesWriter.java123
-rw-r--r--src/main/java/cuchaz/enigma/convert/MemberMatches.java179
12 files changed, 0 insertions, 2270 deletions
diff --git a/src/main/java/cuchaz/enigma/convert/ClassForest.java b/src/main/java/cuchaz/enigma/convert/ClassForest.java
deleted file mode 100644
index 4542fb3..0000000
--- a/src/main/java/cuchaz/enigma/convert/ClassForest.java
+++ /dev/null
@@ -1,59 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.HashMultimap;
15import com.google.common.collect.Multimap;
16import cuchaz.enigma.mapping.ClassEntry;
17
18import java.util.Collection;
19
20public class ClassForest {
21
22 private ClassIdentifier identifier;
23 private Multimap<ClassIdentity, ClassEntry> forest;
24
25 public ClassForest(ClassIdentifier identifier) {
26 this.identifier = identifier;
27 this.forest = HashMultimap.create();
28 }
29
30 public void addAll(Iterable<ClassEntry> entries) {
31 for (ClassEntry entry : entries) {
32 add(entry);
33 }
34 }
35
36 public void add(ClassEntry entry) {
37 try {
38 this.forest.put(this.identifier.identify(entry), entry);
39 } catch (ClassNotFoundException ex) {
40 throw new Error("Unable to find class " + entry.getName());
41 }
42 }
43
44 public Collection<ClassIdentity> identities() {
45 return this.forest.keySet();
46 }
47
48 public Collection<ClassEntry> classes() {
49 return this.forest.values();
50 }
51
52 public Collection<ClassEntry> getClasses(ClassIdentity identity) {
53 return this.forest.get(identity);
54 }
55
56 public boolean containsIdentity(ClassIdentity identity) {
57 return this.forest.containsKey(identity);
58 }
59}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java b/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java
deleted file mode 100644
index 0a72073..0000000
--- a/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java
+++ /dev/null
@@ -1,54 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.Maps;
15import cuchaz.enigma.TranslatingTypeLoader;
16import cuchaz.enigma.analysis.JarIndex;
17import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
18import cuchaz.enigma.mapping.ClassEntry;
19import cuchaz.enigma.mapping.Translator;
20import javassist.CtClass;
21
22import java.util.Map;
23import java.util.jar.JarFile;
24
25public class ClassIdentifier {
26
27 private JarIndex index;
28 private SidedClassNamer namer;
29 private boolean useReferences;
30 private TranslatingTypeLoader loader;
31 private Map<ClassEntry, ClassIdentity> cache;
32
33 public ClassIdentifier(JarFile jar, JarIndex index, SidedClassNamer namer, boolean useReferences) {
34 this.index = index;
35 this.namer = namer;
36 this.useReferences = useReferences;
37 this.loader = new TranslatingTypeLoader(jar, index, new Translator(), new Translator());
38 this.cache = Maps.newHashMap();
39 }
40
41 public ClassIdentity identify(ClassEntry classEntry)
42 throws ClassNotFoundException {
43 ClassIdentity identity = this.cache.get(classEntry);
44 if (identity == null) {
45 CtClass c = this.loader.loadClass(classEntry.getName());
46 if (c == null) {
47 throw new ClassNotFoundException(classEntry.getName());
48 }
49 identity = new ClassIdentity(c, this.namer, this.index, this.useReferences);
50 this.cache.put(classEntry, identity);
51 }
52 return identity;
53 }
54}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentity.java b/src/main/java/cuchaz/enigma/convert/ClassIdentity.java
deleted file mode 100644
index a395b75..0000000
--- a/src/main/java/cuchaz/enigma/convert/ClassIdentity.java
+++ /dev/null
@@ -1,439 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.*;
15import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
16import cuchaz.enigma.analysis.EntryReference;
17import cuchaz.enigma.analysis.JarIndex;
18import cuchaz.enigma.bytecode.ConstPoolEditor;
19import cuchaz.enigma.bytecode.InfoType;
20import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
21import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
22import cuchaz.enigma.mapping.*;
23import cuchaz.enigma.utils.Utils;
24import javassist.*;
25import javassist.bytecode.*;
26import javassist.expr.*;
27
28import java.io.UnsupportedEncodingException;
29import java.security.MessageDigest;
30import java.security.NoSuchAlgorithmException;
31import java.util.Enumeration;
32import java.util.List;
33import java.util.Map;
34import java.util.Set;
35
36public class ClassIdentity {
37
38 private ClassEntry classEntry;
39 private SidedClassNamer namer;
40 private final ClassNameReplacer classNameReplacer = new ClassNameReplacer() {
41
42 private Map<String, String> classNames = Maps.newHashMap();
43
44 @Override
45 public String replace(String className) {
46
47 // classes not in the none package can be passed through
48 ClassEntry classEntry = new ClassEntry(className);
49 if (classEntry.getPackageName() != null) {
50 return className;
51 }
52
53 // is this class ourself?
54 if (className.equals(classEntry.getName())) {
55 return "CSelf";
56 }
57
58 // try the namer
59 if (namer != null) {
60 String newName = namer.getName(className);
61 if (newName != null) {
62 return newName;
63 }
64 }
65
66 // otherwise, use local naming
67 if (!classNames.containsKey(className)) {
68 classNames.put(className, getNewClassName());
69 }
70 return classNames.get(className);
71 }
72
73 private String getNewClassName() {
74 return String.format("C%03d", classNames.size());
75 }
76 };
77 private Multiset<String> fields;
78 private Multiset<String> methods;
79 private Multiset<String> constructors;
80 private String staticInitializer;
81 private String extendz;
82 private Multiset<String> implementz;
83 private Set<String> stringLiterals;
84 private Multiset<String> implementations;
85 private Multiset<String> references;
86 private String outer;
87
88 public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) {
89 this.namer = namer;
90
91 // stuff from the bytecode
92
93 this.classEntry = EntryFactory.getClassEntry(c);
94 this.fields = HashMultiset.create();
95 for (CtField field : c.getDeclaredFields()) {
96 this.fields.add(scrubType(field.getSignature()));
97 }
98 this.methods = HashMultiset.create();
99 for (CtMethod method : c.getDeclaredMethods()) {
100 this.methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method));
101 }
102 this.constructors = HashMultiset.create();
103 for (CtConstructor constructor : c.getDeclaredConstructors()) {
104 this.constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor));
105 }
106 this.staticInitializer = "";
107 if (c.getClassInitializer() != null) {
108 this.staticInitializer = getBehaviorSignature(c.getClassInitializer());
109 }
110 this.extendz = "";
111 if (c.getClassFile().getSuperclass() != null) {
112 this.extendz = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
113 }
114 this.implementz = HashMultiset.create();
115 for (String interfaceName : c.getClassFile().getInterfaces()) {
116 this.implementz.add(scrubClassName(Descriptor.toJvmName(interfaceName)));
117 }
118
119 this.stringLiterals = Sets.newHashSet();
120 ConstPool constants = c.getClassFile().getConstPool();
121 for (int i = 1; i < constants.getSize(); i++) {
122 if (constants.getTag(i) == ConstPool.CONST_String) {
123 this.stringLiterals.add(constants.getStringInfo(i));
124 }
125 }
126
127 // stuff from the jar index
128
129 this.implementations = HashMultiset.create();
130 ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, this.classEntry);
131 if (implementationsNode != null) {
132 @SuppressWarnings("unchecked")
133 Enumeration<ClassImplementationsTreeNode> implementations = implementationsNode.children();
134 while (implementations.hasMoreElements()) {
135 ClassImplementationsTreeNode node = implementations.nextElement();
136 this.implementations.add(scrubClassName(node.getClassEntry().getName()));
137 }
138 }
139
140 this.references = HashMultiset.create();
141 if (useReferences) {
142 for (CtField field : c.getDeclaredFields()) {
143 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
144 index.getFieldReferences(fieldEntry).forEach(this::addReference);
145 }
146 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
147 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
148 index.getBehaviorReferences(behaviorEntry).forEach(this::addReference);
149 }
150 }
151
152 this.outer = null;
153 if (this.classEntry.isInnerClass()) {
154 this.outer = this.classEntry.getOuterClassName();
155 }
156 }
157
158 private void addReference(EntryReference<? extends Entry, BehaviorEntry> reference) {
159 if (reference.context.getSignature() != null) {
160 this.references.add(String.format("%s_%s",
161 scrubClassName(reference.context.getClassName()),
162 scrubSignature(reference.context.getSignature())
163 ));
164 } else {
165 this.references.add(String.format("%s_<clinit>",
166 scrubClassName(reference.context.getClassName())
167 ));
168 }
169 }
170
171 public ClassEntry getClassEntry() {
172 return this.classEntry;
173 }
174
175 @Override
176 public String toString() {
177 StringBuilder buf = new StringBuilder();
178 buf.append("class: ");
179 buf.append(this.classEntry.getName());
180 buf.append(" ");
181 buf.append(hashCode());
182 buf.append("\n");
183 for (String field : this.fields) {
184 buf.append("\tfield ");
185 buf.append(field);
186 buf.append("\n");
187 }
188 for (String method : this.methods) {
189 buf.append("\tmethod ");
190 buf.append(method);
191 buf.append("\n");
192 }
193 for (String constructor : this.constructors) {
194 buf.append("\tconstructor ");
195 buf.append(constructor);
196 buf.append("\n");
197 }
198 if (!this.staticInitializer.isEmpty()) {
199 buf.append("\tinitializer ");
200 buf.append(this.staticInitializer);
201 buf.append("\n");
202 }
203 if (!this.extendz.isEmpty()) {
204 buf.append("\textends ");
205 buf.append(this.extendz);
206 buf.append("\n");
207 }
208 for (String interfaceName : this.implementz) {
209 buf.append("\timplements ");
210 buf.append(interfaceName);
211 buf.append("\n");
212 }
213 for (String implementation : this.implementations) {
214 buf.append("\timplemented by ");
215 buf.append(implementation);
216 buf.append("\n");
217 }
218 for (String reference : this.references) {
219 buf.append("\treference ");
220 buf.append(reference);
221 buf.append("\n");
222 }
223 buf.append("\touter ");
224 buf.append(this.outer);
225 buf.append("\n");
226 return buf.toString();
227 }
228
229 private String scrubClassName(String className) {
230 return classNameReplacer.replace(className);
231 }
232
233 private String scrubType(String typeName) {
234 return scrubType(new Type(typeName)).toString();
235 }
236
237 private Type scrubType(Type type) {
238 if (type.hasClass()) {
239 return new Type(type, classNameReplacer);
240 } else {
241 return type;
242 }
243 }
244
245 private String scrubSignature(String signature) {
246 return scrubSignature(new Signature(signature)).toString();
247 }
248
249 private Signature scrubSignature(Signature signature) {
250 return new Signature(signature, classNameReplacer);
251 }
252
253 private boolean isClassMatchedUniquely(String className) {
254 return this.namer != null && this.namer.getName(Descriptor.toJvmName(className)) != null;
255 }
256
257 private String getBehaviorSignature(CtBehavior behavior) {
258 try {
259 // does this method have an implementation?
260 if (behavior.getMethodInfo().getCodeAttribute() == null) {
261 return "(none)";
262 }
263
264 // compute the hash from the opcodes
265 ConstPool constants = behavior.getMethodInfo().getConstPool();
266 final MessageDigest digest = MessageDigest.getInstance("MD5");
267 CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator();
268 while (iter.hasNext()) {
269 int pos = iter.next();
270
271 // update the hash with the opcode
272 int opcode = iter.byteAt(pos);
273 digest.update((byte) opcode);
274 int constIndex;
275 switch (opcode) {
276 case Opcode.LDC:
277 constIndex = iter.byteAt(pos + 1);
278 updateHashWithConstant(digest, constants, constIndex);
279 break;
280
281 case Opcode.LDC_W:
282 case Opcode.LDC2_W:
283 constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2);
284 updateHashWithConstant(digest, constants, constIndex);
285 break;
286 default:
287 break;
288 }
289 }
290
291 // update hash with method and field accesses
292 behavior.instrument(new ExprEditor() {
293 @Override
294 public void edit(MethodCall call) {
295 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
296 updateHashWithString(digest, scrubSignature(call.getSignature()));
297 if (isClassMatchedUniquely(call.getClassName())) {
298 updateHashWithString(digest, call.getMethodName());
299 }
300 }
301
302 @Override
303 public void edit(FieldAccess access) {
304 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName())));
305 updateHashWithString(digest, scrubType(access.getSignature()));
306 if (isClassMatchedUniquely(access.getClassName())) {
307 updateHashWithString(digest, access.getFieldName());
308 }
309 }
310
311 @Override
312 public void edit(ConstructorCall call) {
313 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
314 updateHashWithString(digest, scrubSignature(call.getSignature()));
315 }
316
317 @Override
318 public void edit(NewExpr expr) {
319 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName())));
320 }
321 });
322
323 // convert the hash to a hex string
324 return toHex(digest.digest());
325 } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) {
326 throw new Error(ex);
327 }
328 }
329
330 private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) {
331 ConstPoolEditor editor = new ConstPoolEditor(constants);
332 ConstInfoAccessor item = editor.getItem(index);
333 if (item.getType() == InfoType.StringInfo) {
334 updateHashWithString(digest, constants.getStringInfo(index));
335 }
336 // TODO: other constants
337 }
338
339 private void updateHashWithString(MessageDigest digest, String val) {
340 try {
341 digest.update(val.getBytes("UTF8"));
342 } catch (UnsupportedEncodingException ex) {
343 throw new Error(ex);
344 }
345 }
346
347 private String toHex(byte[] bytes) {
348 // function taken from:
349 // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java
350 final char[] hexArray = "0123456789ABCDEF".toCharArray();
351 char[] hexChars = new char[bytes.length * 2];
352 for (int j = 0; j < bytes.length; j++) {
353 int v = bytes[j] & 0xFF;
354 hexChars[j * 2] = hexArray[v >>> 4];
355 hexChars[j * 2 + 1] = hexArray[v & 0x0F];
356 }
357 return new String(hexChars);
358 }
359
360 @Override
361 public boolean equals(Object other) {
362 return other instanceof ClassIdentity && equals((ClassIdentity) other);
363 }
364
365 public boolean equals(ClassIdentity other) {
366 return this.fields.equals(other.fields)
367 && this.methods.equals(other.methods)
368 && this.constructors.equals(other.constructors)
369 && this.staticInitializer.equals(other.staticInitializer)
370 && this.extendz.equals(other.extendz)
371 && this.implementz.equals(other.implementz)
372 && this.implementations.equals(other.implementations)
373 && this.references.equals(other.references);
374 }
375
376 @Override
377 public int hashCode() {
378 List<Object> objs = Lists.newArrayList();
379 objs.addAll(this.fields);
380 objs.addAll(this.methods);
381 objs.addAll(this.constructors);
382 objs.add(this.staticInitializer);
383 objs.add(this.extendz);
384 objs.addAll(this.implementz);
385 objs.addAll(this.implementations);
386 objs.addAll(this.references);
387 return Utils.combineHashesOrdered(objs);
388 }
389
390 public int getMatchScore(ClassIdentity other) {
391 return 2 * getNumMatches(this.extendz, other.extendz)
392 + 2 * getNumMatches(this.outer, other.outer)
393 + 2 * getNumMatches(this.implementz, other.implementz)
394 + getNumMatches(this.stringLiterals, other.stringLiterals)
395 + getNumMatches(this.fields, other.fields)
396 + getNumMatches(this.methods, other.methods)
397 + getNumMatches(this.constructors, other.constructors);
398 }
399
400 public int getMaxMatchScore() {
401 return 2 + 2 + 2 * this.implementz.size() + this.stringLiterals.size() + this.fields.size() + this.methods.size() + this.constructors.size();
402 }
403
404 public boolean matches(CtClass c) {
405 // just compare declaration counts
406 return this.fields.size() == c.getDeclaredFields().length
407 && this.methods.size() == c.getDeclaredMethods().length
408 && this.constructors.size() == c.getDeclaredConstructors().length;
409 }
410
411 private int getNumMatches(Set<String> a, Set<String> b) {
412 int numMatches = 0;
413 for (String val : a) {
414 if (b.contains(val)) {
415 numMatches++;
416 }
417 }
418 return numMatches;
419 }
420
421 private int getNumMatches(Multiset<String> a, Multiset<String> b) {
422 int numMatches = 0;
423 for (String val : a) {
424 if (b.contains(val)) {
425 numMatches++;
426 }
427 }
428 return numMatches;
429 }
430
431 private int getNumMatches(String a, String b) {
432 if (a == null && b == null) {
433 return 1;
434 } else if (a != null && b != null && a.equals(b)) {
435 return 1;
436 }
437 return 0;
438 }
439}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatch.java b/src/main/java/cuchaz/enigma/convert/ClassMatch.java
deleted file mode 100644
index bb3e4f4..0000000
--- a/src/main/java/cuchaz/enigma/convert/ClassMatch.java
+++ /dev/null
@@ -1,83 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.Sets;
15import cuchaz.enigma.mapping.ClassEntry;
16import cuchaz.enigma.utils.Utils;
17
18import java.util.Collection;
19import java.util.Set;
20
21public class ClassMatch {
22
23 public Set<ClassEntry> sourceClasses;
24 public Set<ClassEntry> destClasses;
25
26 public ClassMatch(Collection<ClassEntry> sourceClasses, Collection<ClassEntry> destClasses) {
27 this.sourceClasses = Sets.newHashSet(sourceClasses);
28 this.destClasses = Sets.newHashSet(destClasses);
29 }
30
31 public ClassMatch(ClassEntry sourceClass, ClassEntry destClass) {
32 sourceClasses = Sets.newHashSet();
33 if (sourceClass != null) {
34 sourceClasses.add(sourceClass);
35 }
36 destClasses = Sets.newHashSet();
37 if (destClass != null) {
38 destClasses.add(destClass);
39 }
40 }
41
42 public boolean isMatched() {
43 return !sourceClasses.isEmpty() && !destClasses.isEmpty();
44 }
45
46 public boolean isAmbiguous() {
47 return sourceClasses.size() > 1 || destClasses.size() > 1;
48 }
49
50 public ClassEntry getUniqueSource() {
51 if (sourceClasses.size() != 1) {
52 throw new IllegalStateException("Match has ambiguous source!");
53 }
54 return sourceClasses.iterator().next();
55 }
56
57 public ClassEntry getUniqueDest() {
58 if (destClasses.size() != 1) {
59 throw new IllegalStateException("Match has ambiguous source!");
60 }
61 return destClasses.iterator().next();
62 }
63
64 public Set<ClassEntry> intersectSourceClasses(Set<ClassEntry> classes) {
65 Set<ClassEntry> intersection = Sets.newHashSet(sourceClasses);
66 intersection.retainAll(classes);
67 return intersection;
68 }
69
70 @Override
71 public int hashCode() {
72 return Utils.combineHashesOrdered(sourceClasses, destClasses);
73 }
74
75 @Override
76 public boolean equals(Object other) {
77 return other instanceof ClassMatch && equals((ClassMatch) other);
78 }
79
80 public boolean equals(ClassMatch other) {
81 return this.sourceClasses.equals(other.sourceClasses) && this.destClasses.equals(other.destClasses);
82 }
83}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatches.java b/src/main/java/cuchaz/enigma/convert/ClassMatches.java
deleted file mode 100644
index db2c550..0000000
--- a/src/main/java/cuchaz/enigma/convert/ClassMatches.java
+++ /dev/null
@@ -1,158 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.BiMap;
15import com.google.common.collect.HashBiMap;
16import com.google.common.collect.Maps;
17import com.google.common.collect.Sets;
18import cuchaz.enigma.mapping.ClassEntry;
19
20import java.util.*;
21
22public class ClassMatches implements Iterable<ClassMatch> {
23
24 private Collection<ClassMatch> matches;
25 private Map<ClassEntry, ClassMatch> matchesBySource;
26 private Map<ClassEntry, ClassMatch> matchesByDest;
27 private BiMap<ClassEntry, ClassEntry> uniqueMatches;
28 private Map<ClassEntry, ClassMatch> ambiguousMatchesBySource;
29 private Map<ClassEntry, ClassMatch> ambiguousMatchesByDest;
30 private Set<ClassEntry> unmatchedSourceClasses;
31 private Set<ClassEntry> unmatchedDestClasses;
32
33 public ClassMatches() {
34 this(new ArrayList<>());
35 }
36
37 public ClassMatches(Collection<ClassMatch> matches) {
38 this.matches = matches;
39 matchesBySource = Maps.newHashMap();
40 matchesByDest = Maps.newHashMap();
41 uniqueMatches = HashBiMap.create();
42 ambiguousMatchesBySource = Maps.newHashMap();
43 ambiguousMatchesByDest = Maps.newHashMap();
44 unmatchedSourceClasses = Sets.newHashSet();
45 unmatchedDestClasses = Sets.newHashSet();
46
47 for (ClassMatch match : matches) {
48 indexMatch(match);
49 }
50 }
51
52 public void add(ClassMatch match) {
53 matches.add(match);
54 indexMatch(match);
55 }
56
57 public void remove(ClassMatch match) {
58 for (ClassEntry sourceClass : match.sourceClasses) {
59 matchesBySource.remove(sourceClass);
60 uniqueMatches.remove(sourceClass);
61 ambiguousMatchesBySource.remove(sourceClass);
62 unmatchedSourceClasses.remove(sourceClass);
63 }
64 for (ClassEntry destClass : match.destClasses) {
65 matchesByDest.remove(destClass);
66 uniqueMatches.inverse().remove(destClass);
67 ambiguousMatchesByDest.remove(destClass);
68 unmatchedDestClasses.remove(destClass);
69 }
70 matches.remove(match);
71 }
72
73 public int size() {
74 return matches.size();
75 }
76
77 @Override
78 public Iterator<ClassMatch> iterator() {
79 return matches.iterator();
80 }
81
82 private void indexMatch(ClassMatch match) {
83 if (!match.isMatched()) {
84 // unmatched
85 unmatchedSourceClasses.addAll(match.sourceClasses);
86 unmatchedDestClasses.addAll(match.destClasses);
87 } else {
88 if (match.isAmbiguous()) {
89 // ambiguously matched
90 for (ClassEntry entry : match.sourceClasses) {
91 ambiguousMatchesBySource.put(entry, match);
92 }
93 for (ClassEntry entry : match.destClasses) {
94 ambiguousMatchesByDest.put(entry, match);
95 }
96 } else {
97 // uniquely matched
98 uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest());
99 }
100 }
101 for (ClassEntry entry : match.sourceClasses) {
102 matchesBySource.put(entry, match);
103 }
104 for (ClassEntry entry : match.destClasses) {
105 matchesByDest.put(entry, match);
106 }
107 }
108
109 public BiMap<ClassEntry, ClassEntry> getUniqueMatches() {
110 return uniqueMatches;
111 }
112
113 public Set<ClassEntry> getUnmatchedSourceClasses() {
114 return unmatchedSourceClasses;
115 }
116
117 public Set<ClassEntry> getUnmatchedDestClasses() {
118 return unmatchedDestClasses;
119 }
120
121 public Set<ClassEntry> getAmbiguouslyMatchedSourceClasses() {
122 return ambiguousMatchesBySource.keySet();
123 }
124
125 public ClassMatch getAmbiguousMatchBySource(ClassEntry sourceClass) {
126 return ambiguousMatchesBySource.get(sourceClass);
127 }
128
129 public ClassMatch getMatchBySource(ClassEntry sourceClass) {
130 return matchesBySource.get(sourceClass);
131 }
132
133 public ClassMatch getMatchByDest(ClassEntry destClass) {
134 return matchesByDest.get(destClass);
135 }
136
137 public void removeSource(ClassEntry sourceClass) {
138 ClassMatch match = matchesBySource.get(sourceClass);
139 if (match != null) {
140 remove(match);
141 match.sourceClasses.remove(sourceClass);
142 if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) {
143 add(match);
144 }
145 }
146 }
147
148 public void removeDest(ClassEntry destClass) {
149 ClassMatch match = matchesByDest.get(destClass);
150 if (match != null) {
151 remove(match);
152 match.destClasses.remove(destClass);
153 if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) {
154 add(match);
155 }
156 }
157 }
158}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatching.java b/src/main/java/cuchaz/enigma/convert/ClassMatching.java
deleted file mode 100644
index f0f27cf..0000000
--- a/src/main/java/cuchaz/enigma/convert/ClassMatching.java
+++ /dev/null
@@ -1,153 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.BiMap;
15import com.google.common.collect.HashBiMap;
16import com.google.common.collect.Lists;
17import com.google.common.collect.Sets;
18import cuchaz.enigma.mapping.ClassEntry;
19
20import java.util.ArrayList;
21import java.util.Collection;
22import java.util.List;
23import java.util.Map.Entry;
24import java.util.Set;
25
26public class ClassMatching {
27
28 private ClassForest sourceClasses;
29 private ClassForest destClasses;
30 private BiMap<ClassEntry, ClassEntry> knownMatches;
31
32 public ClassMatching(ClassIdentifier sourceIdentifier, ClassIdentifier destIdentifier) {
33 sourceClasses = new ClassForest(sourceIdentifier);
34 destClasses = new ClassForest(destIdentifier);
35 knownMatches = HashBiMap.create();
36 }
37
38 public void addKnownMatches(BiMap<ClassEntry, ClassEntry> knownMatches) {
39 this.knownMatches.putAll(knownMatches);
40 }
41
42 public void match(Iterable<ClassEntry> sourceClasses, Iterable<ClassEntry> destClasses) {
43 for (ClassEntry sourceClass : sourceClasses) {
44 if (!knownMatches.containsKey(sourceClass)) {
45 this.sourceClasses.add(sourceClass);
46 }
47 }
48 for (ClassEntry destClass : destClasses) {
49 if (!knownMatches.containsValue(destClass)) {
50 this.destClasses.add(destClass);
51 }
52 }
53 }
54
55 public Collection<ClassMatch> matches() {
56 List<ClassMatch> matches = Lists.newArrayList();
57 for (Entry<ClassEntry, ClassEntry> entry : knownMatches.entrySet()) {
58 matches.add(new ClassMatch(
59 entry.getKey(),
60 entry.getValue()
61 ));
62 }
63 for (ClassIdentity identity : sourceClasses.identities()) {
64 matches.add(new ClassMatch(
65 sourceClasses.getClasses(identity),
66 destClasses.getClasses(identity)
67 ));
68 }
69 for (ClassIdentity identity : destClasses.identities()) {
70 if (!sourceClasses.containsIdentity(identity)) {
71 matches.add(new ClassMatch(
72 new ArrayList<>(),
73 destClasses.getClasses(identity)
74 ));
75 }
76 }
77 return matches;
78 }
79
80 public Collection<ClassEntry> sourceClasses() {
81 Set<ClassEntry> classes = Sets.newHashSet();
82 for (ClassMatch match : matches()) {
83 classes.addAll(match.sourceClasses);
84 }
85 return classes;
86 }
87
88 public Collection<ClassEntry> destClasses() {
89 Set<ClassEntry> classes = Sets.newHashSet();
90 for (ClassMatch match : matches()) {
91 classes.addAll(match.destClasses);
92 }
93 return classes;
94 }
95
96 public BiMap<ClassEntry, ClassEntry> uniqueMatches() {
97 BiMap<ClassEntry, ClassEntry> uniqueMatches = HashBiMap.create();
98 for (ClassMatch match : matches()) {
99 if (match.isMatched() && !match.isAmbiguous()) {
100 uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest());
101 }
102 }
103 return uniqueMatches;
104 }
105
106 public Collection<ClassMatch> ambiguousMatches() {
107 List<ClassMatch> ambiguousMatches = Lists.newArrayList();
108 for (ClassMatch match : matches()) {
109 if (match.isMatched() && match.isAmbiguous()) {
110 ambiguousMatches.add(match);
111 }
112 }
113 return ambiguousMatches;
114 }
115
116 public Collection<ClassEntry> unmatchedSourceClasses() {
117 List<ClassEntry> classes = Lists.newArrayList();
118 for (ClassMatch match : matches()) {
119 if (!match.isMatched() && !match.sourceClasses.isEmpty()) {
120 classes.addAll(match.sourceClasses);
121 }
122 }
123 return classes;
124 }
125
126 public Collection<ClassEntry> unmatchedDestClasses() {
127 List<ClassEntry> classes = Lists.newArrayList();
128 for (ClassMatch match : matches()) {
129 if (!match.isMatched() && !match.destClasses.isEmpty()) {
130 classes.addAll(match.destClasses);
131 }
132 }
133 return classes;
134 }
135
136 @Override
137 public String toString() {
138
139 // count the ambiguous classes
140 int numAmbiguousSource = 0;
141 int numAmbiguousDest = 0;
142 for (ClassMatch match : ambiguousMatches()) {
143 numAmbiguousSource += match.sourceClasses.size();
144 numAmbiguousDest += match.destClasses.size();
145 }
146
147 return String.format("%20s%8s%8s\n", "", "Source", "Dest") + String
148 .format("%20s%8d%8d\n", "Classes", sourceClasses().size(), destClasses().size()) + String
149 .format("%20s%8d%8d\n", "Uniquely matched", uniqueMatches().size(), uniqueMatches().size()) + String
150 .format("%20s%8d%8d\n", "Ambiguously matched", numAmbiguousSource, numAmbiguousDest) + String
151 .format("%20s%8d%8d\n", "Unmatched", unmatchedSourceClasses().size(), unmatchedDestClasses().size());
152 }
153}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassNamer.java b/src/main/java/cuchaz/enigma/convert/ClassNamer.java
deleted file mode 100644
index e5902c4..0000000
--- a/src/main/java/cuchaz/enigma/convert/ClassNamer.java
+++ /dev/null
@@ -1,56 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.BiMap;
15import com.google.common.collect.Maps;
16import cuchaz.enigma.mapping.ClassEntry;
17
18import java.util.Map;
19
20public class ClassNamer {
21
22 private Map<String, String> sourceNames;
23 private Map<String, String> destNames;
24
25 public ClassNamer(BiMap<ClassEntry, ClassEntry> mappings) {
26 // convert the identity mappings to name maps
27 this.sourceNames = Maps.newHashMap();
28 this.destNames = Maps.newHashMap();
29 int i = 0;
30 for (Map.Entry<ClassEntry, ClassEntry> entry : mappings.entrySet()) {
31 String name = String.format("M%04d", i++);
32 this.sourceNames.put(entry.getKey().getName(), name);
33 this.destNames.put(entry.getValue().getName(), name);
34 }
35 }
36
37 public String getSourceName(String name) {
38 return this.sourceNames.get(name);
39 }
40
41 public String getDestName(String name) {
42 return this.destNames.get(name);
43 }
44
45 public SidedClassNamer getSourceNamer() {
46 return this::getSourceName;
47 }
48
49 public SidedClassNamer getDestNamer() {
50 return this::getDestName;
51 }
52
53 public interface SidedClassNamer {
54 String getName(String name);
55 }
56}
diff --git a/src/main/java/cuchaz/enigma/convert/FieldMatches.java b/src/main/java/cuchaz/enigma/convert/FieldMatches.java
deleted file mode 100644
index a528b27..0000000
--- a/src/main/java/cuchaz/enigma/convert/FieldMatches.java
+++ /dev/null
@@ -1,150 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.*;
15import cuchaz.enigma.mapping.ClassEntry;
16import cuchaz.enigma.mapping.FieldEntry;
17
18import java.util.Collection;
19import java.util.Set;
20
21public class FieldMatches {
22
23 private BiMap<FieldEntry, FieldEntry> matches;
24 private Multimap<ClassEntry, FieldEntry> matchedSourceFields;
25 private Multimap<ClassEntry, FieldEntry> unmatchedSourceFields;
26 private Multimap<ClassEntry, FieldEntry> unmatchedDestFields;
27 private Multimap<ClassEntry, FieldEntry> unmatchableSourceFields;
28
29 public FieldMatches() {
30 matches = HashBiMap.create();
31 matchedSourceFields = HashMultimap.create();
32 unmatchedSourceFields = HashMultimap.create();
33 unmatchedDestFields = HashMultimap.create();
34 unmatchableSourceFields = HashMultimap.create();
35 }
36
37 public void addMatch(FieldEntry srcField, FieldEntry destField) {
38 boolean wasAdded = matches.put(srcField, destField) == null;
39 assert (wasAdded);
40 wasAdded = matchedSourceFields.put(srcField.getClassEntry(), srcField);
41 assert (wasAdded);
42 }
43
44 public void addUnmatchedSourceField(FieldEntry fieldEntry) {
45 boolean wasAdded = unmatchedSourceFields.put(fieldEntry.getClassEntry(), fieldEntry);
46 assert (wasAdded);
47 }
48
49 public void addUnmatchedSourceFields(Iterable<FieldEntry> fieldEntries) {
50 for (FieldEntry fieldEntry : fieldEntries) {
51 addUnmatchedSourceField(fieldEntry);
52 }
53 }
54
55 public void addUnmatchedDestField(FieldEntry fieldEntry) {
56 boolean wasAdded = unmatchedDestFields.put(fieldEntry.getClassEntry(), fieldEntry);
57 assert (wasAdded);
58 }
59
60 public void addUnmatchedDestFields(Iterable<FieldEntry> fieldEntries) {
61 for (FieldEntry fieldEntry : fieldEntries) {
62 addUnmatchedDestField(fieldEntry);
63 }
64 }
65
66 public void addUnmatchableSourceField(FieldEntry sourceField) {
67 boolean wasAdded = unmatchableSourceFields.put(sourceField.getClassEntry(), sourceField);
68 assert (wasAdded);
69 }
70
71 public Set<ClassEntry> getSourceClassesWithUnmatchedFields() {
72 return unmatchedSourceFields.keySet();
73 }
74
75 public Collection<ClassEntry> getSourceClassesWithoutUnmatchedFields() {
76 Set<ClassEntry> out = Sets.newHashSet();
77 out.addAll(matchedSourceFields.keySet());
78 out.removeAll(unmatchedSourceFields.keySet());
79 return out;
80 }
81
82 public Collection<FieldEntry> getUnmatchedSourceFields() {
83 return unmatchedSourceFields.values();
84 }
85
86 public Collection<FieldEntry> getUnmatchedSourceFields(ClassEntry sourceClass) {
87 return unmatchedSourceFields.get(sourceClass);
88 }
89
90 public Collection<FieldEntry> getUnmatchedDestFields() {
91 return unmatchedDestFields.values();
92 }
93
94 public Collection<FieldEntry> getUnmatchedDestFields(ClassEntry destClass) {
95 return unmatchedDestFields.get(destClass);
96 }
97
98 public Collection<FieldEntry> getUnmatchableSourceFields() {
99 return unmatchableSourceFields.values();
100 }
101
102 public boolean hasSource(FieldEntry fieldEntry) {
103 return matches.containsKey(fieldEntry) || unmatchedSourceFields.containsValue(fieldEntry);
104 }
105
106 public boolean hasDest(FieldEntry fieldEntry) {
107 return matches.containsValue(fieldEntry) || unmatchedDestFields.containsValue(fieldEntry);
108 }
109
110 public BiMap<FieldEntry, FieldEntry> matches() {
111 return matches;
112 }
113
114 public boolean isMatchedSourceField(FieldEntry sourceField) {
115 return matches.containsKey(sourceField);
116 }
117
118 public boolean isMatchedDestField(FieldEntry destField) {
119 return matches.containsValue(destField);
120 }
121
122 public void makeMatch(FieldEntry sourceField, FieldEntry destField) {
123 boolean wasRemoved = unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
124 assert (wasRemoved);
125 wasRemoved = unmatchedDestFields.remove(destField.getClassEntry(), destField);
126 assert (wasRemoved);
127 addMatch(sourceField, destField);
128 }
129
130 public boolean isMatched(FieldEntry sourceField, FieldEntry destField) {
131 FieldEntry match = matches.get(sourceField);
132 return match != null && match.equals(destField);
133 }
134
135 public void unmakeMatch(FieldEntry sourceField, FieldEntry destField) {
136 boolean wasRemoved = matches.remove(sourceField) != null;
137 assert (wasRemoved);
138 wasRemoved = matchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
139 assert (wasRemoved);
140 addUnmatchedSourceField(sourceField);
141 addUnmatchedDestField(destField);
142 }
143
144 public void makeSourceUnmatchable(FieldEntry sourceField) {
145 assert (!isMatchedSourceField(sourceField));
146 boolean wasRemoved = unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
147 assert (wasRemoved);
148 addUnmatchableSourceField(sourceField);
149 }
150}
diff --git a/src/main/java/cuchaz/enigma/convert/MappingsConverter.java b/src/main/java/cuchaz/enigma/convert/MappingsConverter.java
deleted file mode 100644
index fa3e936..0000000
--- a/src/main/java/cuchaz/enigma/convert/MappingsConverter.java
+++ /dev/null
@@ -1,711 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.*;
15import cuchaz.enigma.Deobfuscator;
16import cuchaz.enigma.TranslatingTypeLoader;
17import cuchaz.enigma.analysis.JarIndex;
18import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
19import cuchaz.enigma.mapping.*;
20import cuchaz.enigma.throwables.MappingConflict;
21import javassist.CtClass;
22import javassist.CtMethod;
23import javassist.NotFoundException;
24import javassist.bytecode.BadBytecode;
25import javassist.bytecode.CodeAttribute;
26import javassist.bytecode.CodeIterator;
27
28import java.util.*;
29import java.util.jar.JarFile;
30
31public class MappingsConverter {
32
33 public static ClassMatches computeClassMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) {
34
35 // index jars
36 System.out.println("Indexing source jar...");
37 JarIndex sourceIndex = new JarIndex();
38 sourceIndex.indexJar(sourceJar, false);
39 System.out.println("Indexing dest jar...");
40 JarIndex destIndex = new JarIndex();
41 destIndex.indexJar(destJar, false);
42
43 // compute the matching
44 ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null);
45 return new ClassMatches(matching.matches());
46 }
47
48 public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap<ClassEntry, ClassEntry> knownMatches) {
49
50 System.out.println("Iteratively matching classes");
51
52 ClassMatching lastMatching = null;
53 int round = 0;
54 SidedClassNamer sourceNamer = null;
55 SidedClassNamer destNamer = null;
56 for (boolean useReferences : Arrays.asList(false, true)) {
57
58 int numUniqueMatchesLastTime = 0;
59 if (lastMatching != null) {
60 numUniqueMatchesLastTime = lastMatching.uniqueMatches().size();
61 }
62
63 while (true) {
64
65 System.out.println("Round " + (++round) + "...");
66
67 // init the matching with identity settings
68 ClassMatching matching = new ClassMatching(
69 new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences),
70 new ClassIdentifier(destJar, destIndex, destNamer, useReferences)
71 );
72
73 if (knownMatches != null) {
74 matching.addKnownMatches(knownMatches);
75 }
76
77 if (lastMatching == null) {
78 // search all classes
79 matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries());
80 } else {
81 // we already know about these matches from last time
82 matching.addKnownMatches(lastMatching.uniqueMatches());
83
84 // search unmatched and ambiguously-matched classes
85 matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses());
86 for (ClassMatch match : lastMatching.ambiguousMatches()) {
87 matching.match(match.sourceClasses, match.destClasses);
88 }
89 }
90 System.out.println(matching);
91 BiMap<ClassEntry, ClassEntry> uniqueMatches = matching.uniqueMatches();
92
93 // did we match anything new this time?
94 if (uniqueMatches.size() > numUniqueMatchesLastTime) {
95 numUniqueMatchesLastTime = uniqueMatches.size();
96 lastMatching = matching;
97 } else {
98 break;
99 }
100
101 // update the namers
102 ClassNamer namer = new ClassNamer(uniqueMatches);
103 sourceNamer = namer.getSourceNamer();
104 destNamer = namer.getDestNamer();
105 }
106 }
107
108 return lastMatching;
109 }
110
111 public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator)
112 throws MappingConflict {
113 // sort the unique matches by size of inner class chain
114 Multimap<Integer, java.util.Map.Entry<ClassEntry, ClassEntry>> matchesByDestChainSize = HashMultimap.create();
115 for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matches.getUniqueMatches().entrySet()) {
116 int chainSize = destDeobfuscator.getJarIndex().getObfClassChain(match.getValue()).size();
117 matchesByDestChainSize.put(chainSize, match);
118 }
119
120 // build the mappings (in order of small-to-large inner chains)
121 Mappings newMappings = new Mappings();
122 List<Integer> chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet());
123 Collections.sort(chainSizes);
124 for (int chainSize : chainSizes) {
125 for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matchesByDestChainSize.get(chainSize)) {
126 // get class info
127 ClassEntry obfSourceClassEntry = match.getKey();
128 ClassEntry obfDestClassEntry = match.getValue();
129 List<ClassEntry> destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(obfDestClassEntry);
130
131 ClassMapping sourceMapping;
132 if (obfSourceClassEntry.isInnerClass()) {
133 List<ClassMapping> srcClassChain = sourceDeobfuscator.getMappings().getClassMappingChain(obfSourceClassEntry);
134 sourceMapping = srcClassChain.get(srcClassChain.size() - 1);
135 } else {
136 sourceMapping = sourceDeobfuscator.getMappings().getClassByObf(obfSourceClassEntry);
137 }
138
139 if (sourceMapping == null) {
140 // if this class was never deobfuscated, don't try to match it
141 continue;
142 }
143
144 // find out where to make the dest class mapping
145 if (destClassChain.size() == 1) {
146 // not an inner class, add directly to mappings
147 newMappings.addClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, false));
148 } else {
149 // inner class, find the outer class mapping
150 ClassMapping destMapping = null;
151 for (int i = 0; i < destClassChain.size() - 1; i++) {
152 ClassEntry destChainClassEntry = destClassChain.get(i);
153 if (destMapping == null) {
154 destMapping = newMappings.getClassByObf(destChainClassEntry);
155 if (destMapping == null) {
156 destMapping = new ClassMapping(destChainClassEntry.getName());
157 newMappings.addClassMapping(destMapping);
158 }
159 } else {
160 destMapping = destMapping.getInnerClassByObfSimple(destChainClassEntry.getInnermostClassName());
161 if (destMapping == null) {
162 destMapping = new ClassMapping(destChainClassEntry.getName());
163 destMapping.addInnerClassMapping(destMapping);
164 }
165 }
166 }
167 destMapping.addInnerClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, true));
168 }
169 }
170 }
171 return newMappings;
172 }
173
174 private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping oldClassMapping, final ClassMatches matches, boolean useSimpleName) {
175
176 ClassNameReplacer replacer = className ->
177 {
178 ClassEntry newClassEntry = matches.getUniqueMatches().get(new ClassEntry(className));
179 if (newClassEntry != null) {
180 return newClassEntry.getName();
181 }
182 return null;
183 };
184
185 ClassMapping newClassMapping;
186 String deobfName = oldClassMapping.getDeobfName();
187 if (deobfName != null) {
188 if (useSimpleName) {
189 deobfName = new ClassEntry(deobfName).getSimpleName();
190 }
191 newClassMapping = new ClassMapping(newObfClass.getName(), deobfName);
192 } else {
193 newClassMapping = new ClassMapping(newObfClass.getName());
194 }
195
196 // migrate fields
197 for (FieldMapping oldFieldMapping : oldClassMapping.fields()) {
198 if (canMigrate(oldFieldMapping.getObfType(), matches)) {
199 newClassMapping.addFieldMapping(new FieldMapping(oldFieldMapping, replacer));
200 } else {
201 System.out.println(String.format("Can't map field, dropping: %s.%s %s",
202 oldClassMapping.getDeobfName(),
203 oldFieldMapping.getDeobfName(),
204 oldFieldMapping.getObfType()
205 ));
206 }
207 }
208
209 // migrate methods
210 for (MethodMapping oldMethodMapping : oldClassMapping.methods()) {
211 if (canMigrate(oldMethodMapping.getObfSignature(), matches)) {
212 newClassMapping.addMethodMapping(new MethodMapping(oldMethodMapping, replacer));
213 } else {
214 System.out.println(String.format("Can't map method, dropping: %s.%s %s",
215 oldClassMapping.getDeobfName(),
216 oldMethodMapping.getDeobfName(),
217 oldMethodMapping.getObfSignature()
218 ));
219 }
220 }
221
222 return newClassMapping;
223 }
224
225 private static boolean canMigrate(Signature oldObfSignature, ClassMatches classMatches) {
226 for (Type oldObfType : oldObfSignature.types()) {
227 if (!canMigrate(oldObfType, classMatches)) {
228 return false;
229 }
230 }
231 return true;
232 }
233
234 private static boolean canMigrate(Type oldObfType, ClassMatches classMatches) {
235
236 // non classes can be migrated
237 if (!oldObfType.hasClass()) {
238 return true;
239 }
240
241 // non obfuscated classes can be migrated
242 ClassEntry classEntry = oldObfType.getClassEntry();
243 if (classEntry.getPackageName() != null) {
244 return true;
245 }
246
247 // obfuscated classes with mappings can be migrated
248 return classMatches.getUniqueMatches().containsKey(classEntry);
249 }
250
251 public static void convertMappings(Mappings mappings, BiMap<ClassEntry, ClassEntry> changes) {
252
253 // sort the changes so classes are renamed in the correct order
254 // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b
255 LinkedHashMap<ClassEntry, ClassEntry> sortedChanges = Maps.newLinkedHashMap();
256 int numChangesLeft = changes.size();
257 while (!changes.isEmpty()) {
258 Iterator<Map.Entry<ClassEntry, ClassEntry>> iter = changes.entrySet().iterator();
259 while (iter.hasNext()) {
260 Map.Entry<ClassEntry, ClassEntry> change = iter.next();
261 if (changes.containsKey(change.getValue())) {
262 sortedChanges.put(change.getKey(), change.getValue());
263 iter.remove();
264 }
265 }
266
267 // did we remove any changes?
268 if (numChangesLeft - changes.size() > 0) {
269 // keep going
270 numChangesLeft = changes.size();
271 } else {
272 // can't sort anymore. There must be a loop
273 break;
274 }
275 }
276 if (!changes.isEmpty()) {
277 throw new Error("Unable to sort class changes! There must be a cycle.");
278 }
279
280 // convert the mappings in the correct class order
281 for (Map.Entry<ClassEntry, ClassEntry> entry : sortedChanges.entrySet()) {
282 mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName());
283 }
284 }
285
286 public static Doer<FieldEntry> getFieldDoer() {
287 return new Doer<FieldEntry>() {
288
289 @Override
290 public Collection<FieldEntry> getDroppedEntries(MappingsChecker checker) {
291 return checker.getDroppedFieldMappings().keySet();
292 }
293
294 @Override
295 public Collection<FieldEntry> getObfEntries(JarIndex jarIndex) {
296 return jarIndex.getObfFieldEntries();
297 }
298
299 @Override
300 public Collection<? extends MemberMapping<FieldEntry>> getMappings(ClassMapping destClassMapping) {
301 return (Collection<? extends MemberMapping<FieldEntry>>) destClassMapping.fields();
302 }
303
304 @Override
305 public Set<FieldEntry> filterEntries(Collection<FieldEntry> obfDestFields, FieldEntry obfSourceField, ClassMatches classMatches) {
306 Set<FieldEntry> out = Sets.newHashSet();
307 for (FieldEntry obfDestField : obfDestFields) {
308 Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse());
309 if (translatedDestType.equals(obfSourceField.getType())) {
310 out.add(obfDestField);
311 }
312 }
313 return out;
314 }
315
316 @Override
317 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<FieldEntry> memberMapping, FieldEntry newField) {
318 FieldMapping fieldMapping = (FieldMapping) memberMapping;
319 classMapping.setFieldObfNameAndType(fieldMapping.getObfName(), fieldMapping.getObfType(), newField.getName(), newField.getType());
320 }
321
322 @Override
323 public boolean hasObfMember(ClassMapping classMapping, FieldEntry obfField) {
324 return classMapping.getFieldByObf(obfField.getName(), obfField.getType()) != null;
325 }
326
327 @Override
328 public void removeMemberByObf(ClassMapping classMapping, FieldEntry obfField) {
329 classMapping.removeFieldMapping(classMapping.getFieldByObf(obfField.getName(), obfField.getType()));
330 }
331 };
332 }
333
334 public static Doer<BehaviorEntry> getMethodDoer() {
335 return new Doer<BehaviorEntry>() {
336
337 @Override
338 public Collection<BehaviorEntry> getDroppedEntries(MappingsChecker checker) {
339 return checker.getDroppedMethodMappings().keySet();
340 }
341
342 @Override
343 public Collection<BehaviorEntry> getObfEntries(JarIndex jarIndex) {
344 return jarIndex.getObfBehaviorEntries();
345 }
346
347 @Override
348 public Collection<? extends MemberMapping<BehaviorEntry>> getMappings(ClassMapping destClassMapping) {
349 return (Collection<? extends MemberMapping<BehaviorEntry>>) destClassMapping.methods();
350 }
351
352 @Override
353 public Set<BehaviorEntry> filterEntries(Collection<BehaviorEntry> obfDestFields, BehaviorEntry obfSourceField, ClassMatches classMatches) {
354 Set<BehaviorEntry> out = Sets.newHashSet();
355 for (BehaviorEntry obfDestField : obfDestFields) {
356 // Try to translate the signature
357 Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse());
358 if (translatedDestSignature != null && obfSourceField.getSignature() != null && translatedDestSignature.equals(obfSourceField.getSignature()))
359 out.add(obfDestField);
360 }
361 return out;
362 }
363
364 @Override
365 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<BehaviorEntry> memberMapping, BehaviorEntry newBehavior) {
366 MethodMapping methodMapping = (MethodMapping) memberMapping;
367 classMapping.setMethodObfNameAndSignature(methodMapping.getObfName(), methodMapping.getObfSignature(), newBehavior.getName(), newBehavior.getSignature());
368 }
369
370 @Override
371 public boolean hasObfMember(ClassMapping classMapping, BehaviorEntry obfBehavior) {
372 return classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()) != null;
373 }
374
375 @Override
376 public void removeMemberByObf(ClassMapping classMapping, BehaviorEntry obfBehavior) {
377 classMapping.removeMethodMapping(classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()));
378 }
379 };
380 }
381
382 public static int compareMethodByteCode(CodeIterator sourceIt, CodeIterator destIt) {
383 int sourcePos = 0;
384 int destPos = 0;
385 while (sourceIt.hasNext() && destIt.hasNext()) {
386 try {
387 sourcePos = sourceIt.next();
388 destPos = destIt.next();
389 if (sourceIt.byteAt(sourcePos) != destIt.byteAt(destPos))
390 return sourcePos;
391 } catch (BadBytecode badBytecode) {
392 // Ignore bad bytecode (it might be a little bit dangerous...)
393 }
394 }
395 if (sourcePos < destPos)
396 return sourcePos;
397 else if (destPos < sourcePos)
398 return destPos;
399 return sourcePos;
400 }
401
402 public static BehaviorEntry compareMethods(CtClass destCtClass, CtClass sourceCtClass, BehaviorEntry obfSourceEntry,
403 Set<BehaviorEntry> obfDestEntries) {
404 try {
405 // Get the source method with Javassist
406 CtMethod sourceCtClassMethod = sourceCtClass.getMethod(obfSourceEntry.getName(), obfSourceEntry.getSignature().toString());
407 CodeAttribute sourceAttribute = sourceCtClassMethod.getMethodInfo().getCodeAttribute();
408
409 // Empty method body, ignore!
410 if (sourceAttribute == null)
411 return null;
412 for (BehaviorEntry desEntry : obfDestEntries) {
413 try {
414 CtMethod destCtClassMethod = destCtClass
415 .getMethod(desEntry.getName(), desEntry.getSignature().toString());
416 CodeAttribute destAttribute = destCtClassMethod.getMethodInfo().getCodeAttribute();
417
418 // Ignore empty body methods
419 if (destAttribute == null)
420 continue;
421 CodeIterator destIterator = destAttribute.iterator();
422 int maxPos = compareMethodByteCode(sourceAttribute.iterator(), destIterator);
423
424 // The bytecode is identical to the original method, assuming that the method is correct!
425 if (sourceAttribute.getCodeLength() == (maxPos + 1) && maxPos > 1)
426 return desEntry;
427 } catch (NotFoundException e) {
428 e.printStackTrace();
429 }
430 }
431 } catch (NotFoundException e) {
432 e.printStackTrace();
433 return null;
434 }
435 return null;
436 }
437
438 public static MemberMatches<BehaviorEntry> computeMethodsMatches(Deobfuscator destDeobfuscator,
439 Mappings destMappings,
440 Deobfuscator sourceDeobfuscator,
441 Mappings sourceMappings,
442 ClassMatches classMatches,
443 Doer<BehaviorEntry> doer) {
444
445 MemberMatches<BehaviorEntry> memberMatches = new MemberMatches<>();
446
447 // unmatched source fields are easy
448 MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex());
449 checker.dropBrokenMappings(destMappings);
450 for (BehaviorEntry destObfEntry : doer.getDroppedEntries(checker)) {
451 BehaviorEntry srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse());
452 memberMatches.addUnmatchedSourceEntry(srcObfEntry);
453 }
454
455 // get matched fields (anything that's left after the checks/drops is matched(
456 for (ClassMapping classMapping : destMappings.classes())
457 collectMatchedFields(memberMatches, classMapping, classMatches, doer);
458
459 // get unmatched dest fields
460 doer.getObfEntries(destDeobfuscator.getJarIndex()).stream()
461 .filter(destEntry -> !memberMatches.isMatchedDestEntry(destEntry))
462 .forEach(memberMatches::addUnmatchedDestEntry);
463
464 // Apply mappings to deobfuscator
465
466 // Create type loader
467 TranslatingTypeLoader destTypeLoader = destDeobfuscator.createTypeLoader();
468 TranslatingTypeLoader sourceTypeLoader = sourceDeobfuscator.createTypeLoader();
469
470 System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries...");
471
472 // go through the unmatched source fields and try to pick out the easy matches
473 for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) {
474 for (BehaviorEntry obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) {
475
476 // get the possible dest matches
477 ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass);
478
479 // filter by type/signature
480 Set<BehaviorEntry> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches);
481
482 if (obfDestEntries.size() == 1) {
483 // make the easy match
484 memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next());
485 } else if (obfDestEntries.isEmpty()) {
486 // no match is possible =(
487 memberMatches.makeSourceUnmatchable(obfSourceEntry, null);
488 } else {
489 // Multiple matches! Scan methods instructions
490 CtClass destCtClass = destTypeLoader.loadClass(obfDestClass.getClassName());
491 CtClass sourceCtClass = sourceTypeLoader.loadClass(obfSourceClass.getClassName());
492 BehaviorEntry match = compareMethods(destCtClass, sourceCtClass, obfSourceEntry, obfDestEntries);
493 // the method match correctly, match it on the member mapping!
494 if (match != null)
495 memberMatches.makeMatch(obfSourceEntry, match);
496 }
497 }
498 }
499
500 System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries",
501 memberMatches.getUnmatchedSourceEntries().size(),
502 memberMatches.getUnmatchableSourceEntries().size()
503 ));
504
505 return memberMatches;
506 }
507
508 public static <T extends Entry> MemberMatches<T> computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer<T> doer) {
509
510 MemberMatches<T> memberMatches = new MemberMatches<>();
511
512 // unmatched source fields are easy
513 MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex());
514 checker.dropBrokenMappings(destMappings);
515 for (T destObfEntry : doer.getDroppedEntries(checker)) {
516 T srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse());
517 memberMatches.addUnmatchedSourceEntry(srcObfEntry);
518 }
519
520 // get matched fields (anything that's left after the checks/drops is matched(
521 for (ClassMapping classMapping : destMappings.classes()) {
522 collectMatchedFields(memberMatches, classMapping, classMatches, doer);
523 }
524
525 // get unmatched dest fields
526 for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) {
527 if (!memberMatches.isMatchedDestEntry(destEntry)) {
528 memberMatches.addUnmatchedDestEntry(destEntry);
529 }
530 }
531
532 System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries...");
533
534 // go through the unmatched source fields and try to pick out the easy matches
535 for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) {
536 for (T obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) {
537
538 // get the possible dest matches
539 ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass);
540
541 // filter by type/signature
542 Set<T> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches);
543
544 if (obfDestEntries.size() == 1) {
545 // make the easy match
546 memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next());
547 } else if (obfDestEntries.isEmpty()) {
548 // no match is possible =(
549 memberMatches.makeSourceUnmatchable(obfSourceEntry, null);
550 }
551 }
552 }
553
554 System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries",
555 memberMatches.getUnmatchedSourceEntries().size(),
556 memberMatches.getUnmatchableSourceEntries().size()
557 ));
558
559 return memberMatches;
560 }
561
562 private static <T extends Entry> void collectMatchedFields(MemberMatches<T> memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer<T> doer) {
563
564 // get the fields for this class
565 for (MemberMapping<T> destEntryMapping : doer.getMappings(destClassMapping)) {
566 T destObfField = destEntryMapping.getObfEntry(destClassMapping.getObfEntry());
567 T srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse());
568 memberMatches.addMatch(srcObfField, destObfField);
569 }
570
571 // recurse
572 for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) {
573 collectMatchedFields(memberMatches, destInnerClassMapping, classMatches, doer);
574 }
575 }
576
577 @SuppressWarnings("unchecked")
578 private static <T extends Entry> T translate(T in, BiMap<ClassEntry, ClassEntry> map) {
579 if (in instanceof FieldEntry) {
580 return (T) new FieldEntry(
581 map.get(in.getClassEntry()),
582 in.getName(),
583 translate(((FieldEntry) in).getType(), map)
584 );
585 } else if (in instanceof MethodEntry) {
586 return (T) new MethodEntry(
587 map.get(in.getClassEntry()),
588 in.getName(),
589 translate(((MethodEntry) in).getSignature(), map)
590 );
591 } else if (in instanceof ConstructorEntry) {
592 return (T) new ConstructorEntry(
593 map.get(in.getClassEntry()),
594 translate(((ConstructorEntry) in).getSignature(), map)
595 );
596 }
597 throw new Error("Unhandled entry type: " + in.getClass());
598 }
599
600 private static Type translate(Type type, final BiMap<ClassEntry, ClassEntry> map) {
601 return new Type(type, inClassName ->
602 {
603 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName));
604 if (outClassEntry == null) {
605 return null;
606 }
607 return outClassEntry.getName();
608 });
609 }
610
611 private static Signature translate(Signature signature, final BiMap<ClassEntry, ClassEntry> map) {
612 if (signature == null) {
613 return null;
614 }
615 return new Signature(signature, inClassName ->
616 {
617 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName));
618 if (outClassEntry == null) {
619 return null;
620 }
621 return outClassEntry.getName();
622 });
623 }
624
625 public static <T extends Entry> void applyMemberMatches(Mappings mappings, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) {
626 for (ClassMapping classMapping : mappings.classes()) {
627 applyMemberMatches(classMapping, classMatches, memberMatches, doer);
628 }
629 }
630
631 private static <T extends Entry> void applyMemberMatches(ClassMapping classMapping, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) {
632
633 // get the classes
634 ClassEntry obfDestClass = new ClassEntry(classMapping.getObfFullName());
635
636 // make a map of all the renames we need to make
637 Map<T, T> renames = Maps.newHashMap();
638 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) {
639 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass);
640 T obfSourceEntry = getSourceEntryFromDestMapping(memberMapping, obfDestClass, classMatches);
641
642 // but drop the unmatchable things
643 if (memberMatches.isUnmatchableSourceEntry(obfSourceEntry)) {
644 doer.removeMemberByObf(classMapping, obfOldDestEntry);
645 continue;
646 }
647
648 T obfNewDestEntry = memberMatches.matches().get(obfSourceEntry);
649 if (obfNewDestEntry != null && !obfOldDestEntry.getName().equals(obfNewDestEntry.getName())) {
650 renames.put(obfOldDestEntry, obfNewDestEntry);
651 }
652 }
653
654 if (!renames.isEmpty()) {
655
656 // apply to this class (should never need more than n passes)
657 int numRenamesAppliedThisRound;
658 do {
659 numRenamesAppliedThisRound = 0;
660
661 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) {
662 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass);
663 T obfNewDestEntry = renames.get(obfOldDestEntry);
664 if (obfNewDestEntry != null) {
665 // make sure this rename won't cause a collision
666 // otherwise, save it for the next round and try again next time
667 if (!doer.hasObfMember(classMapping, obfNewDestEntry)) {
668 doer.setUpdateObfMember(classMapping, memberMapping, obfNewDestEntry);
669 renames.remove(obfOldDestEntry);
670 numRenamesAppliedThisRound++;
671 }
672 }
673 }
674 } while (numRenamesAppliedThisRound > 0);
675
676 if (!renames.isEmpty()) {
677 System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d renames left.",
678 classMapping.getObfFullName(), renames.size()
679 ));
680 for (Map.Entry<T, T> entry : renames.entrySet()) {
681 System.err.println(String.format("\t%s -> %s", entry.getKey().getName(), entry.getValue().getName()));
682 }
683 }
684 }
685
686 // recurse
687 for (ClassMapping innerClassMapping : classMapping.innerClasses()) {
688 applyMemberMatches(innerClassMapping, classMatches, memberMatches, doer);
689 }
690 }
691
692 private static <T extends Entry> T getSourceEntryFromDestMapping(MemberMapping<T> destMemberMapping, ClassEntry obfDestClass, ClassMatches classMatches) {
693 return translate(destMemberMapping.getObfEntry(obfDestClass), classMatches.getUniqueMatches().inverse());
694 }
695
696 public interface Doer<T extends Entry> {
697 Collection<T> getDroppedEntries(MappingsChecker checker);
698
699 Collection<T> getObfEntries(JarIndex jarIndex);
700
701 Collection<? extends MemberMapping<T>> getMappings(ClassMapping destClassMapping);
702
703 Set<T> filterEntries(Collection<T> obfEntries, T obfSourceEntry, ClassMatches classMatches);
704
705 void setUpdateObfMember(ClassMapping classMapping, MemberMapping<T> memberMapping, T newEntry);
706
707 boolean hasObfMember(ClassMapping classMapping, T obfEntry);
708
709 void removeMemberByObf(ClassMapping classMapping, T obfEntry);
710 }
711}
diff --git a/src/main/java/cuchaz/enigma/convert/MatchesReader.java b/src/main/java/cuchaz/enigma/convert/MatchesReader.java
deleted file mode 100644
index 1cf50fa..0000000
--- a/src/main/java/cuchaz/enigma/convert/MatchesReader.java
+++ /dev/null
@@ -1,105 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.Lists;
15import cuchaz.enigma.mapping.*;
16
17import java.io.*;
18import java.nio.charset.Charset;
19import java.util.Collection;
20import java.util.List;
21
22public class MatchesReader {
23
24 public static ClassMatches readClasses(File file)
25 throws IOException {
26 try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")))) {
27 ClassMatches matches = new ClassMatches();
28 String line;
29 while ((line = in.readLine()) != null) {
30 matches.add(readClassMatch(line));
31 }
32 return matches;
33 }
34 }
35
36 private static ClassMatch readClassMatch(String line) {
37 String[] sides = line.split(":", 2);
38 return new ClassMatch(readClasses(sides[0]), readClasses(sides[1]));
39 }
40
41 private static Collection<ClassEntry> readClasses(String in) {
42 List<ClassEntry> entries = Lists.newArrayList();
43 for (String className : in.split(",")) {
44 className = className.trim();
45 if (!className.isEmpty()) {
46 entries.add(new ClassEntry(className));
47 }
48 }
49 return entries;
50 }
51
52 public static <T extends Entry> MemberMatches<T> readMembers(File file)
53 throws IOException {
54 try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")))) {
55 MemberMatches<T> matches = new MemberMatches<>();
56 String line;
57 while ((line = in.readLine()) != null) {
58 readMemberMatch(matches, line);
59 }
60 return matches;
61 }
62 }
63
64 private static <T extends Entry> void readMemberMatch(MemberMatches<T> matches, String line) {
65 if (line.startsWith("!")) {
66 T source = readEntry(line.substring(1));
67 matches.addUnmatchableSourceEntry(source);
68 } else {
69 String[] parts = line.split(":", 2);
70 T source = readEntry(parts[0]);
71 T dest = readEntry(parts[1]);
72 if (source != null && dest != null) {
73 matches.addMatch(source, dest);
74 } else if (source != null) {
75 matches.addUnmatchedSourceEntry(source);
76 } else if (dest != null) {
77 matches.addUnmatchedDestEntry(dest);
78 }
79 }
80 }
81
82 @SuppressWarnings("unchecked")
83 private static <T extends Entry> T readEntry(String in) {
84 if (in.length() <= 0) {
85 return null;
86 }
87 String[] parts = in.split(" ");
88 if (parts.length == 3 && parts[2].indexOf('(') < 0) {
89 return (T) new FieldEntry(
90 new ClassEntry(parts[0]),
91 parts[1],
92 new Type(parts[2])
93 );
94 } else {
95 assert (parts.length == 2 || parts.length == 3);
96 if (parts.length == 2) {
97 return (T) EntryFactory.getBehaviorEntry(parts[0], parts[1]);
98 } else if (parts.length == 3) {
99 return (T) EntryFactory.getBehaviorEntry(parts[0], parts[1], parts[2]);
100 } else {
101 throw new Error("Malformed behavior entry: " + in);
102 }
103 }
104 }
105}
diff --git a/src/main/java/cuchaz/enigma/convert/MatchesWriter.java b/src/main/java/cuchaz/enigma/convert/MatchesWriter.java
deleted file mode 100644
index 8fe7326..0000000
--- a/src/main/java/cuchaz/enigma/convert/MatchesWriter.java
+++ /dev/null
@@ -1,123 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import cuchaz.enigma.mapping.BehaviorEntry;
15import cuchaz.enigma.mapping.ClassEntry;
16import cuchaz.enigma.mapping.Entry;
17import cuchaz.enigma.mapping.FieldEntry;
18
19import java.io.File;
20import java.io.FileOutputStream;
21import java.io.IOException;
22import java.io.OutputStreamWriter;
23import java.nio.charset.Charset;
24import java.util.Map;
25
26public class MatchesWriter {
27
28 public static void writeClasses(ClassMatches matches, File file)
29 throws IOException {
30 try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8"))) {
31 for (ClassMatch match : matches) {
32 writeClassMatch(out, match);
33 }
34 }
35 }
36
37 private static void writeClassMatch(OutputStreamWriter out, ClassMatch match)
38 throws IOException {
39 writeClasses(out, match.sourceClasses);
40 out.write(":");
41 writeClasses(out, match.destClasses);
42 out.write("\n");
43 }
44
45 private static void writeClasses(OutputStreamWriter out, Iterable<ClassEntry> classes)
46 throws IOException {
47 boolean isFirst = true;
48 for (ClassEntry entry : classes) {
49 if (isFirst) {
50 isFirst = false;
51 } else {
52 out.write(",");
53 }
54 out.write(entry.toString());
55 }
56 }
57
58 public static <T extends Entry> void writeMembers(MemberMatches<T> matches, File file)
59 throws IOException {
60 try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8"))) {
61 for (Map.Entry<T, T> match : matches.matches().entrySet()) {
62 writeMemberMatch(out, match.getKey(), match.getValue());
63 }
64 for (T entry : matches.getUnmatchedSourceEntries()) {
65 writeMemberMatch(out, entry, null);
66 }
67 for (T entry : matches.getUnmatchedDestEntries()) {
68 writeMemberMatch(out, null, entry);
69 }
70 for (T entry : matches.getUnmatchableSourceEntries()) {
71 writeUnmatchableEntry(out, entry);
72 }
73 }
74 }
75
76 private static <T extends Entry> void writeMemberMatch(OutputStreamWriter out, T source, T dest)
77 throws IOException {
78 if (source != null) {
79 writeEntry(out, source);
80 }
81 out.write(":");
82 if (dest != null) {
83 writeEntry(out, dest);
84 }
85 out.write("\n");
86 }
87
88 private static <T extends Entry> void writeUnmatchableEntry(OutputStreamWriter out, T entry)
89 throws IOException {
90 out.write("!");
91 writeEntry(out, entry);
92 out.write("\n");
93 }
94
95 private static <T extends Entry> void writeEntry(OutputStreamWriter out, T entry)
96 throws IOException {
97 if (entry instanceof FieldEntry) {
98 writeField(out, (FieldEntry) entry);
99 } else if (entry instanceof BehaviorEntry) {
100 writeBehavior(out, (BehaviorEntry) entry);
101 }
102 }
103
104 private static void writeField(OutputStreamWriter out, FieldEntry fieldEntry)
105 throws IOException {
106 out.write(fieldEntry.getClassName());
107 out.write(" ");
108 out.write(fieldEntry.getName());
109 out.write(" ");
110 out.write(fieldEntry.getType().toString());
111 }
112
113 private static void writeBehavior(OutputStreamWriter out, BehaviorEntry behaviorEntry)
114 throws IOException {
115 out.write(behaviorEntry.getClassName());
116 out.write(" ");
117 out.write(behaviorEntry.getName());
118 out.write(" ");
119 if (behaviorEntry.getSignature() != null) {
120 out.write(behaviorEntry.getSignature().toString());
121 }
122 }
123}
diff --git a/src/main/java/cuchaz/enigma/convert/MemberMatches.java b/src/main/java/cuchaz/enigma/convert/MemberMatches.java
deleted file mode 100644
index bd74311..0000000
--- a/src/main/java/cuchaz/enigma/convert/MemberMatches.java
+++ /dev/null
@@ -1,179 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.convert;
13
14import com.google.common.collect.*;
15import cuchaz.enigma.Deobfuscator;
16import cuchaz.enigma.mapping.ClassEntry;
17import cuchaz.enigma.mapping.Entry;
18
19import java.util.Collection;
20import java.util.Set;
21
22public class MemberMatches<T extends Entry> {
23
24 private BiMap<T, T> matches;
25 private Multimap<ClassEntry, T> matchedSourceEntries;
26 private Multimap<ClassEntry, T> unmatchedSourceEntries;
27 private Multimap<ClassEntry, T> unmatchedDestEntries;
28 private Multimap<ClassEntry, T> unmatchableSourceEntries;
29
30 public MemberMatches() {
31 matches = HashBiMap.create();
32 matchedSourceEntries = HashMultimap.create();
33 unmatchedSourceEntries = HashMultimap.create();
34 unmatchedDestEntries = HashMultimap.create();
35 unmatchableSourceEntries = HashMultimap.create();
36 }
37
38 public void addMatch(T srcEntry, T destEntry) {
39 boolean wasAdded = matches.put(srcEntry, destEntry) == null;
40 assert (wasAdded);
41 wasAdded = matchedSourceEntries.put(srcEntry.getClassEntry(), srcEntry);
42 assert (wasAdded);
43 }
44
45 public void addUnmatchedSourceEntry(T sourceEntry) {
46 boolean wasAdded = unmatchedSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry);
47 assert (wasAdded);
48 }
49
50 public void addUnmatchedSourceEntries(Iterable<T> sourceEntries) {
51 for (T sourceEntry : sourceEntries) {
52 addUnmatchedSourceEntry(sourceEntry);
53 }
54 }
55
56 public void addUnmatchedDestEntry(T destEntry) {
57 if (destEntry.getName().equals("<clinit>") || destEntry.getName().equals("<init>"))
58 return;
59 boolean wasAdded = unmatchedDestEntries.put(destEntry.getClassEntry(), destEntry);
60 assert (wasAdded);
61 }
62
63 public void addUnmatchedDestEntries(Iterable<T> destEntriesntries) {
64 for (T entry : destEntriesntries) {
65 addUnmatchedDestEntry(entry);
66 }
67 }
68
69 public void addUnmatchableSourceEntry(T sourceEntry) {
70 boolean wasAdded = unmatchableSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry);
71 assert (wasAdded);
72 }
73
74 public Set<ClassEntry> getSourceClassesWithUnmatchedEntries() {
75 return unmatchedSourceEntries.keySet();
76 }
77
78 public Collection<ClassEntry> getSourceClassesWithoutUnmatchedEntries() {
79 Set<ClassEntry> out = Sets.newHashSet();
80 out.addAll(matchedSourceEntries.keySet());
81 out.removeAll(unmatchedSourceEntries.keySet());
82 return out;
83 }
84
85 public Collection<T> getUnmatchedSourceEntries() {
86 return unmatchedSourceEntries.values();
87 }
88
89 public Collection<T> getUnmatchedSourceEntries(ClassEntry sourceClass) {
90 return unmatchedSourceEntries.get(sourceClass);
91 }
92
93 public Collection<T> getUnmatchedDestEntries() {
94 return unmatchedDestEntries.values();
95 }
96
97 public Collection<T> getUnmatchedDestEntries(ClassEntry destClass) {
98 return unmatchedDestEntries.get(destClass);
99 }
100
101 public Collection<T> getUnmatchableSourceEntries() {
102 return unmatchableSourceEntries.values();
103 }
104
105 public boolean hasSource(T sourceEntry) {
106 return matches.containsKey(sourceEntry) || unmatchedSourceEntries.containsValue(sourceEntry);
107 }
108
109 public boolean hasDest(T destEntry) {
110 return matches.containsValue(destEntry) || unmatchedDestEntries.containsValue(destEntry);
111 }
112
113 public BiMap<T, T> matches() {
114 return matches;
115 }
116
117 public boolean isMatchedSourceEntry(T sourceEntry) {
118 return matches.containsKey(sourceEntry);
119 }
120
121 public boolean isMatchedDestEntry(T destEntry) {
122 return matches.containsValue(destEntry);
123 }
124
125 public boolean isUnmatchableSourceEntry(T sourceEntry) {
126 return unmatchableSourceEntries.containsEntry(sourceEntry.getClassEntry(), sourceEntry);
127 }
128
129 public void makeMatch(T sourceEntry, T destEntry) {
130 makeMatch(sourceEntry, destEntry, null, null);
131 }
132
133 public void makeMatch(T sourceEntry, T destEntry, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
134 if (sourceDeobfuscator != null && destDeobfuscator != null) {
135 makeMatch(sourceEntry, destEntry);
136 sourceEntry = (T) sourceEntry.cloneToNewClass(sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true));
137 destEntry = (T) destEntry.cloneToNewClass(destDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(destEntry, true));
138 }
139 boolean wasRemoved = unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
140 assert (wasRemoved);
141 wasRemoved = unmatchedDestEntries.remove(destEntry.getClassEntry(), destEntry);
142 assert (wasRemoved);
143 addMatch(sourceEntry, destEntry);
144 }
145
146 public boolean isMatched(T sourceEntry, T destEntry) {
147 T match = matches.get(sourceEntry);
148 return match != null && match.equals(destEntry);
149 }
150
151 public void unmakeMatch(T sourceEntry, T destEntry, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
152 if (sourceDeobfuscator != null && destDeobfuscator != null) {
153 unmakeMatch(sourceEntry, destEntry, null, null);
154 sourceEntry = (T) sourceEntry.cloneToNewClass(
155 sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true));
156 destEntry = (T) destEntry.cloneToNewClass(
157 destDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(destEntry, true));
158 }
159
160 boolean wasRemoved = matches.remove(sourceEntry) != null;
161 assert (wasRemoved);
162 wasRemoved = matchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
163 assert (wasRemoved);
164 addUnmatchedSourceEntry(sourceEntry);
165 addUnmatchedDestEntry(destEntry);
166 }
167
168 public void makeSourceUnmatchable(T sourceEntry, Deobfuscator sourceDeobfuscator) {
169 if (sourceDeobfuscator != null) {
170 makeSourceUnmatchable(sourceEntry, null);
171 sourceEntry = (T) sourceEntry.cloneToNewClass(
172 sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true));
173 }
174 assert (!isMatchedSourceEntry(sourceEntry));
175 boolean wasRemoved = unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
176 assert (wasRemoved);
177 addUnmatchableSourceEntry(sourceEntry);
178 }
179}