aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/me/djtheredstoner/perspectivemod/asm/ClassTransformer.java
blob: c7db68b770033801ec5abf7ab544393fbaee681e (plain)
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
package me.djtheredstoner.perspectivemod.asm;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import me.djtheredstoner.perspectivemod.asm.transformers.ActiveRenderInfoTransformer;
import me.djtheredstoner.perspectivemod.asm.transformers.EntityRendererTransformer;
import me.djtheredstoner.perspectivemod.asm.transformers.MinecraftTransformer;
import me.djtheredstoner.perspectivemod.asm.transformers.RenderManagerTransformer;
import net.minecraft.launchwrapper.IClassTransformer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;

public class ClassTransformer implements IClassTransformer {

    // create a logger to distinguish our errors from a normal error
    private static final Logger LOGGER = LogManager.getLogger("Perspective Mod v4 Transformer");

    // create a map of transformers
    private final Multimap<String, ITransformer> transformerMap = ArrayListMultimap.create();

    // make a jvm flag that could be used to dump transformed classes
    // usable by adding -DdebugBytecode=true to the jvm arguments
    public static final boolean outputBytecode = Boolean.parseBoolean(System.getProperty("debugBytecode", "false"));

    public ClassTransformer() {
        // any transformer will be registered here
        registerTransformer(new EntityRendererTransformer());
        registerTransformer(new RenderManagerTransformer());
        registerTransformer(new MinecraftTransformer());
        registerTransformer(new ActiveRenderInfoTransformer());
    }

    private void registerTransformer(ITransformer transformer) {
        // loop through names of classes
        for (String cls : transformer.getClassName()) {
            // put the classes into the transformer map
            transformerMap.put(cls, transformer);
        }
    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    @Override
    public byte[] transform(String name, String transformedName, byte[] bytes) {
        if (bytes == null) return null;

        // get the list of transformers
        Collection<ITransformer> transformers = transformerMap.get(transformedName);
        // if empty, don't bother trying to run through transformation
        if (transformers.isEmpty()) return bytes;

        // wjat
        ClassReader reader = new ClassReader(bytes);
        ClassNode node = new ClassNode();
        reader.accept(node, ClassReader.EXPAND_FRAMES);

        // for every transformer, perform the transformations
        for (ITransformer transformer : transformers) {
            transformer.transform(node, transformedName);
        }

        // what?????
        ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        try {
            // write™
            node.accept(writer);
        } catch (Throwable t) {
            LOGGER.error("Exception when transforming " + transformedName + " : " + t.getClass().getSimpleName());
            t.printStackTrace();
        }

        if (outputBytecode) {
            File bytecodeDirectory = new File("bytecode");
            String transformedClassName;

            // anonymous classes
            if (transformedName.contains("$")) {
                transformedClassName = transformedName.replace('$', '.') + ".class";
            } else {
                transformedClassName = transformedName + ".class";
            }

            if (!bytecodeDirectory.exists()) {
                bytecodeDirectory.mkdirs();
            }

            File bytecodeOutput = new File(bytecodeDirectory, transformedClassName);

            try {
                if (!bytecodeOutput.exists()) {
                    bytecodeOutput.createNewFile();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

            try (FileOutputStream os = new FileOutputStream(bytecodeOutput)) {
                // write to the generated class to /run/bytecode/classfile.class
                // with the class bytes from transforming
                os.write(writer.toByteArray());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        // return the written bytes and finalize transform
        return writer.toByteArray();
    }

}