summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java
blob: 6e2a29d5695ce0ff1002b61efca296dee5b9d5b9 (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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/*******************************************************************************
 * Copyright (c) 2015 Jeff Martin.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public
 * License v3.0 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 * <p>
 * Contributors:
 * Jeff Martin - initial API and implementation
 ******************************************************************************/
package cuchaz.enigma.bytecode;

import com.google.common.collect.Lists;

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

import cuchaz.enigma.Deobfuscator;
import cuchaz.enigma.analysis.JarIndex;
import cuchaz.enigma.mapping.*;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import javassist.bytecode.*;

public class InnerClassWriter {

    private JarIndex index;
    private Translator deobfuscatorTranslator;

    public InnerClassWriter(JarIndex index, Translator deobfuscatorTranslator) {
        this.index = index;
        this.deobfuscatorTranslator = deobfuscatorTranslator;
    }

    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 = this.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 = this.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 = this.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());
            }
        }
    }

    // FIXME: modiffier is not applied to inner class
    public static void changeModifier(CtClass c, InnerClassesAttribute attr, Translator translator)
    {
        ClassPool pool = c.getClassPool();
        for (int i = 0; i < attr.tableLength(); i++) {

            String innerName = attr.innerClass(i);
            // get the inner class full name (which has already been translated)
            ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(innerName));
            try
            {
                CtClass innerClass = pool.get(innerName);
                Mappings.EntryModifier modifier = translator.getModifier(classEntry);
                if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED)
                    ClassRenamer.applyModifier(innerClass, modifier);
            } catch (NotFoundException e)
            {
                // This shouldn't be possible in theory
                //e.printStackTrace();
            }
        }
    }

    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 (!this.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()
		));
		*/
    }
}