summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java')
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java514
1 files changed, 514 insertions, 0 deletions
diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java b/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java
new file mode 100644
index 0000000..548bea7
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java
@@ -0,0 +1,514 @@
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.bytecode;
12
13import java.lang.reflect.InvocationTargetException;
14import java.lang.reflect.Method;
15import java.util.Arrays;
16import java.util.HashMap;
17import java.util.List;
18import java.util.Map;
19
20import cuchaz.enigma.mapping.ClassEntry;
21import cuchaz.enigma.mapping.ClassNameReplacer;
22import cuchaz.enigma.mapping.Translator;
23import javassist.CtClass;
24import javassist.bytecode.*;
25import javassist.bytecode.SignatureAttribute.*;
26
27public class ClassRenamer {
28
29 private enum SignatureType {
30 Class {
31 @Override
32 public String rename(String signature, ReplacerClassMap map) {
33 return renameClassSignature(signature, map);
34 }
35 },
36 Field {
37 @Override
38 public String rename(String signature, ReplacerClassMap map) {
39 return renameFieldSignature(signature, map);
40 }
41 },
42 Method {
43 @Override
44 public String rename(String signature, ReplacerClassMap map) {
45 return renameMethodSignature(signature, map);
46 }
47 };
48
49 public abstract String rename(String signature, ReplacerClassMap map);
50 }
51
52 private static class ReplacerClassMap extends HashMap<String, String> {
53
54 private static final long serialVersionUID = 317915213205066168L;
55
56 private ClassNameReplacer m_replacer;
57
58 public ReplacerClassMap(ClassNameReplacer replacer) {
59 m_replacer = replacer;
60 }
61
62 @Override
63 public String get(Object obj) {
64 if (obj instanceof String) {
65 return get((String) obj);
66 }
67 return null;
68 }
69
70 public String get(String className) {
71 return m_replacer.replace(className);
72 }
73 }
74
75 public static void renameClasses(CtClass c, final Translator translator) {
76 renameClasses(c, className -> {
77 ClassEntry entry = translator.translateEntry(new ClassEntry(className));
78 if (entry != null) {
79 return entry.getName();
80 }
81 return null;
82 });
83 }
84
85 public static void moveAllClassesOutOfDefaultPackage(CtClass c, final String newPackageName) {
86 renameClasses(c, className -> {
87 ClassEntry entry = new ClassEntry(className);
88 if (entry.isInDefaultPackage()) {
89 return newPackageName + "/" + entry.getName();
90 }
91 return null;
92 });
93 }
94
95 public static void moveAllClassesIntoDefaultPackage(CtClass c, final String oldPackageName) {
96 renameClasses(c, className -> {
97 ClassEntry entry = new ClassEntry(className);
98 if (entry.getPackageName().equals(oldPackageName)) {
99 return entry.getSimpleName();
100 }
101 return null;
102 });
103 }
104
105 @SuppressWarnings("unchecked")
106 public static void renameClasses(CtClass c, ClassNameReplacer replacer) {
107
108 // sadly, we can't use CtClass.renameClass() because SignatureAttribute.renameClass() is extremely buggy =(
109
110 ReplacerClassMap map = new ReplacerClassMap(replacer);
111 ClassFile classFile = c.getClassFile();
112
113 // rename the constant pool (covers ClassInfo, MethodTypeInfo, and NameAndTypeInfo)
114 ConstPool constPool = c.getClassFile().getConstPool();
115 constPool.renameClass(map);
116
117 // rename class attributes
118 renameAttributes(classFile.getAttributes(), map, SignatureType.Class);
119
120 // rename methods
121 for (MethodInfo methodInfo : (List<MethodInfo>) classFile.getMethods()) {
122 methodInfo.setDescriptor(Descriptor.rename(methodInfo.getDescriptor(), map));
123 renameAttributes(methodInfo.getAttributes(), map, SignatureType.Method);
124 }
125
126 // rename fields
127 for (FieldInfo fieldInfo : (List<FieldInfo>) classFile.getFields()) {
128 fieldInfo.setDescriptor(Descriptor.rename(fieldInfo.getDescriptor(), map));
129 renameAttributes(fieldInfo.getAttributes(), map, SignatureType.Field);
130 }
131
132 // rename the class name itself last
133 // NOTE: don't use the map here, because setName() calls the buggy SignatureAttribute.renameClass()
134 // we only want to replace exactly this class name
135 String newName = renameClassName(c.getName(), map);
136 if (newName != null) {
137 c.setName(newName);
138 }
139
140 // replace simple names in the InnerClasses attribute too
141 InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
142 if (attr != null) {
143 for (int i = 0; i < attr.tableLength(); i++) {
144
145 // get the inner class full name (which has already been translated)
146 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(attr.innerClass(i)));
147
148 if (attr.innerNameIndex(i) != 0) {
149 // update the inner name
150 attr.setInnerNameIndex(i, constPool.addUtf8Info(classEntry.getInnermostClassName()));
151 }
152
153 /* DEBUG
154 System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i)));
155 */
156 }
157 }
158 }
159
160 @SuppressWarnings("unchecked")
161 private static void renameAttributes(List<AttributeInfo> attributes, ReplacerClassMap map, SignatureType type) {
162 try {
163
164 // make the rename class method accessible
165 Method renameClassMethod = AttributeInfo.class.getDeclaredMethod("renameClass", Map.class);
166 renameClassMethod.setAccessible(true);
167
168 for (AttributeInfo attribute : attributes) {
169 if (attribute instanceof SignatureAttribute) {
170 // this has to be handled specially because SignatureAttribute.renameClass() is buggy as hell
171 SignatureAttribute signatureAttribute = (SignatureAttribute) attribute;
172 String newSignature = type.rename(signatureAttribute.getSignature(), map);
173 if (newSignature != null) {
174 signatureAttribute.setSignature(newSignature);
175 }
176 } else if (attribute instanceof CodeAttribute) {
177 // code attributes have signature attributes too (indirectly)
178 CodeAttribute codeAttribute = (CodeAttribute) attribute;
179 renameAttributes(codeAttribute.getAttributes(), map, type);
180 } else if (attribute instanceof LocalVariableTypeAttribute) {
181 // lvt attributes have signature attributes too
182 LocalVariableTypeAttribute localVariableAttribute = (LocalVariableTypeAttribute) attribute;
183 renameLocalVariableTypeAttribute(localVariableAttribute, map);
184 } else {
185 renameClassMethod.invoke(attribute, map);
186 }
187 }
188
189 } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
190 throw new Error("Unable to call javassist methods by reflection!", ex);
191 }
192 }
193
194 private static void renameLocalVariableTypeAttribute(LocalVariableTypeAttribute attribute, ReplacerClassMap map) {
195
196 // adapted from LocalVariableAttribute.renameClass()
197 ConstPool cp = attribute.getConstPool();
198 int n = attribute.tableLength();
199 byte[] info = attribute.get();
200 for (int i = 0; i < n; ++i) {
201 int pos = i * 10 + 2;
202 int index = ByteArray.readU16bit(info, pos + 6);
203 if (index != 0) {
204 String signature = cp.getUtf8Info(index);
205 String newSignature = renameLocalVariableSignature(signature, map);
206 if (newSignature != null) {
207 ByteArray.write16bit(cp.addUtf8Info(newSignature), info, pos + 6);
208 }
209 }
210 }
211 }
212
213 private static String renameLocalVariableSignature(String signature, ReplacerClassMap map) {
214
215 // for some reason, signatures with . in them don't count as field signatures
216 // looks like anonymous classes delimit with . in stead of $
217 // convert the . to $, but keep track of how many we replace
218 // we need to put them back after we translate
219 int start = signature.lastIndexOf('$') + 1;
220 int numConverted = 0;
221 StringBuilder buf = new StringBuilder(signature);
222 for (int i = buf.length() - 1; i >= start; i--) {
223 char c = buf.charAt(i);
224 if (c == '.') {
225 buf.setCharAt(i, '$');
226 numConverted++;
227 }
228 }
229 signature = buf.toString();
230
231 // translate
232 String newSignature = renameFieldSignature(signature, map);
233 if (newSignature != null) {
234
235 // put the delimiters back
236 buf = new StringBuilder(newSignature);
237 for (int i = buf.length() - 1; i >= 0 && numConverted > 0; i--) {
238 char c = buf.charAt(i);
239 if (c == '$') {
240 buf.setCharAt(i, '.');
241 numConverted--;
242 }
243 }
244 assert (numConverted == 0);
245 newSignature = buf.toString();
246
247 return newSignature;
248 }
249
250 return null;
251 }
252
253 private static String renameClassSignature(String signature, ReplacerClassMap map) {
254 try {
255 ClassSignature type = renameType(SignatureAttribute.toClassSignature(signature), map);
256 if (type != null) {
257 return type.encode();
258 }
259 return null;
260 } catch (BadBytecode ex) {
261 throw new Error("Can't parse field signature: " + signature);
262 }
263 }
264
265 private static String renameFieldSignature(String signature, ReplacerClassMap map) {
266 try {
267 ObjectType type = renameType(SignatureAttribute.toFieldSignature(signature), map);
268 if (type != null) {
269 return type.encode();
270 }
271 return null;
272 } catch (BadBytecode ex) {
273 throw new Error("Can't parse class signature: " + signature);
274 }
275 }
276
277 private static String renameMethodSignature(String signature, ReplacerClassMap map) {
278 try {
279 MethodSignature type = renameType(SignatureAttribute.toMethodSignature(signature), map);
280 if (type != null) {
281 return type.encode();
282 }
283 return null;
284 } catch (BadBytecode ex) {
285 throw new Error("Can't parse method signature: " + signature);
286 }
287 }
288
289 private static ClassSignature renameType(ClassSignature type, ReplacerClassMap map) {
290
291 TypeParameter[] typeParamTypes = type.getParameters();
292 if (typeParamTypes != null) {
293 typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length);
294 for (int i = 0; i < typeParamTypes.length; i++) {
295 TypeParameter newParamType = renameType(typeParamTypes[i], map);
296 if (newParamType != null) {
297 typeParamTypes[i] = newParamType;
298 }
299 }
300 }
301
302 ClassType superclassType = type.getSuperClass();
303 if (superclassType != ClassType.OBJECT) {
304 ClassType newSuperclassType = renameType(superclassType, map);
305 if (newSuperclassType != null) {
306 superclassType = newSuperclassType;
307 }
308 }
309
310 ClassType[] interfaceTypes = type.getInterfaces();
311 if (interfaceTypes != null) {
312 interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length);
313 for (int i = 0; i < interfaceTypes.length; i++) {
314 ClassType newInterfaceType = renameType(interfaceTypes[i], map);
315 if (newInterfaceType != null) {
316 interfaceTypes[i] = newInterfaceType;
317 }
318 }
319 }
320
321 return new ClassSignature(typeParamTypes, superclassType, interfaceTypes);
322 }
323
324 private static MethodSignature renameType(MethodSignature type, ReplacerClassMap map) {
325
326 TypeParameter[] typeParamTypes = type.getTypeParameters();
327 if (typeParamTypes != null) {
328 typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length);
329 for (int i = 0; i < typeParamTypes.length; i++) {
330 TypeParameter newParamType = renameType(typeParamTypes[i], map);
331 if (newParamType != null) {
332 typeParamTypes[i] = newParamType;
333 }
334 }
335 }
336
337 Type[] paramTypes = type.getParameterTypes();
338 if (paramTypes != null) {
339 paramTypes = Arrays.copyOf(paramTypes, paramTypes.length);
340 for (int i = 0; i < paramTypes.length; i++) {
341 Type newParamType = renameType(paramTypes[i], map);
342 if (newParamType != null) {
343 paramTypes[i] = newParamType;
344 }
345 }
346 }
347
348 Type returnType = type.getReturnType();
349 if (returnType != null) {
350 Type newReturnType = renameType(returnType, map);
351 if (newReturnType != null) {
352 returnType = newReturnType;
353 }
354 }
355
356 ObjectType[] exceptionTypes = type.getExceptionTypes();
357 if (exceptionTypes != null) {
358 exceptionTypes = Arrays.copyOf(exceptionTypes, exceptionTypes.length);
359 for (int i = 0; i < exceptionTypes.length; i++) {
360 ObjectType newExceptionType = renameType(exceptionTypes[i], map);
361 if (newExceptionType != null) {
362 exceptionTypes[i] = newExceptionType;
363 }
364 }
365 }
366
367 return new MethodSignature(typeParamTypes, paramTypes, returnType, exceptionTypes);
368 }
369
370 private static Type renameType(Type type, ReplacerClassMap map) {
371 if (type instanceof ObjectType) {
372 return renameType((ObjectType) type, map);
373 } else if (type instanceof BaseType) {
374 return renameType((BaseType) type, map);
375 } else {
376 throw new Error("Don't know how to rename type " + type.getClass());
377 }
378 }
379
380 private static ObjectType renameType(ObjectType type, ReplacerClassMap map) {
381 if (type instanceof ArrayType) {
382 return renameType((ArrayType) type, map);
383 } else if (type instanceof ClassType) {
384 return renameType((ClassType) type, map);
385 } else if (type instanceof TypeVariable) {
386 return renameType((TypeVariable) type, map);
387 } else {
388 throw new Error("Don't know how to rename type " + type.getClass());
389 }
390 }
391
392 private static BaseType renameType(BaseType type, ReplacerClassMap map) {
393 // don't have to rename primitives
394 return null;
395 }
396
397 private static TypeVariable renameType(TypeVariable type, ReplacerClassMap map) {
398 // don't have to rename template args
399 return null;
400 }
401
402 private static ClassType renameType(ClassType type, ReplacerClassMap map) {
403
404 // translate type args
405 TypeArgument[] args = type.getTypeArguments();
406 if (args != null) {
407 args = Arrays.copyOf(args, args.length);
408 for (int i = 0; i < args.length; i++) {
409 TypeArgument newType = renameType(args[i], map);
410 if (newType != null) {
411 args[i] = newType;
412 }
413 }
414 }
415
416 if (type instanceof NestedClassType) {
417 NestedClassType nestedType = (NestedClassType) type;
418
419 // translate the name
420 String name = getClassName(type);
421 String newName = map.get(name);
422 if (newName != null) {
423 name = new ClassEntry(newName).getInnermostClassName();
424 }
425
426 // translate the parent class too
427 ClassType parent = renameType(nestedType.getDeclaringClass(), map);
428 if (parent == null) {
429 parent = nestedType.getDeclaringClass();
430 }
431
432 return new NestedClassType(parent, name, args);
433 } else {
434
435 // translate the name
436 String name = type.getName();
437 String newName = renameClassName(name, map);
438 if (newName != null) {
439 name = newName;
440 }
441
442 return new ClassType(name, args);
443 }
444 }
445
446 private static String getClassName(ClassType type) {
447 if (type instanceof NestedClassType) {
448 NestedClassType nestedType = (NestedClassType) type;
449 return getClassName(nestedType.getDeclaringClass()) + "$" + Descriptor.toJvmName(type.getName().replace('.', '$'));
450 } else {
451 return Descriptor.toJvmName(type.getName());
452 }
453 }
454
455 private static String renameClassName(String name, ReplacerClassMap map) {
456 String newName = map.get(Descriptor.toJvmName(name));
457 if (newName != null) {
458 return Descriptor.toJavaName(newName);
459 }
460 return null;
461 }
462
463 private static TypeArgument renameType(TypeArgument type, ReplacerClassMap map) {
464 ObjectType subType = type.getType();
465 if (subType != null) {
466 ObjectType newSubType = renameType(subType, map);
467 if (newSubType != null) {
468 switch (type.getKind()) {
469 case ' ':
470 return new TypeArgument(newSubType);
471 case '+':
472 return TypeArgument.subclassOf(newSubType);
473 case '-':
474 return TypeArgument.superOf(newSubType);
475 default:
476 throw new Error("Unknown type kind: " + type.getKind());
477 }
478 }
479 }
480 return null;
481 }
482
483 private static ArrayType renameType(ArrayType type, ReplacerClassMap map) {
484 Type newSubType = renameType(type.getComponentType(), map);
485 if (newSubType != null) {
486 return new ArrayType(type.getDimension(), newSubType);
487 }
488 return null;
489 }
490
491 private static TypeParameter renameType(TypeParameter type, ReplacerClassMap map) {
492
493 ObjectType superclassType = type.getClassBound();
494 if (superclassType != null) {
495 ObjectType newSuperclassType = renameType(superclassType, map);
496 if (newSuperclassType != null) {
497 superclassType = newSuperclassType;
498 }
499 }
500
501 ObjectType[] interfaceTypes = type.getInterfaceBound();
502 if (interfaceTypes != null) {
503 interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length);
504 for (int i = 0; i < interfaceTypes.length; i++) {
505 ObjectType newInterfaceType = renameType(interfaceTypes[i], map);
506 if (newInterfaceType != null) {
507 interfaceTypes[i] = newInterfaceType;
508 }
509 }
510 }
511
512 return new TypeParameter(type.getName(), superclassType, interfaceTypes);
513 }
514}