summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/convert/ClassIdentity.java
diff options
context:
space:
mode:
authorGravatar lclc982016-07-04 18:14:22 +1000
committerGravatar lclc982016-07-04 18:14:22 +1000
commit59e189bef2b5e6d129fb7c2c988ed0b2130e36ac (patch)
tree2b638e60905251de85a4917152d6fc39a4112194 /src/main/java/cuchaz/enigma/convert/ClassIdentity.java
parentFixed Obf Class list order (diff)
downloadenigma-fork-59e189bef2b5e6d129fb7c2c988ed0b2130e36ac.tar.gz
enigma-fork-59e189bef2b5e6d129fb7c2c988ed0b2130e36ac.tar.xz
enigma-fork-59e189bef2b5e6d129fb7c2c988ed0b2130e36ac.zip
Reformat
Diffstat (limited to 'src/main/java/cuchaz/enigma/convert/ClassIdentity.java')
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassIdentity.java441
1 files changed, 0 insertions, 441 deletions
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 606c1df..0000000
--- a/src/main/java/cuchaz/enigma/convert/ClassIdentity.java
+++ /dev/null
@@ -1,441 +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 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.*;
14
15import java.io.UnsupportedEncodingException;
16import java.security.MessageDigest;
17import java.security.NoSuchAlgorithmException;
18import java.util.Enumeration;
19import java.util.List;
20import java.util.Map;
21import java.util.Set;
22
23import cuchaz.enigma.Constants;
24import cuchaz.enigma.Util;
25import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
26import cuchaz.enigma.analysis.EntryReference;
27import cuchaz.enigma.analysis.JarIndex;
28import cuchaz.enigma.bytecode.ConstPoolEditor;
29import cuchaz.enigma.bytecode.InfoType;
30import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
31import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
32import cuchaz.enigma.mapping.*;
33import javassist.*;
34import javassist.bytecode.*;
35import javassist.expr.*;
36
37public class ClassIdentity {
38
39 private ClassEntry classEntry;
40 private SidedClassNamer namer;
41 private Multiset<String> fields;
42 private Multiset<String> methods;
43 private Multiset<String> constructors;
44 private String staticInitializer;
45 private String extendz;
46 private Multiset<String> implementz;
47 private Set<String> stringLiterals;
48 private Multiset<String> implementations;
49 private Multiset<String> references;
50 private String outer;
51
52 private final ClassNameReplacer m_classNameReplacer = new ClassNameReplacer() {
53
54 private Map<String, String> m_classNames = Maps.newHashMap();
55
56 @Override
57 public String replace(String className) {
58
59 // classes not in the none package can be passed through
60 ClassEntry classEntry = new ClassEntry(className);
61 if (!classEntry.getPackageName().equals(Constants.NONE_PACKAGE)) {
62 return className;
63 }
64
65 // is this class ourself?
66 if (className.equals(classEntry.getName())) {
67 return "CSelf";
68 }
69
70 // try the namer
71 if (namer != null) {
72 String newName = namer.getName(className);
73 if (newName != null) {
74 return newName;
75 }
76 }
77
78 // otherwise, use local naming
79 if (!m_classNames.containsKey(className)) {
80 m_classNames.put(className, getNewClassName());
81 }
82 return m_classNames.get(className);
83 }
84
85 private String getNewClassName() {
86 return String.format("C%03d", m_classNames.size());
87 }
88 };
89
90 public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) {
91 this.namer = namer;
92
93 // stuff from the bytecode
94
95 this.classEntry = EntryFactory.getClassEntry(c);
96 this.fields = HashMultiset.create();
97 for (CtField field : c.getDeclaredFields()) {
98 this.fields.add(scrubType(field.getSignature()));
99 }
100 this.methods = HashMultiset.create();
101 for (CtMethod method : c.getDeclaredMethods()) {
102 this.methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method));
103 }
104 this.constructors = HashMultiset.create();
105 for (CtConstructor constructor : c.getDeclaredConstructors()) {
106 this.constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor));
107 }
108 this.staticInitializer = "";
109 if (c.getClassInitializer() != null) {
110 this.staticInitializer = getBehaviorSignature(c.getClassInitializer());
111 }
112 this.extendz = "";
113 if (c.getClassFile().getSuperclass() != null) {
114 this.extendz = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
115 }
116 this.implementz = HashMultiset.create();
117 for (String interfaceName : c.getClassFile().getInterfaces()) {
118 this.implementz.add(scrubClassName(Descriptor.toJvmName(interfaceName)));
119 }
120
121 this.stringLiterals = Sets.newHashSet();
122 ConstPool constants = c.getClassFile().getConstPool();
123 for (int i = 1; i < constants.getSize(); i++) {
124 if (constants.getTag(i) == ConstPool.CONST_String) {
125 this.stringLiterals.add(constants.getStringInfo(i));
126 }
127 }
128
129 // stuff from the jar index
130
131 this.implementations = HashMultiset.create();
132 ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, this.classEntry);
133 if (implementationsNode != null) {
134 @SuppressWarnings("unchecked")
135 Enumeration<ClassImplementationsTreeNode> implementations = implementationsNode.children();
136 while (implementations.hasMoreElements()) {
137 ClassImplementationsTreeNode node = implementations.nextElement();
138 this.implementations.add(scrubClassName(node.getClassEntry().getName()));
139 }
140 }
141
142 this.references = HashMultiset.create();
143 if (useReferences) {
144 for (CtField field : c.getDeclaredFields()) {
145 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
146 index.getFieldReferences(fieldEntry).forEach(this::addReference);
147 }
148 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
149 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
150 index.getBehaviorReferences(behaviorEntry).forEach(this::addReference);
151 }
152 }
153
154 this.outer = null;
155 if (this.classEntry.isInnerClass()) {
156 this.outer = this.classEntry.getOuterClassName();
157 }
158 }
159
160 private void addReference(EntryReference<? extends Entry, BehaviorEntry> reference) {
161 if (reference.context.getSignature() != null) {
162 this.references.add(String.format("%s_%s",
163 scrubClassName(reference.context.getClassName()),
164 scrubSignature(reference.context.getSignature())
165 ));
166 } else {
167 this.references.add(String.format("%s_<clinit>",
168 scrubClassName(reference.context.getClassName())
169 ));
170 }
171 }
172
173 public ClassEntry getClassEntry() {
174 return this.classEntry;
175 }
176
177 @Override
178 public String toString() {
179 StringBuilder buf = new StringBuilder();
180 buf.append("class: ");
181 buf.append(this.classEntry.getName());
182 buf.append(" ");
183 buf.append(hashCode());
184 buf.append("\n");
185 for (String field : this.fields) {
186 buf.append("\tfield ");
187 buf.append(field);
188 buf.append("\n");
189 }
190 for (String method : this.methods) {
191 buf.append("\tmethod ");
192 buf.append(method);
193 buf.append("\n");
194 }
195 for (String constructor : this.constructors) {
196 buf.append("\tconstructor ");
197 buf.append(constructor);
198 buf.append("\n");
199 }
200 if (this.staticInitializer.length() > 0) {
201 buf.append("\tinitializer ");
202 buf.append(this.staticInitializer);
203 buf.append("\n");
204 }
205 if (this.extendz.length() > 0) {
206 buf.append("\textends ");
207 buf.append(this.extendz);
208 buf.append("\n");
209 }
210 for (String interfaceName : this.implementz) {
211 buf.append("\timplements ");
212 buf.append(interfaceName);
213 buf.append("\n");
214 }
215 for (String implementation : this.implementations) {
216 buf.append("\timplemented by ");
217 buf.append(implementation);
218 buf.append("\n");
219 }
220 for (String reference : this.references) {
221 buf.append("\treference ");
222 buf.append(reference);
223 buf.append("\n");
224 }
225 buf.append("\touter ");
226 buf.append(this.outer);
227 buf.append("\n");
228 return buf.toString();
229 }
230
231 private String scrubClassName(String className) {
232 return m_classNameReplacer.replace(className);
233 }
234
235 private String scrubType(String typeName) {
236 return scrubType(new Type(typeName)).toString();
237 }
238
239 private Type scrubType(Type type) {
240 if (type.hasClass()) {
241 return new Type(type, m_classNameReplacer);
242 } else {
243 return type;
244 }
245 }
246
247 private String scrubSignature(String signature) {
248 return scrubSignature(new Signature(signature)).toString();
249 }
250
251 private Signature scrubSignature(Signature signature) {
252 return new Signature(signature, m_classNameReplacer);
253 }
254
255 private boolean isClassMatchedUniquely(String className) {
256 return this.namer != null && this.namer.getName(Descriptor.toJvmName(className)) != null;
257 }
258
259 private String getBehaviorSignature(CtBehavior behavior) {
260 try {
261 // does this method have an implementation?
262 if (behavior.getMethodInfo().getCodeAttribute() == null) {
263 return "(none)";
264 }
265
266 // compute the hash from the opcodes
267 ConstPool constants = behavior.getMethodInfo().getConstPool();
268 final MessageDigest digest = MessageDigest.getInstance("MD5");
269 CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator();
270 while (iter.hasNext()) {
271 int pos = iter.next();
272
273 // update the hash with the opcode
274 int opcode = iter.byteAt(pos);
275 digest.update((byte) opcode);
276
277 switch (opcode) {
278 case Opcode.LDC: {
279 int constIndex = iter.byteAt(pos + 1);
280 updateHashWithConstant(digest, constants, constIndex);
281 }
282 break;
283
284 case Opcode.LDC_W:
285 case Opcode.LDC2_W: {
286 int constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2);
287 updateHashWithConstant(digest, constants, constIndex);
288 }
289 break;
290 }
291 }
292
293 // update hash with method and field accesses
294 behavior.instrument(new ExprEditor() {
295 @Override
296 public void edit(MethodCall call) {
297 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
298 updateHashWithString(digest, scrubSignature(call.getSignature()));
299 if (isClassMatchedUniquely(call.getClassName())) {
300 updateHashWithString(digest, call.getMethodName());
301 }
302 }
303
304 @Override
305 public void edit(FieldAccess access) {
306 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName())));
307 updateHashWithString(digest, scrubType(access.getSignature()));
308 if (isClassMatchedUniquely(access.getClassName())) {
309 updateHashWithString(digest, access.getFieldName());
310 }
311 }
312
313 @Override
314 public void edit(ConstructorCall call) {
315 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
316 updateHashWithString(digest, scrubSignature(call.getSignature()));
317 }
318
319 @Override
320 public void edit(NewExpr expr) {
321 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName())));
322 }
323 });
324
325 // convert the hash to a hex string
326 return toHex(digest.digest());
327 } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) {
328 throw new Error(ex);
329 }
330 }
331
332 private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) {
333 ConstPoolEditor editor = new ConstPoolEditor(constants);
334 ConstInfoAccessor item = editor.getItem(index);
335 if (item.getType() == InfoType.StringInfo) {
336 updateHashWithString(digest, constants.getStringInfo(index));
337 }
338 // TODO: other constants
339 }
340
341 private void updateHashWithString(MessageDigest digest, String val) {
342 try {
343 digest.update(val.getBytes("UTF8"));
344 } catch (UnsupportedEncodingException ex) {
345 throw new Error(ex);
346 }
347 }
348
349 private String toHex(byte[] bytes) {
350 // function taken from:
351 // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java
352 final char[] hexArray = "0123456789ABCDEF".toCharArray();
353 char[] hexChars = new char[bytes.length * 2];
354 for (int j = 0; j < bytes.length; j++) {
355 int v = bytes[j] & 0xFF;
356 hexChars[j * 2] = hexArray[v >>> 4];
357 hexChars[j * 2 + 1] = hexArray[v & 0x0F];
358 }
359 return new String(hexChars);
360 }
361
362 @Override
363 public boolean equals(Object other) {
364 return other instanceof ClassIdentity && equals((ClassIdentity) other);
365 }
366
367 public boolean equals(ClassIdentity other) {
368 return this.fields.equals(other.fields)
369 && this.methods.equals(other.methods)
370 && this.constructors.equals(other.constructors)
371 && this.staticInitializer.equals(other.staticInitializer)
372 && this.extendz.equals(other.extendz)
373 && this.implementz.equals(other.implementz)
374 && this.implementations.equals(other.implementations)
375 && this.references.equals(other.references);
376 }
377
378 @Override
379 public int hashCode() {
380 List<Object> objs = Lists.newArrayList();
381 objs.addAll(this.fields);
382 objs.addAll(this.methods);
383 objs.addAll(this.constructors);
384 objs.add(this.staticInitializer);
385 objs.add(this.extendz);
386 objs.addAll(this.implementz);
387 objs.addAll(this.implementations);
388 objs.addAll(this.references);
389 return Util.combineHashesOrdered(objs);
390 }
391
392 public int getMatchScore(ClassIdentity other) {
393 return 2 * getNumMatches(this.extendz, other.extendz)
394 + 2 * getNumMatches(this.outer, other.outer)
395 + 2 * getNumMatches(this.implementz, other.implementz)
396 + getNumMatches(this.stringLiterals, other.stringLiterals)
397 + getNumMatches(this.fields, other.fields)
398 + getNumMatches(this.methods, other.methods)
399 + getNumMatches(this.constructors, other.constructors);
400 }
401
402 public int getMaxMatchScore() {
403 return 2 + 2 + 2 * this.implementz.size() + this.stringLiterals.size() + this.fields.size() + this.methods.size() + this.constructors.size();
404 }
405
406 public boolean matches(CtClass c) {
407 // just compare declaration counts
408 return this.fields.size() == c.getDeclaredFields().length
409 && this.methods.size() == c.getDeclaredMethods().length
410 && this.constructors.size() == c.getDeclaredConstructors().length;
411 }
412
413 private int getNumMatches(Set<String> a, Set<String> b) {
414 int numMatches = 0;
415 for (String val : a) {
416 if (b.contains(val)) {
417 numMatches++;
418 }
419 }
420 return numMatches;
421 }
422
423 private int getNumMatches(Multiset<String> a, Multiset<String> b) {
424 int numMatches = 0;
425 for (String val : a) {
426 if (b.contains(val)) {
427 numMatches++;
428 }
429 }
430 return numMatches;
431 }
432
433 private int getNumMatches(String a, String b) {
434 if (a == null && b == null) {
435 return 1;
436 } else if (a != null && b != null && a.equals(b)) {
437 return 1;
438 }
439 return 0;
440 }
441}