summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/Deobfuscator.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/cuchaz/enigma/Deobfuscator.java')
-rw-r--r--src/main/java/cuchaz/enigma/Deobfuscator.java435
1 files changed, 0 insertions, 435 deletions
diff --git a/src/main/java/cuchaz/enigma/Deobfuscator.java b/src/main/java/cuchaz/enigma/Deobfuscator.java
deleted file mode 100644
index 32f7aa7..0000000
--- a/src/main/java/cuchaz/enigma/Deobfuscator.java
+++ /dev/null
@@ -1,435 +0,0 @@
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 ******************************************************************************/
11
12package cuchaz.enigma;
13
14import com.google.common.base.Functions;
15import com.google.common.base.Stopwatch;
16import com.google.common.collect.Streams;
17import com.strobel.assembler.metadata.ITypeLoader;
18import com.strobel.assembler.metadata.MetadataSystem;
19import com.strobel.assembler.metadata.TypeDefinition;
20import com.strobel.assembler.metadata.TypeReference;
21import com.strobel.decompiler.DecompilerSettings;
22import com.strobel.decompiler.languages.java.ast.CompilationUnit;
23import cuchaz.enigma.analysis.EntryReference;
24import cuchaz.enigma.analysis.IndexTreeBuilder;
25import cuchaz.enigma.analysis.ParsedJar;
26import cuchaz.enigma.analysis.index.JarIndex;
27import cuchaz.enigma.api.EntryNameProposer;
28import cuchaz.enigma.api.JarProcessor;
29import cuchaz.enigma.bytecode.translators.SourceFixVisitor;
30import cuchaz.enigma.bytecode.translators.TranslationClassVisitor;
31import cuchaz.enigma.translation.Translatable;
32import cuchaz.enigma.translation.Translator;
33import cuchaz.enigma.translation.mapping.*;
34import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree;
35import cuchaz.enigma.translation.mapping.tree.EntryTree;
36import cuchaz.enigma.translation.representation.entry.ClassEntry;
37import cuchaz.enigma.translation.representation.entry.Entry;
38import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
39import cuchaz.enigma.translation.representation.entry.MethodEntry;
40import org.objectweb.asm.ClassVisitor;
41import org.objectweb.asm.ClassWriter;
42import org.objectweb.asm.Opcodes;
43import org.objectweb.asm.tree.ClassNode;
44
45import java.io.File;
46import java.io.FileOutputStream;
47import java.io.IOException;
48import java.io.Writer;
49import java.nio.file.Files;
50import java.nio.file.Path;
51import java.util.*;
52import java.util.concurrent.ConcurrentHashMap;
53import java.util.concurrent.atomic.AtomicInteger;
54import java.util.function.Consumer;
55import java.util.jar.JarEntry;
56import java.util.jar.JarFile;
57import java.util.jar.JarOutputStream;
58import java.util.stream.Collectors;
59import java.util.stream.Stream;
60
61public class Deobfuscator {
62
63 private final ServiceLoader<JarProcessor> jarProcessors = ServiceLoader.load(JarProcessor.class);
64 private final ServiceLoader<EntryNameProposer> nameProposers = ServiceLoader.load(EntryNameProposer.class);
65
66 private final ParsedJar parsedJar;
67 private final JarIndex jarIndex;
68 private final IndexTreeBuilder indexTreeBuilder;
69
70 private final SourceProvider obfSourceProvider;
71
72 private EntryRemapper mapper;
73
74 public Deobfuscator(ParsedJar jar, Consumer<String> listener) {
75 this.parsedJar = jar;
76
77 // build the jar index
78 this.jarIndex = JarIndex.empty();
79 this.jarIndex.indexJar(this.parsedJar, listener);
80
81 listener.accept("Processing jar");
82 this.jarProcessors.forEach(processor -> processor.accept(parsedJar, jarIndex));
83
84 this.indexTreeBuilder = new IndexTreeBuilder(jarIndex);
85
86 listener.accept("Preparing...");
87
88 CompiledSourceTypeLoader typeLoader = new CompiledSourceTypeLoader(parsedJar);
89 typeLoader.addVisitor(visitor -> new SourceFixVisitor(Opcodes.ASM5, visitor, jarIndex));
90
91 this.obfSourceProvider = new SourceProvider(SourceProvider.createSettings(), typeLoader);
92
93 // init mappings
94 mapper = new EntryRemapper(jarIndex);
95 }
96
97 public Deobfuscator(JarFile jar, Consumer<String> listener) throws IOException {
98 this(new ParsedJar(jar), listener);
99 }
100
101 public Deobfuscator(ParsedJar jar) {
102 this(jar, (msg) -> {
103 });
104 }
105
106 public Deobfuscator(JarFile jar) throws IOException {
107 this(jar, (msg) -> {
108 });
109 }
110
111 public Stream<EntryNameProposer> getNameProposers() {
112 return Streams.stream(nameProposers);
113 }
114
115 public ParsedJar getJar() {
116 return this.parsedJar;
117 }
118
119 public JarIndex getJarIndex() {
120 return this.jarIndex;
121 }
122
123 public IndexTreeBuilder getIndexTreeBuilder() {
124 return indexTreeBuilder;
125 }
126
127 public EntryRemapper getMapper() {
128 return this.mapper;
129 }
130
131 public void setMappings(EntryTree<EntryMapping> mappings) {
132 setMappings(mappings, ProgressListener.VOID);
133 }
134
135 public void setMappings(EntryTree<EntryMapping> mappings, ProgressListener progress) {
136 if (mappings != null) {
137 Collection<Entry<?>> dropped = dropMappings(mappings, progress);
138 mapper = new EntryRemapper(jarIndex, mappings);
139
140 DeltaTrackingTree<EntryMapping> obfToDeobf = mapper.getObfToDeobf();
141 for (Entry<?> entry : dropped) {
142 obfToDeobf.trackChange(entry);
143 }
144 } else {
145 mapper = new EntryRemapper(jarIndex);
146 }
147 }
148
149 private Collection<Entry<?>> dropMappings(EntryTree<EntryMapping> mappings, ProgressListener progress) {
150 // drop mappings that don't match the jar
151 MappingsChecker checker = new MappingsChecker(jarIndex, mappings);
152 MappingsChecker.Dropped dropped = checker.dropBrokenMappings(progress);
153
154 Map<Entry<?>, String> droppedMappings = dropped.getDroppedMappings();
155 for (Map.Entry<Entry<?>, String> mapping : droppedMappings.entrySet()) {
156 System.out.println("WARNING: Couldn't find " + mapping.getKey() + " (" + mapping.getValue() + ") in jar. Mapping was dropped.");
157 }
158
159 return droppedMappings.keySet();
160 }
161
162 public void getSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) {
163 for (ClassEntry obfClassEntry : this.jarIndex.getEntryIndex().getClasses()) {
164 // skip inner classes
165 if (obfClassEntry.isInnerClass()) {
166 continue;
167 }
168
169 // separate the classes
170 ClassEntry deobfClassEntry = mapper.deobfuscate(obfClassEntry);
171 if (!deobfClassEntry.equals(obfClassEntry)) {
172 // if the class has a mapping, clearly it's deobfuscated
173 deobfClasses.add(obfClassEntry);
174 } else if (obfClassEntry.getPackageName() != null) {
175 // also call it deobufscated if it's not in the none package
176 deobfClasses.add(obfClassEntry);
177 } else {
178 // otherwise, assume it's still obfuscated
179 obfClasses.add(obfClassEntry);
180 }
181 }
182 }
183
184 public SourceProvider getObfSourceProvider() {
185 return obfSourceProvider;
186 }
187
188 public void writeSources(Path outputDirectory, ProgressListener progress) {
189 // get the classes to decompile
190 Collection<ClassEntry> classEntries = jarIndex.getEntryIndex().getClasses();
191
192 Stopwatch stopwatch = Stopwatch.createStarted();
193
194 try {
195 Translator deobfuscator = mapper.getDeobfuscator();
196
197 // deobfuscate everything first
198 Map<String, ClassNode> translatedNodes = deobfuscateClasses(progress, classEntries, deobfuscator);
199
200 decompileClasses(outputDirectory, progress, translatedNodes);
201 } finally {
202 stopwatch.stop();
203
204 System.out.println("writeSources Done in : " + stopwatch.toString());
205 }
206 }
207
208 private Map<String, ClassNode> deobfuscateClasses(ProgressListener progress, Collection<ClassEntry> classEntries, Translator translator) {
209 AtomicInteger count = new AtomicInteger();
210 if (progress != null) {
211 progress.init(classEntries.size(), "Deobfuscating classes...");
212 }
213
214 return classEntries.parallelStream()
215 .map(entry -> {
216 ClassEntry translatedEntry = translator.translate(entry);
217 if (progress != null) {
218 progress.step(count.getAndIncrement(), translatedEntry.toString());
219 }
220
221 ClassNode node = parsedJar.getClassNode(entry.getFullName());
222 if (node != null) {
223 ClassNode translatedNode = new ClassNode();
224 node.accept(new TranslationClassVisitor(translator, Opcodes.ASM5, translatedNode));
225 return translatedNode;
226 }
227
228 return null;
229 })
230 .filter(Objects::nonNull)
231 .collect(Collectors.toMap(n -> n.name, Functions.identity()));
232 }
233
234 private void decompileClasses(Path outputDirectory, ProgressListener progress, Map<String, ClassNode> translatedClasses) {
235 Collection<ClassNode> decompileClasses = translatedClasses.values().stream()
236 .filter(classNode -> classNode.name.indexOf('$') == -1)
237 .collect(Collectors.toList());
238
239 if (progress != null) {
240 progress.init(decompileClasses.size(), "Decompiling classes...");
241 }
242
243 //create a common instance outside the loop as mappings shouldn't be changing while this is happening
244 CompiledSourceTypeLoader typeLoader = new CompiledSourceTypeLoader(translatedClasses::get);
245 typeLoader.addVisitor(visitor -> new SourceFixVisitor(Opcodes.ASM5, visitor, jarIndex));
246
247 //synchronized to make sure the parallelStream doesn't CME with the cache
248 ITypeLoader synchronizedTypeLoader = new SynchronizedTypeLoader(typeLoader);
249
250 MetadataSystem metadataSystem = new Deobfuscator.NoRetryMetadataSystem(synchronizedTypeLoader);
251
252 //ensures methods are loaded on classload and prevents race conditions
253 metadataSystem.setEagerMethodLoadingEnabled(true);
254
255 DecompilerSettings settings = SourceProvider.createSettings();
256 SourceProvider sourceProvider = new SourceProvider(settings, synchronizedTypeLoader, metadataSystem);
257
258 AtomicInteger count = new AtomicInteger();
259
260 decompileClasses.parallelStream().forEach(translatedNode -> {
261 if (progress != null) {
262 progress.step(count.getAndIncrement(), translatedNode.name);
263 }
264
265 decompileClass(outputDirectory, translatedNode, sourceProvider);
266 });
267 }
268
269 private void decompileClass(Path outputDirectory, ClassNode translatedNode, SourceProvider sourceProvider) {
270 try {
271 // get the source
272 CompilationUnit sourceTree = sourceProvider.getSources(translatedNode.name);
273
274 Path path = outputDirectory.resolve(translatedNode.name.replace('.', '/') + ".java");
275 Files.createDirectories(path.getParent());
276
277 try (Writer writer = Files.newBufferedWriter(path)) {
278 sourceProvider.writeSource(writer, sourceTree);
279 }
280 } catch (Throwable t) {
281 // don't crash the whole world here, just log the error and keep going
282 // TODO: set up logback via log4j
283 System.err.println("Unable to decompile class " + translatedNode.name);
284 t.printStackTrace(System.err);
285 }
286 }
287
288 public void writeTransformedJar(File out, ProgressListener progress) {
289 Translator deobfuscator = mapper.getDeobfuscator();
290 writeTransformedJar(out, progress, (node, visitor) -> {
291 ClassEntry entry = new ClassEntry(node.name);
292 node.accept(new TranslationClassVisitor(deobfuscator, Opcodes.ASM5, visitor));
293 return deobfuscator.translate(entry).getFullName();
294 });
295 }
296
297 public void writeTransformedJar(File out, ProgressListener progress, ClassTransformer transformer) {
298 try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) {
299 if (progress != null) {
300 progress.init(parsedJar.getClassCount(), "Transforming classes...");
301 }
302
303 AtomicInteger count = new AtomicInteger();
304 parsedJar.visitNode(node -> {
305 if (progress != null) {
306 progress.step(count.getAndIncrement(), node.name);
307 }
308
309 try {
310 ClassWriter writer = new ClassWriter(0);
311 String transformedName = transformer.transform(node, writer);
312 outJar.putNextEntry(new JarEntry(transformedName.replace('.', '/') + ".class"));
313 outJar.write(writer.toByteArray());
314 outJar.closeEntry();
315 } catch (Throwable t) {
316 throw new Error("Unable to transform class " + node.name, t);
317 }
318 });
319 } catch (IOException ex) {
320 throw new Error("Unable to write to Jar file!");
321 }
322 }
323
324 public AccessModifier getModifier(Entry<?> entry) {
325 EntryMapping mapping = mapper.getDeobfMapping(entry);
326 if (mapping == null) {
327 return AccessModifier.UNCHANGED;
328 }
329 return mapping.getAccessModifier();
330 }
331
332 public void changeModifier(Entry<?> entry, AccessModifier modifier) {
333 EntryMapping mapping = mapper.getDeobfMapping(entry);
334 if (mapping != null) {
335 mapper.mapFromObf(entry, new EntryMapping(mapping.getTargetName(), modifier));
336 } else {
337 mapper.mapFromObf(entry, new EntryMapping(entry.getName(), modifier));
338 }
339 }
340
341 public boolean isRenamable(Entry<?> obfEntry) {
342 if (obfEntry instanceof MethodEntry) {
343 // HACKHACK: Object methods are not obfuscated identifiers
344 MethodEntry obfMethodEntry = (MethodEntry) obfEntry;
345 String name = obfMethodEntry.getName();
346 String sig = obfMethodEntry.getDesc().toString();
347 if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) {
348 return false;
349 } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) {
350 return false;
351 } else if (name.equals("finalize") && sig.equals("()V")) {
352 return false;
353 } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) {
354 return false;
355 } else if (name.equals("hashCode") && sig.equals("()I")) {
356 return false;
357 } else if (name.equals("notify") && sig.equals("()V")) {
358 return false;
359 } else if (name.equals("notifyAll") && sig.equals("()V")) {
360 return false;
361 } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) {
362 return false;
363 } else if (name.equals("wait") && sig.equals("()V")) {
364 return false;
365 } else if (name.equals("wait") && sig.equals("(J)V")) {
366 return false;
367 } else if (name.equals("wait") && sig.equals("(JI)V")) {
368 return false;
369 }
370 } else if (obfEntry instanceof LocalVariableEntry && !((LocalVariableEntry) obfEntry).isArgument()) {
371 return false;
372 }
373
374 return this.jarIndex.getEntryIndex().hasEntry(obfEntry);
375 }
376
377 public boolean isRenamable(EntryReference<Entry<?>, Entry<?>> obfReference) {
378 return obfReference.isNamed() && isRenamable(obfReference.getNameableEntry());
379 }
380
381 public boolean isRemapped(Entry<?> entry) {
382 EntryResolver resolver = mapper.getObfResolver();
383 DeltaTrackingTree<EntryMapping> mappings = mapper.getObfToDeobf();
384 return resolver.resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT).stream()
385 .anyMatch(mappings::contains);
386 }
387
388 public void rename(Entry<?> obfEntry, String newName) {
389 mapper.mapFromObf(obfEntry, new EntryMapping(newName));
390 }
391
392 public void removeMapping(Entry<?> obfEntry) {
393 mapper.removeByObf(obfEntry);
394 }
395
396 public void markAsDeobfuscated(Entry<?> obfEntry) {
397 mapper.mapFromObf(obfEntry, new EntryMapping(mapper.deobfuscate(obfEntry).getName()));
398 }
399
400 public <T extends Translatable> T deobfuscate(T translatable) {
401 return mapper.deobfuscate(translatable);
402 }
403
404 public interface ClassTransformer {
405 String transform(ClassNode node, ClassVisitor visitor);
406 }
407
408 public static class NoRetryMetadataSystem extends MetadataSystem {
409 private final Set<String> _failedTypes = Collections.newSetFromMap(new ConcurrentHashMap<>());
410
411 public NoRetryMetadataSystem(final ITypeLoader typeLoader) {
412 super(typeLoader);
413 }
414
415 @Override
416 protected synchronized TypeDefinition resolveType(final String descriptor, final boolean mightBePrimitive) {
417 if (_failedTypes.contains(descriptor)) {
418 return null;
419 }
420
421 final TypeDefinition result = super.resolveType(descriptor, mightBePrimitive);
422
423 if (result == null) {
424 _failedTypes.add(descriptor);
425 }
426
427 return result;
428 }
429
430 @Override
431 public synchronized TypeDefinition resolve(final TypeReference type) {
432 return super.resolve(type);
433 }
434 }
435}