summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/EnigmaProject.java
diff options
context:
space:
mode:
authorGravatar Modmuss502019-06-19 18:51:31 +0100
committerGravatar GitHub2019-06-19 18:51:31 +0100
commit546c617598b10c341fe6549678803f6ac0c95619 (patch)
treed818bcebf7634ed5b716ee29619725fdc29a04e8 /src/main/java/cuchaz/enigma/EnigmaProject.java
parentfix unwanted declaration navigation during Quick Find (diff)
parentParse profile json from cli args (diff)
downloadenigma-fork-546c617598b10c341fe6549678803f6ac0c95619.tar.gz
enigma-fork-546c617598b10c341fe6549678803f6ac0c95619.tar.xz
enigma-fork-546c617598b10c341fe6549678803f6ac0c95619.zip
Merge pull request #135 from gegy1000/proposal-tweak
Plugin rework
Diffstat (limited to 'src/main/java/cuchaz/enigma/EnigmaProject.java')
-rw-r--r--src/main/java/cuchaz/enigma/EnigmaProject.java285
1 files changed, 285 insertions, 0 deletions
diff --git a/src/main/java/cuchaz/enigma/EnigmaProject.java b/src/main/java/cuchaz/enigma/EnigmaProject.java
new file mode 100644
index 0000000..bb2f7fa
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/EnigmaProject.java
@@ -0,0 +1,285 @@
1package cuchaz.enigma;
2
3import com.google.common.base.Functions;
4import com.strobel.assembler.metadata.ITypeLoader;
5import com.strobel.assembler.metadata.MetadataSystem;
6import com.strobel.decompiler.DecompilerSettings;
7import com.strobel.decompiler.languages.java.ast.CompilationUnit;
8import cuchaz.enigma.analysis.ClassCache;
9import cuchaz.enigma.analysis.EntryReference;
10import cuchaz.enigma.analysis.index.JarIndex;
11import cuchaz.enigma.bytecode.translators.SourceFixVisitor;
12import cuchaz.enigma.bytecode.translators.TranslationClassVisitor;
13import cuchaz.enigma.translation.Translator;
14import cuchaz.enigma.translation.mapping.EntryMapping;
15import cuchaz.enigma.translation.mapping.EntryRemapper;
16import cuchaz.enigma.translation.mapping.MappingsChecker;
17import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree;
18import cuchaz.enigma.translation.mapping.tree.EntryTree;
19import cuchaz.enigma.translation.representation.entry.ClassEntry;
20import cuchaz.enigma.translation.representation.entry.Entry;
21import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
22import cuchaz.enigma.translation.representation.entry.MethodEntry;
23import org.objectweb.asm.ClassWriter;
24import org.objectweb.asm.Opcodes;
25import org.objectweb.asm.tree.ClassNode;
26
27import java.io.BufferedWriter;
28import java.io.IOException;
29import java.io.StringWriter;
30import java.nio.file.Files;
31import java.nio.file.Path;
32import java.util.Collection;
33import java.util.Map;
34import java.util.Objects;
35import java.util.concurrent.atomic.AtomicInteger;
36import java.util.jar.JarEntry;
37import java.util.jar.JarOutputStream;
38import java.util.stream.Collectors;
39
40public class EnigmaProject {
41 private final Enigma enigma;
42
43 private final ClassCache classCache;
44 private final JarIndex jarIndex;
45
46 private EntryRemapper mapper;
47
48 public EnigmaProject(Enigma enigma, ClassCache classCache, JarIndex jarIndex) {
49 this.enigma = enigma;
50 this.classCache = classCache;
51 this.jarIndex = jarIndex;
52
53 this.mapper = EntryRemapper.empty(jarIndex);
54 }
55
56 public void setMappings(EntryTree<EntryMapping> mappings) {
57 if (mappings != null) {
58 mapper = EntryRemapper.mapped(jarIndex, mappings);
59 } else {
60 mapper = EntryRemapper.empty(jarIndex);
61 }
62 }
63
64 public Enigma getEnigma() {
65 return enigma;
66 }
67
68 public ClassCache getClassCache() {
69 return classCache;
70 }
71
72 public JarIndex getJarIndex() {
73 return jarIndex;
74 }
75
76 public EntryRemapper getMapper() {
77 return mapper;
78 }
79
80 public void dropMappings(ProgressListener progress) {
81 DeltaTrackingTree<EntryMapping> mappings = mapper.getObfToDeobf();
82
83 Collection<Entry<?>> dropped = dropMappings(mappings, progress);
84 for (Entry<?> entry : dropped) {
85 mappings.trackChange(entry);
86 }
87 }
88
89 private Collection<Entry<?>> dropMappings(EntryTree<EntryMapping> mappings, ProgressListener progress) {
90 // drop mappings that don't match the jar
91 MappingsChecker checker = new MappingsChecker(jarIndex, mappings);
92 MappingsChecker.Dropped dropped = checker.dropBrokenMappings(progress);
93
94 Map<Entry<?>, String> droppedMappings = dropped.getDroppedMappings();
95 for (Map.Entry<Entry<?>, String> mapping : droppedMappings.entrySet()) {
96 System.out.println("WARNING: Couldn't find " + mapping.getKey() + " (" + mapping.getValue() + ") in jar. Mapping was dropped.");
97 }
98
99 return droppedMappings.keySet();
100 }
101
102 public boolean isRenamable(Entry<?> obfEntry) {
103 if (obfEntry instanceof MethodEntry) {
104 // HACKHACK: Object methods are not obfuscated identifiers
105 MethodEntry obfMethodEntry = (MethodEntry) obfEntry;
106 String name = obfMethodEntry.getName();
107 String sig = obfMethodEntry.getDesc().toString();
108 if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) {
109 return false;
110 } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) {
111 return false;
112 } else if (name.equals("finalize") && sig.equals("()V")) {
113 return false;
114 } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) {
115 return false;
116 } else if (name.equals("hashCode") && sig.equals("()I")) {
117 return false;
118 } else if (name.equals("notify") && sig.equals("()V")) {
119 return false;
120 } else if (name.equals("notifyAll") && sig.equals("()V")) {
121 return false;
122 } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) {
123 return false;
124 } else if (name.equals("wait") && sig.equals("()V")) {
125 return false;
126 } else if (name.equals("wait") && sig.equals("(J)V")) {
127 return false;
128 } else if (name.equals("wait") && sig.equals("(JI)V")) {
129 return false;
130 }
131 } else if (obfEntry instanceof LocalVariableEntry && !((LocalVariableEntry) obfEntry).isArgument()) {
132 return false;
133 }
134
135 return this.jarIndex.getEntryIndex().hasEntry(obfEntry);
136 }
137
138 public boolean isRenamable(EntryReference<Entry<?>, Entry<?>> obfReference) {
139 return obfReference.isNamed() && isRenamable(obfReference.getNameableEntry());
140 }
141
142 public JarExport exportRemappedJar(ProgressListener progress) {
143 Collection<ClassEntry> classEntries = jarIndex.getEntryIndex().getClasses();
144 Translator deobfuscator = mapper.getDeobfuscator();
145
146 AtomicInteger count = new AtomicInteger();
147 progress.init(classEntries.size(), "Deobfuscating classes...");
148
149 Map<String, ClassNode> compiled = classEntries.parallelStream()
150 .map(entry -> {
151 ClassEntry translatedEntry = deobfuscator.translate(entry);
152 progress.step(count.getAndIncrement(), translatedEntry.toString());
153
154 ClassNode node = classCache.getClassNode(entry.getFullName());
155 if (node != null) {
156 ClassNode translatedNode = new ClassNode();
157 node.accept(new TranslationClassVisitor(deobfuscator, Opcodes.ASM5, translatedNode));
158 return translatedNode;
159 }
160
161 return null;
162 })
163 .filter(Objects::nonNull)
164 .collect(Collectors.toMap(n -> n.name, Functions.identity()));
165
166 return new JarExport(jarIndex, compiled);
167 }
168
169 public static final class JarExport {
170 private final JarIndex jarIndex;
171 private final Map<String, ClassNode> compiled;
172
173 JarExport(JarIndex jarIndex, Map<String, ClassNode> compiled) {
174 this.jarIndex = jarIndex;
175 this.compiled = compiled;
176 }
177
178 public void write(Path path, ProgressListener progress) throws IOException {
179 progress.init(this.compiled.size(), "Writing jar...");
180
181 try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(path))) {
182 AtomicInteger count = new AtomicInteger();
183
184 for (ClassNode node : this.compiled.values()) {
185 progress.step(count.getAndIncrement(), node.name);
186
187 String entryName = node.name.replace('.', '/') + ".class";
188
189 ClassWriter writer = new ClassWriter(0);
190 node.accept(writer);
191
192 out.putNextEntry(new JarEntry(entryName));
193 out.write(writer.toByteArray());
194 out.closeEntry();
195 }
196 }
197 }
198
199 public SourceExport decompile(ProgressListener progress) {
200 Collection<ClassNode> classes = this.compiled.values().stream()
201 .filter(classNode -> classNode.name.indexOf('$') == -1)
202 .collect(Collectors.toList());
203
204 progress.init(classes.size(), "Decompiling classes...");
205
206 //create a common instance outside the loop as mappings shouldn't be changing while this is happening
207 CompiledSourceTypeLoader typeLoader = new CompiledSourceTypeLoader(this.compiled::get);
208 typeLoader.addVisitor(visitor -> new SourceFixVisitor(Opcodes.ASM5, visitor, jarIndex));
209
210 //synchronized to make sure the parallelStream doesn't CME with the cache
211 ITypeLoader synchronizedTypeLoader = new SynchronizedTypeLoader(typeLoader);
212
213 MetadataSystem metadataSystem = new NoRetryMetadataSystem(synchronizedTypeLoader);
214
215 //ensures methods are loaded on classload and prevents race conditions
216 metadataSystem.setEagerMethodLoadingEnabled(true);
217
218 DecompilerSettings settings = SourceProvider.createSettings();
219 SourceProvider sourceProvider = new SourceProvider(settings, synchronizedTypeLoader, metadataSystem);
220
221 AtomicInteger count = new AtomicInteger();
222
223 Collection<ClassSource> decompiled = classes.parallelStream()
224 .map(translatedNode -> {
225 progress.step(count.getAndIncrement(), translatedNode.name);
226
227 String source = decompileClass(translatedNode, sourceProvider);
228 return new ClassSource(translatedNode.name, source);
229 })
230 .collect(Collectors.toList());
231
232 return new SourceExport(decompiled);
233 }
234
235 private String decompileClass(ClassNode translatedNode, SourceProvider sourceProvider) {
236 CompilationUnit sourceTree = sourceProvider.getSources(translatedNode.name);
237
238 StringWriter writer = new StringWriter();
239 sourceProvider.writeSource(writer, sourceTree);
240
241 return writer.toString();
242 }
243 }
244
245 public static final class SourceExport {
246 private final Collection<ClassSource> decompiled;
247
248 SourceExport(Collection<ClassSource> decompiled) {
249 this.decompiled = decompiled;
250 }
251
252 public void write(Path path, ProgressListener progress) throws IOException {
253 progress.init(decompiled.size(), "Writing sources...");
254
255 int count = 0;
256 for (ClassSource source : decompiled) {
257 progress.step(count++, source.name);
258
259 Path sourcePath = source.resolvePath(path);
260 source.writeTo(sourcePath);
261 }
262 }
263 }
264
265 private static class ClassSource {
266 private final String name;
267 private final String source;
268
269 ClassSource(String name, String source) {
270 this.name = name;
271 this.source = source;
272 }
273
274 void writeTo(Path path) throws IOException {
275 Files.createDirectories(path.getParent());
276 try (BufferedWriter writer = Files.newBufferedWriter(path)) {
277 writer.write(source);
278 }
279 }
280
281 Path resolvePath(Path root) {
282 return root.resolve(name.replace('.', '/') + ".java");
283 }
284 }
285}