summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/bytecode
diff options
context:
space:
mode:
authorGravatar lclc982016-06-30 00:49:21 +1000
committerGravatar GitHub2016-06-30 00:49:21 +1000
commit4be005617b3b8c3578cca07c5d085d12916f0d1d (patch)
treedb163431f38703e26da417ef05eaea2b27a498b9 /src/main/java/cuchaz/enigma/bytecode
parentSome small changes to fix idea importing (diff)
downloadenigma-fork-4be005617b3b8c3578cca07c5d085d12916f0d1d.tar.gz
enigma-fork-4be005617b3b8c3578cca07c5d085d12916f0d1d.tar.xz
enigma-fork-4be005617b3b8c3578cca07c5d085d12916f0d1d.zip
Json format (#2)
* Added new format * Fixed bug * Updated Version
Diffstat (limited to 'src/main/java/cuchaz/enigma/bytecode')
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java117
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java51
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java51
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java514
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java151
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java263
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/InfoType.java301
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java132
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java119
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java66
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java86
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java55
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java151
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java74
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java74
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java74
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java55
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java74
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java55
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java28
20 files changed, 2491 insertions, 0 deletions
diff --git a/src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java b/src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java
new file mode 100644
index 0000000..8058d0e
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java
@@ -0,0 +1,117 @@
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.util.Iterator;
14
15import cuchaz.enigma.bytecode.CheckCastIterator.CheckCast;
16import cuchaz.enigma.mapping.ClassEntry;
17import cuchaz.enigma.mapping.MethodEntry;
18import cuchaz.enigma.mapping.Signature;
19import javassist.bytecode.*;
20
21public class CheckCastIterator implements Iterator<CheckCast> {
22
23 public static class CheckCast {
24
25 public String className;
26 public MethodEntry prevMethodEntry;
27
28 public CheckCast(String className, MethodEntry prevMethodEntry) {
29 this.className = className;
30 this.prevMethodEntry = prevMethodEntry;
31 }
32 }
33
34 private ConstPool m_constants;
35 private CodeAttribute m_attribute;
36 private CodeIterator m_iter;
37 private CheckCast m_next;
38
39 public CheckCastIterator(CodeAttribute codeAttribute) throws BadBytecode {
40 m_constants = codeAttribute.getConstPool();
41 m_attribute = codeAttribute;
42 m_iter = m_attribute.iterator();
43
44 m_next = getNext();
45 }
46
47 @Override
48 public boolean hasNext() {
49 return m_next != null;
50 }
51
52 @Override
53 public CheckCast next() {
54 CheckCast out = m_next;
55 try {
56 m_next = getNext();
57 } catch (BadBytecode ex) {
58 throw new Error(ex);
59 }
60 return out;
61 }
62
63 @Override
64 public void remove() {
65 throw new UnsupportedOperationException();
66 }
67
68 private CheckCast getNext() throws BadBytecode {
69 int prevPos = 0;
70 while (m_iter.hasNext()) {
71 int pos = m_iter.next();
72 int opcode = m_iter.byteAt(pos);
73 switch (opcode) {
74 case Opcode.CHECKCAST:
75
76 // get the type of this op code (next two bytes are a classinfo index)
77 MethodEntry prevMethodEntry = getMethodEntry(prevPos);
78 if (prevMethodEntry != null) {
79 return new CheckCast(m_constants.getClassInfo(m_iter.s16bitAt(pos + 1)), prevMethodEntry);
80 }
81 break;
82 }
83 prevPos = pos;
84 }
85 return null;
86 }
87
88 private MethodEntry getMethodEntry(int pos) {
89 switch (m_iter.byteAt(pos)) {
90 case Opcode.INVOKEVIRTUAL:
91 case Opcode.INVOKESTATIC:
92 case Opcode.INVOKEDYNAMIC:
93 case Opcode.INVOKESPECIAL: {
94 int index = m_iter.s16bitAt(pos + 1);
95 return new MethodEntry(
96 new ClassEntry(Descriptor.toJvmName(m_constants.getMethodrefClassName(index))),
97 m_constants.getMethodrefName(index),
98 new Signature(m_constants.getMethodrefType(index))
99 );
100 }
101
102 case Opcode.INVOKEINTERFACE: {
103 int index = m_iter.s16bitAt(pos + 1);
104 return new MethodEntry(
105 new ClassEntry(Descriptor.toJvmName(m_constants.getInterfaceMethodrefClassName(index))),
106 m_constants.getInterfaceMethodrefName(index),
107 new Signature(m_constants.getInterfaceMethodrefType(index))
108 );
109 }
110 }
111 return null;
112 }
113
114 public Iterable<CheckCast> casts() {
115 return () -> CheckCastIterator.this;
116 }
117}
diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java b/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java
new file mode 100644
index 0000000..ad5bab0
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java
@@ -0,0 +1,51 @@
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 javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.CtField;
16import javassist.bytecode.AccessFlag;
17import javassist.bytecode.InnerClassesAttribute;
18
19
20public class ClassProtectifier {
21
22 public static CtClass protectify(CtClass c) {
23
24 // protectify all the fields
25 for (CtField field : c.getDeclaredFields()) {
26 field.setModifiers(protectify(field.getModifiers()));
27 }
28
29 // protectify all the methods and constructors
30 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
31 behavior.setModifiers(protectify(behavior.getModifiers()));
32 }
33
34 // protectify all the inner classes
35 InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
36 if (attr != null) {
37 for (int i = 0; i < attr.tableLength(); i++) {
38 attr.setAccessFlags(i, protectify(attr.accessFlags(i)));
39 }
40 }
41
42 return c;
43 }
44
45 private static int protectify(int flags) {
46 if (AccessFlag.isPrivate(flags)) {
47 flags = AccessFlag.setProtected(flags);
48 }
49 return flags;
50 }
51}
diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java b/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java
new file mode 100644
index 0000000..da86b2b
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java
@@ -0,0 +1,51 @@
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 javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.CtField;
16import javassist.bytecode.AccessFlag;
17import javassist.bytecode.InnerClassesAttribute;
18
19
20public class ClassPublifier {
21
22 public static CtClass publify(CtClass c) {
23
24 // publify all the fields
25 for (CtField field : c.getDeclaredFields()) {
26 field.setModifiers(publify(field.getModifiers()));
27 }
28
29 // publify all the methods and constructors
30 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
31 behavior.setModifiers(publify(behavior.getModifiers()));
32 }
33
34 // publify all the inner classes
35 InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
36 if (attr != null) {
37 for (int i = 0; i < attr.tableLength(); i++) {
38 attr.setAccessFlags(i, publify(attr.accessFlags(i)));
39 }
40 }
41
42 return c;
43 }
44
45 private static int publify(int flags) {
46 if (AccessFlag.isPrivate(flags) || AccessFlag.isProtected(flags)) {
47 flags = AccessFlag.setPublic(flags);
48 }
49 return flags;
50 }
51}
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}
diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java b/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java
new file mode 100644
index 0000000..ef197cb
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java
@@ -0,0 +1,151 @@
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 cuchaz.enigma.mapping.*;
14import javassist.CtBehavior;
15import javassist.CtClass;
16import javassist.CtField;
17import javassist.CtMethod;
18import javassist.bytecode.ConstPool;
19import javassist.bytecode.Descriptor;
20import javassist.bytecode.EnclosingMethodAttribute;
21import javassist.bytecode.SourceFileAttribute;
22
23public class ClassTranslator {
24
25 private Translator m_translator;
26
27 public ClassTranslator(Translator translator) {
28 m_translator = translator;
29 }
30
31 public void translate(CtClass c) {
32
33 // NOTE: the order of these translations is very important
34
35 // translate all the field and method references in the code by editing the constant pool
36 ConstPool constants = c.getClassFile().getConstPool();
37 ConstPoolEditor editor = new ConstPoolEditor(constants);
38 for (int i = 1; i < constants.getSize(); i++) {
39 switch (constants.getTag(i)) {
40
41 case ConstPool.CONST_Fieldref: {
42
43 // translate the name and type
44 FieldEntry entry = EntryFactory.getFieldEntry(
45 Descriptor.toJvmName(constants.getFieldrefClassName(i)),
46 constants.getFieldrefName(i),
47 constants.getFieldrefType(i)
48 );
49 FieldEntry translatedEntry = m_translator.translateEntry(entry);
50 if (!entry.equals(translatedEntry)) {
51 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getType().toString());
52 }
53 }
54 break;
55
56 case ConstPool.CONST_Methodref:
57 case ConstPool.CONST_InterfaceMethodref: {
58
59 // translate the name and type (ie signature)
60 BehaviorEntry entry = EntryFactory.getBehaviorEntry(
61 Descriptor.toJvmName(editor.getMemberrefClassname(i)),
62 editor.getMemberrefName(i),
63 editor.getMemberrefType(i)
64 );
65 BehaviorEntry translatedEntry = m_translator.translateEntry(entry);
66 if (!entry.equals(translatedEntry)) {
67 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString());
68 }
69 }
70 break;
71 }
72 }
73
74 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
75
76 // translate all the fields
77 for (CtField field : c.getDeclaredFields()) {
78
79 // translate the name
80 FieldEntry entry = EntryFactory.getFieldEntry(field);
81 String translatedName = m_translator.translate(entry);
82 if (translatedName != null) {
83 field.setName(translatedName);
84 }
85
86 // translate the type
87 Type translatedType = m_translator.translateType(entry.getType());
88 field.getFieldInfo().setDescriptor(translatedType.toString());
89 }
90
91 // translate all the methods and constructors
92 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
93
94 BehaviorEntry entry = EntryFactory.getBehaviorEntry(behavior);
95
96 if (behavior instanceof CtMethod) {
97 CtMethod method = (CtMethod) behavior;
98
99 // translate the name
100 String translatedName = m_translator.translate(entry);
101 if (translatedName != null) {
102 method.setName(translatedName);
103 }
104 }
105
106 if (entry.getSignature() != null) {
107 // translate the signature
108 Signature translatedSignature = m_translator.translateSignature(entry.getSignature());
109 behavior.getMethodInfo().setDescriptor(translatedSignature.toString());
110 }
111 }
112
113 // translate the EnclosingMethod attribute
114 EnclosingMethodAttribute enclosingMethodAttr = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag);
115 if (enclosingMethodAttr != null) {
116
117 if (enclosingMethodAttr.methodIndex() == 0) {
118 BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(Descriptor.toJvmName(enclosingMethodAttr.className()));
119 BehaviorEntry deobfBehaviorEntry = m_translator.translateEntry(obfBehaviorEntry);
120 c.getClassFile().addAttribute(new EnclosingMethodAttribute(
121 constants,
122 deobfBehaviorEntry.getClassName()
123 ));
124 } else {
125 BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(
126 Descriptor.toJvmName(enclosingMethodAttr.className()),
127 enclosingMethodAttr.methodName(),
128 enclosingMethodAttr.methodDescriptor()
129 );
130 BehaviorEntry deobfBehaviorEntry = m_translator.translateEntry(obfBehaviorEntry);
131 c.getClassFile().addAttribute(new EnclosingMethodAttribute(
132 constants,
133 deobfBehaviorEntry.getClassName(),
134 deobfBehaviorEntry.getName(),
135 deobfBehaviorEntry.getSignature().toString()
136 ));
137 }
138 }
139
140 // translate all the class names referenced in the code
141 // the above code only changed method/field/reference names and types, but not the rest of the class references
142 ClassRenamer.renameClasses(c, m_translator);
143
144 // translate the source file attribute too
145 ClassEntry deobfClassEntry = m_translator.translateEntry(classEntry);
146 if (deobfClassEntry != null) {
147 String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOutermostClassEntry().getSimpleName()) + ".java";
148 c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile));
149 }
150 }
151}
diff --git a/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java b/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java
new file mode 100644
index 0000000..0082a72
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java
@@ -0,0 +1,263 @@
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.io.DataInputStream;
14import java.io.DataOutputStream;
15import java.lang.reflect.Constructor;
16import java.lang.reflect.Field;
17import java.lang.reflect.Method;
18import java.util.HashMap;
19
20import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor;
21import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
22import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor;
23import javassist.bytecode.ConstPool;
24import javassist.bytecode.Descriptor;
25
26public class ConstPoolEditor {
27
28 private static Method m_getItem;
29 private static Method m_addItem;
30 private static Method m_addItem0;
31 private static Field m_items;
32 private static Field m_cache;
33 private static Field m_numItems;
34 private static Field m_objects;
35 private static Field m_elements;
36 private static Method m_methodWritePool;
37 private static Constructor<ConstPool> m_constructorPool;
38
39 static {
40 try {
41 m_getItem = ConstPool.class.getDeclaredMethod("getItem", int.class);
42 m_getItem.setAccessible(true);
43
44 m_addItem = ConstPool.class.getDeclaredMethod("addItem", Class.forName("javassist.bytecode.ConstInfo"));
45 m_addItem.setAccessible(true);
46
47 m_addItem0 = ConstPool.class.getDeclaredMethod("addItem0", Class.forName("javassist.bytecode.ConstInfo"));
48 m_addItem0.setAccessible(true);
49
50 m_items = ConstPool.class.getDeclaredField("items");
51 m_items.setAccessible(true);
52
53 m_cache = ConstPool.class.getDeclaredField("itemsCache");
54 m_cache.setAccessible(true);
55
56 m_numItems = ConstPool.class.getDeclaredField("numOfItems");
57 m_numItems.setAccessible(true);
58
59 m_objects = Class.forName("javassist.bytecode.LongVector").getDeclaredField("objects");
60 m_objects.setAccessible(true);
61
62 m_elements = Class.forName("javassist.bytecode.LongVector").getDeclaredField("elements");
63 m_elements.setAccessible(true);
64
65 m_methodWritePool = ConstPool.class.getDeclaredMethod("write", DataOutputStream.class);
66 m_methodWritePool.setAccessible(true);
67
68 m_constructorPool = ConstPool.class.getDeclaredConstructor(DataInputStream.class);
69 m_constructorPool.setAccessible(true);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74
75 private ConstPool m_pool;
76
77 public ConstPoolEditor(ConstPool pool) {
78 m_pool = pool;
79 }
80
81 public void writePool(DataOutputStream out) {
82 try {
83 m_methodWritePool.invoke(m_pool, out);
84 } catch (Exception ex) {
85 throw new Error(ex);
86 }
87 }
88
89 public static ConstPool readPool(DataInputStream in) {
90 try {
91 return m_constructorPool.newInstance(in);
92 } catch (Exception ex) {
93 throw new Error(ex);
94 }
95 }
96
97 public String getMemberrefClassname(int memberrefIndex) {
98 return Descriptor.toJvmName(m_pool.getClassInfo(m_pool.getMemberClass(memberrefIndex)));
99 }
100
101 public String getMemberrefName(int memberrefIndex) {
102 return m_pool.getUtf8Info(m_pool.getNameAndTypeName(m_pool.getMemberNameAndType(memberrefIndex)));
103 }
104
105 public String getMemberrefType(int memberrefIndex) {
106 return m_pool.getUtf8Info(m_pool.getNameAndTypeDescriptor(m_pool.getMemberNameAndType(memberrefIndex)));
107 }
108
109 public ConstInfoAccessor getItem(int index) {
110 try {
111 Object entry = m_getItem.invoke(m_pool, index);
112 if (entry == null) {
113 return null;
114 }
115 return new ConstInfoAccessor(entry);
116 } catch (Exception ex) {
117 throw new Error(ex);
118 }
119 }
120
121 public int addItem(Object item) {
122 try {
123 return (Integer) m_addItem.invoke(m_pool, item);
124 } catch (Exception ex) {
125 throw new Error(ex);
126 }
127 }
128
129 public int addItemForceNew(Object item) {
130 try {
131 return (Integer) m_addItem0.invoke(m_pool, item);
132 } catch (Exception ex) {
133 throw new Error(ex);
134 }
135 }
136
137 @SuppressWarnings("rawtypes")
138 public void removeLastItem() {
139 try {
140 // remove the item from the cache
141 HashMap cache = getCache();
142 if (cache != null) {
143 Object item = getItem(m_pool.getSize() - 1);
144 cache.remove(item);
145 }
146
147 // remove the actual item
148 // based off of LongVector.addElement()
149 Object items = m_items.get(m_pool);
150 Object[][] objects = (Object[][]) m_objects.get(items);
151 int numElements = (Integer) m_elements.get(items) - 1;
152 int nth = numElements >> 7;
153 int offset = numElements & (128 - 1);
154 objects[nth][offset] = null;
155
156 // decrement the number of items
157 m_elements.set(items, numElements);
158 m_numItems.set(m_pool, (Integer) m_numItems.get(m_pool) - 1);
159 } catch (Exception ex) {
160 throw new Error(ex);
161 }
162 }
163
164 @SuppressWarnings("rawtypes")
165 public HashMap getCache() {
166 try {
167 return (HashMap) m_cache.get(m_pool);
168 } catch (Exception ex) {
169 throw new Error(ex);
170 }
171 }
172
173 @SuppressWarnings({"rawtypes", "unchecked"})
174 public void changeMemberrefNameAndType(int memberrefIndex, String newName, String newType) {
175 // NOTE: when changing values, we always need to copy-on-write
176 try {
177 // get the memberref item
178 Object item = getItem(memberrefIndex).getItem();
179
180 // update the cache
181 HashMap cache = getCache();
182 if (cache != null) {
183 cache.remove(item);
184 }
185
186 new MemberRefInfoAccessor(item).setNameAndTypeIndex(m_pool.addNameAndTypeInfo(newName, newType));
187
188 // update the cache
189 if (cache != null) {
190 cache.put(item, item);
191 }
192 } catch (Exception ex) {
193 throw new Error(ex);
194 }
195
196 // make sure the change worked
197 assert (newName.equals(getMemberrefName(memberrefIndex)));
198 assert (newType.equals(getMemberrefType(memberrefIndex)));
199 }
200
201 @SuppressWarnings({"rawtypes", "unchecked"})
202 public void changeClassName(int classNameIndex, String newName) {
203 // NOTE: when changing values, we always need to copy-on-write
204 try {
205 // get the class item
206 Object item = getItem(classNameIndex).getItem();
207
208 // update the cache
209 HashMap cache = getCache();
210 if (cache != null) {
211 cache.remove(item);
212 }
213
214 // add the new name and repoint the name-and-type to it
215 new ClassInfoAccessor(item).setNameIndex(m_pool.addUtf8Info(newName));
216
217 // update the cache
218 if (cache != null) {
219 cache.put(item, item);
220 }
221 } catch (Exception ex) {
222 throw new Error(ex);
223 }
224 }
225
226 public static ConstPool newConstPool() {
227 // const pool expects the name of a class to initialize itself
228 // but we want an empty pool
229 // so give it a bogus name, and then clear the entries afterwards
230 ConstPool pool = new ConstPool("a");
231
232 ConstPoolEditor editor = new ConstPoolEditor(pool);
233 int size = pool.getSize();
234 for (int i = 0; i < size - 1; i++) {
235 editor.removeLastItem();
236 }
237
238 // make sure the pool is actually empty
239 // although, in this case "empty" means one thing in it
240 // the JVM spec says index 0 should be reserved
241 assert (pool.getSize() == 1);
242 assert (editor.getItem(0) == null);
243 assert (editor.getItem(1) == null);
244 assert (editor.getItem(2) == null);
245 assert (editor.getItem(3) == null);
246
247 // also, clear the cache
248 editor.getCache().clear();
249
250 return pool;
251 }
252
253 public String dump() {
254 StringBuilder buf = new StringBuilder();
255 for (int i = 1; i < m_pool.getSize(); i++) {
256 buf.append(String.format("%4d", i));
257 buf.append(" ");
258 buf.append(getItem(i).toString());
259 buf.append("\n");
260 }
261 return buf.toString();
262 }
263}
diff --git a/src/main/java/cuchaz/enigma/bytecode/InfoType.java b/src/main/java/cuchaz/enigma/bytecode/InfoType.java
new file mode 100644
index 0000000..89940d9
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/InfoType.java
@@ -0,0 +1,301 @@
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 com.google.common.collect.Lists;
14import com.google.common.collect.Maps;
15
16import java.util.Collection;
17import java.util.List;
18import java.util.Map;
19
20import cuchaz.enigma.bytecode.accessors.*;
21
22public enum InfoType {
23
24 Utf8Info(1, 0),
25 IntegerInfo(3, 0),
26 FloatInfo(4, 0),
27 LongInfo(5, 0),
28 DoubleInfo(6, 0),
29 ClassInfo(7, 1) {
30 @Override
31 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
32 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
33 gatherIndexTree(indices, editor, accessor.getNameIndex());
34 }
35
36 @Override
37 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
38 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
39 accessor.setNameIndex(remapIndex(map, accessor.getNameIndex()));
40 }
41
42 @Override
43 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
44 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
45 ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex());
46 return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag();
47 }
48 },
49 StringInfo(8, 1) {
50 @Override
51 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
52 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
53 gatherIndexTree(indices, editor, accessor.getStringIndex());
54 }
55
56 @Override
57 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
58 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
59 accessor.setStringIndex(remapIndex(map, accessor.getStringIndex()));
60 }
61
62 @Override
63 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
64 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
65 ConstInfoAccessor stringEntry = pool.getItem(accessor.getStringIndex());
66 return stringEntry != null && stringEntry.getTag() == Utf8Info.getTag();
67 }
68 },
69 FieldRefInfo(9, 2) {
70 @Override
71 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
72 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
73 gatherIndexTree(indices, editor, accessor.getClassIndex());
74 gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex());
75 }
76
77 @Override
78 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
79 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
80 accessor.setClassIndex(remapIndex(map, accessor.getClassIndex()));
81 accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex()));
82 }
83
84 @Override
85 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
86 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
87 ConstInfoAccessor classEntry = pool.getItem(accessor.getClassIndex());
88 ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex());
89 return classEntry != null && classEntry.getTag() == ClassInfo.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag();
90 }
91 },
92 // same as FieldRefInfo
93 MethodRefInfo(10, 2) {
94 @Override
95 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
96 FieldRefInfo.gatherIndexTree(indices, editor, entry);
97 }
98
99 @Override
100 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
101 FieldRefInfo.remapIndices(map, entry);
102 }
103
104 @Override
105 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
106 return FieldRefInfo.subIndicesAreValid(entry, pool);
107 }
108 },
109 // same as FieldRefInfo
110 InterfaceMethodRefInfo(11, 2) {
111 @Override
112 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
113 FieldRefInfo.gatherIndexTree(indices, editor, entry);
114 }
115
116 @Override
117 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
118 FieldRefInfo.remapIndices(map, entry);
119 }
120
121 @Override
122 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
123 return FieldRefInfo.subIndicesAreValid(entry, pool);
124 }
125 },
126 NameAndTypeInfo(12, 1) {
127 @Override
128 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
129 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
130 gatherIndexTree(indices, editor, accessor.getNameIndex());
131 gatherIndexTree(indices, editor, accessor.getTypeIndex());
132 }
133
134 @Override
135 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
136 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
137 accessor.setNameIndex(remapIndex(map, accessor.getNameIndex()));
138 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
139 }
140
141 @Override
142 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
143 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
144 ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex());
145 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
146 return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag() && typeEntry != null && typeEntry.getTag() == Utf8Info.getTag();
147 }
148 },
149 MethodHandleInfo(15, 3) {
150 @Override
151 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
152 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
153 gatherIndexTree(indices, editor, accessor.getTypeIndex());
154 gatherIndexTree(indices, editor, accessor.getMethodRefIndex());
155 }
156
157 @Override
158 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
159 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
160 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
161 accessor.setMethodRefIndex(remapIndex(map, accessor.getMethodRefIndex()));
162 }
163
164 @Override
165 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
166 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
167 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
168 ConstInfoAccessor methodRefEntry = pool.getItem(accessor.getMethodRefIndex());
169 return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag() && methodRefEntry != null && methodRefEntry.getTag() == MethodRefInfo.getTag();
170 }
171 },
172 MethodTypeInfo(16, 1) {
173 @Override
174 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
175 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
176 gatherIndexTree(indices, editor, accessor.getTypeIndex());
177 }
178
179 @Override
180 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
181 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
182 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
183 }
184
185 @Override
186 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
187 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
188 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
189 return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag();
190 }
191 },
192 InvokeDynamicInfo(18, 2) {
193 @Override
194 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
195 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
196 gatherIndexTree(indices, editor, accessor.getBootstrapIndex());
197 gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex());
198 }
199
200 @Override
201 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
202 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
203 accessor.setBootstrapIndex(remapIndex(map, accessor.getBootstrapIndex()));
204 accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex()));
205 }
206
207 @Override
208 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
209 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
210 ConstInfoAccessor bootstrapEntry = pool.getItem(accessor.getBootstrapIndex());
211 ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex());
212 return bootstrapEntry != null && bootstrapEntry.getTag() == Utf8Info.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag();
213 }
214 };
215
216 private static Map<Integer, InfoType> m_types;
217
218 static {
219 m_types = Maps.newTreeMap();
220 for (InfoType type : values()) {
221 m_types.put(type.getTag(), type);
222 }
223 }
224
225 private int m_tag;
226 private int m_level;
227
228 InfoType(int tag, int level) {
229 m_tag = tag;
230 m_level = level;
231 }
232
233 public int getTag() {
234 return m_tag;
235 }
236
237 public int getLevel() {
238 return m_level;
239 }
240
241 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
242 // by default, do nothing
243 }
244
245 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
246 // by default, do nothing
247 }
248
249 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
250 // by default, everything is good
251 return true;
252 }
253
254 public boolean selfIndexIsValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
255 ConstInfoAccessor entryCheck = pool.getItem(entry.getIndex());
256 if (entryCheck == null) {
257 return false;
258 }
259 return entryCheck.getItem().equals(entry.getItem());
260 }
261
262 public static InfoType getByTag(int tag) {
263 return m_types.get(tag);
264 }
265
266 public static List<InfoType> getByLevel(int level) {
267 List<InfoType> types = Lists.newArrayList();
268 for (InfoType type : values()) {
269 if (type.getLevel() == level) {
270 types.add(type);
271 }
272 }
273 return types;
274 }
275
276 public static List<InfoType> getSortedByLevel() {
277 List<InfoType> types = Lists.newArrayList();
278 types.addAll(getByLevel(0));
279 types.addAll(getByLevel(1));
280 types.addAll(getByLevel(2));
281 types.addAll(getByLevel(3));
282 return types;
283 }
284
285 public static void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, int index) {
286 // add own index
287 indices.add(index);
288
289 // recurse
290 ConstInfoAccessor entry = editor.getItem(index);
291 entry.getType().gatherIndexTree(indices, editor, entry);
292 }
293
294 private static int remapIndex(Map<Integer, Integer> map, int index) {
295 Integer newIndex = map.get(index);
296 if (newIndex == null) {
297 newIndex = index;
298 }
299 return newIndex;
300 }
301}
diff --git a/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java
new file mode 100644
index 0000000..25ac7d6
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java
@@ -0,0 +1,132 @@
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 com.google.common.collect.Lists;
14
15import java.util.Collection;
16import java.util.List;
17
18import cuchaz.enigma.analysis.JarIndex;
19import cuchaz.enigma.mapping.BehaviorEntry;
20import cuchaz.enigma.mapping.ClassEntry;
21import cuchaz.enigma.mapping.EntryFactory;
22import javassist.CtClass;
23import javassist.bytecode.AccessFlag;
24import javassist.bytecode.ConstPool;
25import javassist.bytecode.EnclosingMethodAttribute;
26import javassist.bytecode.InnerClassesAttribute;
27
28public class InnerClassWriter {
29
30 private JarIndex m_index;
31
32 public InnerClassWriter(JarIndex index) {
33 m_index = index;
34 }
35
36 public void write(CtClass c) {
37
38 // don't change anything if there's already an attribute there
39 InnerClassesAttribute oldAttr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
40 if (oldAttr != null) {
41 // bail!
42 return;
43 }
44
45 ClassEntry obfClassEntry = EntryFactory.getClassEntry(c);
46 List<ClassEntry> obfClassChain = m_index.getObfClassChain(obfClassEntry);
47
48 boolean isInnerClass = obfClassChain.size() > 1;
49 if (isInnerClass) {
50
51 // it's an inner class, rename it to the fully qualified name
52 c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName());
53
54 BehaviorEntry caller = m_index.getAnonymousClassCaller(obfClassEntry);
55 if (caller != null) {
56
57 // write the enclosing method attribute
58 if (caller.getName().equals("<clinit>")) {
59 c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName()));
60 } else {
61 c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature().toString()));
62 }
63 }
64 }
65
66 // does this class have any inner classes?
67 Collection<ClassEntry> obfInnerClassEntries = m_index.getInnerClasses(obfClassEntry);
68
69 if (isInnerClass || !obfInnerClassEntries.isEmpty()) {
70
71 // create an inner class attribute
72 InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool());
73 c.getClassFile().addAttribute(attr);
74
75 // write the ancestry, but not the outermost class
76 for (int i = 1; i < obfClassChain.size(); i++) {
77 ClassEntry obfInnerClassEntry = obfClassChain.get(i);
78 writeInnerClass(attr, obfClassChain, obfInnerClassEntry);
79
80 // update references to use the fully qualified inner class name
81 c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(obfClassChain).getName());
82 }
83
84 // write the inner classes
85 for (ClassEntry obfInnerClassEntry : obfInnerClassEntries) {
86
87 // extend the class chain
88 List<ClassEntry> extendedObfClassChain = Lists.newArrayList(obfClassChain);
89 extendedObfClassChain.add(obfInnerClassEntry);
90
91 writeInnerClass(attr, extendedObfClassChain, obfInnerClassEntry);
92
93 // update references to use the fully qualified inner class name
94 c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName());
95 }
96 }
97 }
98
99 private void writeInnerClass(InnerClassesAttribute attr, List<ClassEntry> obfClassChain, ClassEntry obfClassEntry) {
100
101 // get the new inner class name
102 ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain);
103 ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry();
104
105 // here's what the JVM spec says about the InnerClasses attribute
106 // append(inner, parent, 0 if anonymous else simple name, flags);
107
108 // update the attribute with this inner class
109 ConstPool constPool = attr.getConstPool();
110 int innerClassIndex = constPool.addClassInfo(obfInnerClassEntry.getName());
111 int parentClassIndex = constPool.addClassInfo(obfOuterClassEntry.getName());
112 int innerClassNameIndex = 0;
113 int accessFlags = AccessFlag.PUBLIC;
114 // TODO: need to figure out if we can put static or not
115 if (!m_index.isAnonymousClass(obfClassEntry)) {
116 innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnermostClassName());
117 }
118
119 attr.append(innerClassIndex, parentClassIndex, innerClassNameIndex, accessFlags);
120
121 /* DEBUG
122 System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)",
123 obfClassEntry,
124 attr.innerClass(attr.tableLength() - 1),
125 attr.outerClass(attr.tableLength() - 1),
126 attr.innerName(attr.tableLength() - 1),
127 Constants.NonePackage + "/" + obfInnerClassName,
128 obfClassEntry.getName()
129 ));
130 */
131 }
132}
diff --git a/src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java b/src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java
new file mode 100644
index 0000000..d0ce107
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java
@@ -0,0 +1,119 @@
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 cuchaz.enigma.mapping.ArgumentEntry;
14import cuchaz.enigma.mapping.BehaviorEntry;
15import cuchaz.enigma.mapping.EntryFactory;
16import cuchaz.enigma.mapping.Translator;
17import javassist.CtBehavior;
18import javassist.CtClass;
19import javassist.bytecode.*;
20
21
22public class LocalVariableRenamer {
23
24 private Translator m_translator;
25
26 public LocalVariableRenamer(Translator translator) {
27 m_translator = translator;
28 }
29
30 public void rename(CtClass c) {
31 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
32
33 // if there's a local variable table, just rename everything to v1, v2, v3, ... for now
34 CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute();
35 if (codeAttribute == null) {
36 continue;
37 }
38
39 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
40 ConstPool constants = c.getClassFile().getConstPool();
41
42 LocalVariableAttribute table = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
43 if (table != null) {
44 renameLVT(behaviorEntry, constants, table);
45 }
46
47 LocalVariableTypeAttribute typeTable = (LocalVariableTypeAttribute) codeAttribute.getAttribute(LocalVariableAttribute.typeTag);
48 if (typeTable != null) {
49 renameLVTT(typeTable, table);
50 }
51 }
52 }
53
54 // DEBUG
55 @SuppressWarnings("unused")
56 private void dumpTable(LocalVariableAttribute table) {
57 for (int i = 0; i < table.tableLength(); i++) {
58 System.out.println(String.format("\t%d (%d): %s %s",
59 i, table.index(i), table.variableName(i), table.descriptor(i)
60 ));
61 }
62 }
63
64 private void renameLVT(BehaviorEntry behaviorEntry, ConstPool constants, LocalVariableAttribute table) {
65
66 // skip empty tables
67 if (table.tableLength() <= 0) {
68 return;
69 }
70
71 // where do we start counting variables?
72 int starti = 0;
73 if (table.variableName(0).equals("this")) {
74 // skip the "this" variable
75 starti = 1;
76 }
77
78 // rename method arguments first
79 int numArgs = 0;
80 if (behaviorEntry.getSignature() != null) {
81 numArgs = behaviorEntry.getSignature().getArgumentTypes().size();
82 for (int i = starti; i < starti + numArgs && i < table.tableLength(); i++) {
83 int argi = i - starti;
84 String argName = m_translator.translate(new ArgumentEntry(behaviorEntry, argi, ""));
85 if (argName == null) {
86 argName = "a" + (argi + 1);
87 }
88 renameVariable(table, i, constants.addUtf8Info(argName));
89 }
90 }
91
92 // then rename the rest of the args, if any
93 for (int i = starti + numArgs; i < table.tableLength(); i++) {
94 int firstIndex = table.index(starti + numArgs);
95 renameVariable(table, i, constants.addUtf8Info("v" + (table.index(i) - firstIndex + 1)));
96 }
97 }
98
99 private void renameLVTT(LocalVariableTypeAttribute typeTable, LocalVariableAttribute table) {
100 // rename args to the same names as in the LVT
101 for (int i = 0; i < typeTable.tableLength(); i++) {
102 renameVariable(typeTable, i, getNameIndex(table, typeTable.index(i)));
103 }
104 }
105
106 private void renameVariable(LocalVariableAttribute table, int i, int stringId) {
107 // based off of LocalVariableAttribute.nameIndex()
108 ByteArray.write16bit(stringId, table.get(), i * 10 + 6);
109 }
110
111 private int getNameIndex(LocalVariableAttribute table, int index) {
112 for (int i = 0; i < table.tableLength(); i++) {
113 if (table.index(i) == index) {
114 return table.nameIndex(i);
115 }
116 }
117 return 0;
118 }
119}
diff --git a/src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java b/src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java
new file mode 100644
index 0000000..e53e8e7
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java
@@ -0,0 +1,66 @@
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.util.ArrayList;
14import java.util.List;
15
16import cuchaz.enigma.mapping.*;
17import javassist.CtBehavior;
18import javassist.CtClass;
19import javassist.bytecode.CodeAttribute;
20import javassist.bytecode.LocalVariableAttribute;
21
22public class MethodParameterWriter {
23
24 private Translator m_translator;
25
26 public MethodParameterWriter(Translator translator) {
27 m_translator = translator;
28 }
29
30 public void writeMethodArguments(CtClass c) {
31
32 // Procyon will read method arguments from the "MethodParameters" attribute, so write those
33 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
34
35 // if there's a local variable table here, don't write a MethodParameters attribute
36 // let the local variable writer deal with it instead
37 // procyon starts doing really weird things if we give it both attributes
38 CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute();
39 if (codeAttribute != null && codeAttribute.getAttribute(LocalVariableAttribute.tag) != null) {
40 continue;
41 }
42
43 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
44
45 // get the number of arguments
46 Signature signature = behaviorEntry.getSignature();
47 if (signature == null) {
48 // static initializers have no signatures, or arguments
49 continue;
50 }
51 int numParams = signature.getArgumentTypes().size();
52 if (numParams <= 0) {
53 continue;
54 }
55
56 // get the list of argument names
57 List<String> names = new ArrayList<String>(numParams);
58 for (int i = 0; i < numParams; i++) {
59 names.add(m_translator.translate(new ArgumentEntry(behaviorEntry, i, "")));
60 }
61
62 // save the mappings to the class
63 MethodParametersAttribute.updateClass(behavior.getMethodInfo(), names);
64 }
65 }
66}
diff --git a/src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java b/src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java
new file mode 100644
index 0000000..ee7ed87
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java
@@ -0,0 +1,86 @@
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.io.ByteArrayOutputStream;
14import java.io.DataOutputStream;
15import java.io.IOException;
16import java.util.ArrayList;
17import java.util.List;
18
19import javassist.bytecode.AttributeInfo;
20import javassist.bytecode.ConstPool;
21import javassist.bytecode.MethodInfo;
22
23public class MethodParametersAttribute extends AttributeInfo {
24
25 private MethodParametersAttribute(ConstPool pool, List<Integer> parameterNameIndices) {
26 super(pool, "MethodParameters", writeStruct(parameterNameIndices));
27 }
28
29 public static void updateClass(MethodInfo info, List<String> names) {
30
31 // add the names to the class const pool
32 ConstPool constPool = info.getConstPool();
33 List<Integer> parameterNameIndices = new ArrayList<Integer>();
34 for (String name : names) {
35 if (name != null) {
36 parameterNameIndices.add(constPool.addUtf8Info(name));
37 } else {
38 parameterNameIndices.add(0);
39 }
40 }
41
42 // add the attribute to the method
43 info.addAttribute(new MethodParametersAttribute(constPool, parameterNameIndices));
44 }
45
46 private static byte[] writeStruct(List<Integer> parameterNameIndices) {
47 // JVM 8 Spec says the struct looks like this:
48 // http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.24
49 // uint8 num_params
50 // for each param:
51 // uint16 name_index -> points to UTF8 entry in constant pool, or 0 for no entry
52 // uint16 access_flags -> don't care, just set to 0
53
54 ByteArrayOutputStream buf = new ByteArrayOutputStream();
55 DataOutputStream out = new DataOutputStream(buf);
56
57 // NOTE: java hates unsigned integers, so we have to be careful here
58 // the writeShort(), writeByte() methods will read 16,8 low-order bits from the int argument
59 // as long as the int argument is in range of the unsigned short/byte type, it will be written as an unsigned short/byte
60 // if the int is out of range, the byte stream won't look the way we want and weird things will happen
61 final int SIZEOF_UINT8 = 1;
62 final int SIZEOF_UINT16 = 2;
63 final int MAX_UINT8 = (1 << 8) - 1;
64 final int MAX_UINT16 = (1 << 16) - 1;
65
66 try {
67 assert (parameterNameIndices.size() >= 0 && parameterNameIndices.size() <= MAX_UINT8);
68 out.writeByte(parameterNameIndices.size());
69
70 for (Integer index : parameterNameIndices) {
71 assert (index >= 0 && index <= MAX_UINT16);
72 out.writeShort(index);
73
74 // just write 0 for the access flags
75 out.writeShort(0);
76 }
77
78 out.close();
79 byte[] data = buf.toByteArray();
80 assert (data.length == SIZEOF_UINT8 + parameterNameIndices.size() * (SIZEOF_UINT16 + SIZEOF_UINT16));
81 return data;
82 } catch (IOException ex) {
83 throw new Error(ex);
84 }
85 }
86}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
new file mode 100644
index 0000000..fd987f5
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
@@ -0,0 +1,55 @@
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.accessors;
12
13import java.lang.reflect.Field;
14
15public class ClassInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_nameIndex;
19
20 static {
21 try {
22 m_class = Class.forName("javassist.bytecode.ClassInfo");
23 m_nameIndex = m_class.getDeclaredField("name");
24 m_nameIndex.setAccessible(true);
25 } catch (Exception ex) {
26 throw new Error(ex);
27 }
28 }
29
30 public static boolean isType(ConstInfoAccessor accessor) {
31 return m_class.isAssignableFrom(accessor.getItem().getClass());
32 }
33
34 private Object m_item;
35
36 public ClassInfoAccessor(Object item) {
37 m_item = item;
38 }
39
40 public int getNameIndex() {
41 try {
42 return (Integer) m_nameIndex.get(m_item);
43 } catch (Exception ex) {
44 throw new Error(ex);
45 }
46 }
47
48 public void setNameIndex(int val) {
49 try {
50 m_nameIndex.set(m_item, val);
51 } catch (Exception ex) {
52 throw new Error(ex);
53 }
54 }
55}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java
new file mode 100644
index 0000000..2692c06
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java
@@ -0,0 +1,151 @@
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.accessors;
12
13import java.io.*;
14import java.lang.reflect.Constructor;
15import java.lang.reflect.Field;
16import java.lang.reflect.Method;
17
18import cuchaz.enigma.bytecode.InfoType;
19
20public class ConstInfoAccessor {
21
22 private static Class<?> m_class;
23 private static Field m_index;
24 private static Method m_getTag;
25
26 static {
27 try {
28 m_class = Class.forName("javassist.bytecode.ConstInfo");
29 m_index = m_class.getDeclaredField("index");
30 m_index.setAccessible(true);
31 m_getTag = m_class.getMethod("getTag");
32 m_getTag.setAccessible(true);
33 } catch (Exception ex) {
34 throw new Error(ex);
35 }
36 }
37
38 private Object m_item;
39
40 public ConstInfoAccessor(Object item) {
41 if (item == null) {
42 throw new IllegalArgumentException("item cannot be null!");
43 }
44 m_item = item;
45 }
46
47 public ConstInfoAccessor(DataInputStream in) throws IOException {
48 try {
49 // read the entry
50 String className = in.readUTF();
51 int oldIndex = in.readInt();
52
53 // NOTE: ConstInfo instances write a type id (a "tag"), but they don't read it back
54 // so we have to read it here
55 in.readByte();
56
57 Constructor<?> constructor = Class.forName(className).getConstructor(DataInputStream.class, int.class);
58 constructor.setAccessible(true);
59 m_item = constructor.newInstance(in, oldIndex);
60 } catch (IOException ex) {
61 throw ex;
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public Object getItem() {
68 return m_item;
69 }
70
71 public int getIndex() {
72 try {
73 return (Integer) m_index.get(m_item);
74 } catch (Exception ex) {
75 throw new Error(ex);
76 }
77 }
78
79 public void setIndex(int val) {
80 try {
81 m_index.set(m_item, val);
82 } catch (Exception ex) {
83 throw new Error(ex);
84 }
85 }
86
87 public int getTag() {
88 try {
89 return (Integer) m_getTag.invoke(m_item);
90 } catch (Exception ex) {
91 throw new Error(ex);
92 }
93 }
94
95 public ConstInfoAccessor copy() {
96 return new ConstInfoAccessor(copyItem());
97 }
98
99 public Object copyItem() {
100 // I don't know of a simpler way to copy one of these silly things...
101 try {
102 // serialize the item
103 ByteArrayOutputStream buf = new ByteArrayOutputStream();
104 DataOutputStream out = new DataOutputStream(buf);
105 write(out);
106
107 // deserialize the item
108 DataInputStream in = new DataInputStream(new ByteArrayInputStream(buf.toByteArray()));
109 Object item = new ConstInfoAccessor(in).getItem();
110 in.close();
111
112 return item;
113 } catch (Exception ex) {
114 throw new Error(ex);
115 }
116 }
117
118 public void write(DataOutputStream out) throws IOException {
119 try {
120 out.writeUTF(m_item.getClass().getName());
121 out.writeInt(getIndex());
122
123 Method method = m_item.getClass().getMethod("write", DataOutputStream.class);
124 method.setAccessible(true);
125 method.invoke(m_item, out);
126 } catch (IOException ex) {
127 throw ex;
128 } catch (Exception ex) {
129 throw new Error(ex);
130 }
131 }
132
133 @Override
134 public String toString() {
135 try {
136 ByteArrayOutputStream buf = new ByteArrayOutputStream();
137 PrintWriter out = new PrintWriter(buf);
138 Method print = m_item.getClass().getMethod("print", PrintWriter.class);
139 print.setAccessible(true);
140 print.invoke(m_item, out);
141 out.close();
142 return buf.toString().replace("\n", "");
143 } catch (Exception ex) {
144 throw new Error(ex);
145 }
146 }
147
148 public InfoType getType() {
149 return InfoType.getByTag(getTag());
150 }
151}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java
new file mode 100644
index 0000000..0ca82b7
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java
@@ -0,0 +1,74 @@
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.accessors;
12
13import java.lang.reflect.Field;
14
15public class InvokeDynamicInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_bootstrapIndex;
19 private static Field m_nameAndTypeIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.InvokeDynamicInfo");
24 m_bootstrapIndex = m_class.getDeclaredField("bootstrap");
25 m_bootstrapIndex.setAccessible(true);
26 m_nameAndTypeIndex = m_class.getDeclaredField("nameAndType");
27 m_nameAndTypeIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public InvokeDynamicInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getBootstrapIndex() {
44 try {
45 return (Integer) m_bootstrapIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setBootstrapIndex(int val) {
52 try {
53 m_bootstrapIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getNameAndTypeIndex() {
60 try {
61 return (Integer) m_nameAndTypeIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setNameAndTypeIndex(int val) {
68 try {
69 m_nameAndTypeIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java
new file mode 100644
index 0000000..bb9d16b
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java
@@ -0,0 +1,74 @@
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.accessors;
12
13import java.lang.reflect.Field;
14
15public class MemberRefInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_classIndex;
19 private static Field m_nameAndTypeIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.MemberrefInfo");
24 m_classIndex = m_class.getDeclaredField("classIndex");
25 m_classIndex.setAccessible(true);
26 m_nameAndTypeIndex = m_class.getDeclaredField("nameAndTypeIndex");
27 m_nameAndTypeIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public MemberRefInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getClassIndex() {
44 try {
45 return (Integer) m_classIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setClassIndex(int val) {
52 try {
53 m_classIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getNameAndTypeIndex() {
60 try {
61 return (Integer) m_nameAndTypeIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setNameAndTypeIndex(int val) {
68 try {
69 m_nameAndTypeIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java
new file mode 100644
index 0000000..88e42f4
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java
@@ -0,0 +1,74 @@
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.accessors;
12
13import java.lang.reflect.Field;
14
15public class MethodHandleInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_kindIndex;
19 private static Field m_indexIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.MethodHandleInfo");
24 m_kindIndex = m_class.getDeclaredField("refKind");
25 m_kindIndex.setAccessible(true);
26 m_indexIndex = m_class.getDeclaredField("refIndex");
27 m_indexIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public MethodHandleInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getTypeIndex() {
44 try {
45 return (Integer) m_kindIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setTypeIndex(int val) {
52 try {
53 m_kindIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getMethodRefIndex() {
60 try {
61 return (Integer) m_indexIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setMethodRefIndex(int val) {
68 try {
69 m_indexIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java
new file mode 100644
index 0000000..1d039f6
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java
@@ -0,0 +1,55 @@
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.accessors;
12
13import java.lang.reflect.Field;
14
15public class MethodTypeInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_descriptorIndex;
19
20 static {
21 try {
22 m_class = Class.forName("javassist.bytecode.MethodTypeInfo");
23 m_descriptorIndex = m_class.getDeclaredField("descriptor");
24 m_descriptorIndex.setAccessible(true);
25 } catch (Exception ex) {
26 throw new Error(ex);
27 }
28 }
29
30 public static boolean isType(ConstInfoAccessor accessor) {
31 return m_class.isAssignableFrom(accessor.getItem().getClass());
32 }
33
34 private Object m_item;
35
36 public MethodTypeInfoAccessor(Object item) {
37 m_item = item;
38 }
39
40 public int getTypeIndex() {
41 try {
42 return (Integer) m_descriptorIndex.get(m_item);
43 } catch (Exception ex) {
44 throw new Error(ex);
45 }
46 }
47
48 public void setTypeIndex(int val) {
49 try {
50 m_descriptorIndex.set(m_item, val);
51 } catch (Exception ex) {
52 throw new Error(ex);
53 }
54 }
55}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java
new file mode 100644
index 0000000..acba779
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java
@@ -0,0 +1,74 @@
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.accessors;
12
13import java.lang.reflect.Field;
14
15public class NameAndTypeInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_nameIndex;
19 private static Field m_typeIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.NameAndTypeInfo");
24 m_nameIndex = m_class.getDeclaredField("memberName");
25 m_nameIndex.setAccessible(true);
26 m_typeIndex = m_class.getDeclaredField("typeDescriptor");
27 m_typeIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public NameAndTypeInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getNameIndex() {
44 try {
45 return (Integer) m_nameIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setNameIndex(int val) {
52 try {
53 m_nameIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getTypeIndex() {
60 try {
61 return (Integer) m_typeIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setTypeIndex(int val) {
68 try {
69 m_typeIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java
new file mode 100644
index 0000000..b40e0eb
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java
@@ -0,0 +1,55 @@
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.accessors;
12
13import java.lang.reflect.Field;
14
15public class StringInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_stringIndex;
19
20 static {
21 try {
22 m_class = Class.forName("javassist.bytecode.StringInfo");
23 m_stringIndex = m_class.getDeclaredField("string");
24 m_stringIndex.setAccessible(true);
25 } catch (Exception ex) {
26 throw new Error(ex);
27 }
28 }
29
30 public static boolean isType(ConstInfoAccessor accessor) {
31 return m_class.isAssignableFrom(accessor.getItem().getClass());
32 }
33
34 private Object m_item;
35
36 public StringInfoAccessor(Object item) {
37 m_item = item;
38 }
39
40 public int getStringIndex() {
41 try {
42 return (Integer) m_stringIndex.get(m_item);
43 } catch (Exception ex) {
44 throw new Error(ex);
45 }
46 }
47
48 public void setStringIndex(int val) {
49 try {
50 m_stringIndex.set(m_item, val);
51 } catch (Exception ex) {
52 throw new Error(ex);
53 }
54 }
55}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java
new file mode 100644
index 0000000..9303b41
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java
@@ -0,0 +1,28 @@
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.accessors;
12
13public class Utf8InfoAccessor {
14
15 private static Class<?> m_class;
16
17 static {
18 try {
19 m_class = Class.forName("javassist.bytecode.Utf8Info");
20 } catch (Exception ex) {
21 throw new Error(ex);
22 }
23 }
24
25 public static boolean isType(ConstInfoAccessor accessor) {
26 return m_class.isAssignableFrom(accessor.getItem().getClass());
27 }
28}