summaryrefslogtreecommitdiff
path: root/src/cuchaz/enigma/bytecode/InnerClassWriter.java
blob: 9cdfd685991a8f3ad2279f5e76ef26c5a883e10c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/*******************************************************************************
 * Copyright (c) 2014 Jeff Martin.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 * 
 * Contributors:
 *     Jeff Martin - initial API and implementation
 ******************************************************************************/
package cuchaz.enigma.bytecode;

import java.util.Collection;
import java.util.List;

import com.google.common.collect.Lists;

import javassist.CtClass;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.ConstPool;
import javassist.bytecode.EnclosingMethodAttribute;
import javassist.bytecode.InnerClassesAttribute;
import cuchaz.enigma.analysis.JarIndex;
import cuchaz.enigma.mapping.BehaviorEntry;
import cuchaz.enigma.mapping.ClassEntry;
import cuchaz.enigma.mapping.EntryFactory;

public class InnerClassWriter {
	
	private JarIndex m_index;
	
	public InnerClassWriter(JarIndex index) {
		m_index = index;
	}
	
	public void write(CtClass c) {
		
		// don't change anything if there's already an attribute there
		InnerClassesAttribute oldAttr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
		if (oldAttr != null) {
			// bail!
			return;
		}
		
		ClassEntry obfClassEntry = EntryFactory.getClassEntry(c);
		List<ClassEntry> obfClassChain = m_index.getObfClassChain(obfClassEntry);
		
		boolean isInnerClass = obfClassChain.size() > 1;
		if (isInnerClass) {
			
			// it's an inner class, rename it to the fully qualified name
			c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName());
			
			BehaviorEntry caller = m_index.getAnonymousClassCaller(obfClassEntry);
			if (caller != null) {
				
				// write the enclosing method attribute
				if (caller.getName().equals("<clinit>")) {
					c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName()));
				} else {
					c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature().toString()));
				}
			}
		}
		
		// does this class have any inner classes?
		Collection<ClassEntry> obfInnerClassEntries = m_index.getInnerClasses(obfClassEntry);
		
		if (isInnerClass || !obfInnerClassEntries.isEmpty()) {
			
			// create an inner class attribute
			InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool());
			c.getClassFile().addAttribute(attr);
			
			// write the ancestry, but not the outermost class
			for (int i=1; i<obfClassChain.size(); i++) {
				ClassEntry obfInnerClassEntry = obfClassChain.get(i);
				writeInnerClass(attr, obfClassChain, obfInnerClassEntry);
				
				// update references to use the fully qualified inner class name
				c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(obfClassChain).getName());
			}
			
			// write the inner classes
			for (ClassEntry obfInnerClassEntry : obfInnerClassEntries) {
				
				// extend the class chain
				List<ClassEntry> extendedObfClassChain = Lists.newArrayList(obfClassChain);
				extendedObfClassChain.add(obfInnerClassEntry);
				
				writeInnerClass(attr, extendedObfClassChain, obfInnerClassEntry);
				
				// update references to use the fully qualified inner class name
				c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName());
			}
		}
	}
	
	private void writeInnerClass(InnerClassesAttribute attr, List<ClassEntry> obfClassChain, ClassEntry obfClassEntry) {
		
		// get the new inner class name
		ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain);
		ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry();
		
		// here's what the JVM spec says about the InnerClasses attribute
		// append(inner, parent, 0 if anonymous else simple name, flags);
		
		// update the attribute with this inner class
		ConstPool constPool = attr.getConstPool();
		int innerClassIndex = constPool.addClassInfo(obfInnerClassEntry.getName());
		int parentClassIndex = constPool.addClassInfo(obfOuterClassEntry.getName());
		int innerClassNameIndex = 0;
		int accessFlags = AccessFlag.PUBLIC;
		// TODO: need to figure out if we can put static or not
		if (!m_index.isAnonymousClass(obfClassEntry)) {
			innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnermostClassName());
		}
		
		attr.append(innerClassIndex, parentClassIndex, innerClassNameIndex, accessFlags);
		
		/* DEBUG 
		System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)",
			obfClassEntry,
			attr.innerClass(attr.tableLength() - 1),
			attr.outerClass(attr.tableLength() - 1),
			attr.innerName(attr.tableLength() - 1),
			Constants.NonePackage + "/" + obfInnerClassName,
			obfClassEntry.getName()
		));
		*/
	}
}