summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.gradle27
-rw-r--r--src/main/java/cuchaz/enigma/Deobfuscator.java435
-rw-r--r--src/main/java/cuchaz/enigma/Enigma.java114
-rw-r--r--src/main/java/cuchaz/enigma/EnigmaProfile.java51
-rw-r--r--src/main/java/cuchaz/enigma/EnigmaProject.java285
-rw-r--r--src/main/java/cuchaz/enigma/EnigmaServices.java21
-rw-r--r--src/main/java/cuchaz/enigma/Main.java3
-rw-r--r--src/main/java/cuchaz/enigma/NoRetryMetadataSystem.java38
-rw-r--r--src/main/java/cuchaz/enigma/ProgressListener.java18
-rw-r--r--src/main/java/cuchaz/enigma/SourceProvider.java2
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ClassCache.java127
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ParsedJar.java129
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/JarIndex.java22
-rw-r--r--src/main/java/cuchaz/enigma/api/EnigmaPlugin.java5
-rw-r--r--src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java9
-rw-r--r--src/main/java/cuchaz/enigma/api/JarProcessor.java8
-rw-r--r--src/main/java/cuchaz/enigma/api/service/EnigmaService.java4
-rw-r--r--src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java11
-rw-r--r--src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java5
-rw-r--r--src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java29
-rw-r--r--src/main/java/cuchaz/enigma/api/service/JarIndexerService.java10
-rw-r--r--src/main/java/cuchaz/enigma/api/service/NameProposalService.java (renamed from src/main/java/cuchaz/enigma/api/EntryNameProposer.java)6
-rw-r--r--src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java9
-rw-r--r--src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java32
-rw-r--r--src/main/java/cuchaz/enigma/command/Command.java20
-rw-r--r--src/main/java/cuchaz/enigma/command/DecompileCommand.java20
-rw-r--r--src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java20
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassSelector.java4
-rw-r--r--src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java38
-rw-r--r--src/main/java/cuchaz/enigma/gui/Gui.java45
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiController.java343
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java3
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java2
-rw-r--r--src/main/java/cuchaz/enigma/gui/elements/MenuBar.java33
-rw-r--r--src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java6
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java10
-rw-r--r--src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java10
-rw-r--r--src/test/java/cuchaz/enigma/TestDeobfed.java22
-rw-r--r--src/test/java/cuchaz/enigma/TestDeobfuscator.java12
-rw-r--r--src/test/java/cuchaz/enigma/TestInnerClasses.java15
-rw-r--r--src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java1
-rw-r--r--src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java8
-rw-r--r--src/test/java/cuchaz/enigma/TestSourceIndex.java6
-rw-r--r--src/test/java/cuchaz/enigma/TestTranslator.java2
-rw-r--r--src/test/java/cuchaz/enigma/TokenChecker.java8
45 files changed, 1164 insertions, 864 deletions
diff --git a/build.gradle b/build.gradle
index 7711454f..63c221da 100644
--- a/build.gradle
+++ b/build.gradle
@@ -19,7 +19,7 @@ apply plugin: 'com.github.johnrengelman.shadow'
19apply plugin: 'maven' 19apply plugin: 'maven'
20 20
21group = 'cuchaz' 21group = 'cuchaz'
22version = '0.13.1' 22version = '0.13.2'
23 23
24def generatedSourcesDir = "$buildDir/generated-src" 24def generatedSourcesDir = "$buildDir/generated-src"
25 25
@@ -197,7 +197,30 @@ artifacts {
197// And finally, make the build generate / install the jars. 197// And finally, make the build generate / install the jars.
198build.dependsOn install 198build.dependsOn install
199 199
200apply from: 'https://github.com/FabricMC/fabric-docs/raw/master/gradle/maven.gradle' 200//apply from: 'https://github.com/FabricMC/fabric-docs/raw/master/gradle/maven.gradle'
201
202configurations {
203 deployerJars
204}
205
206dependencies {
207 deployerJars "org.apache.maven.wagon:wagon-ssh:2.10"
208}
209
210uploadArchives {
211 repositories {
212 mavenDeployer {
213 repository(url: 'file://localhost/Users/gegy1/.m2/repository')
214 pom {
215 artifactId = project.archivesBaseName
216 project {
217 name = project.name
218 packaging = 'jar'
219 }
220 }
221 }
222 }
223}
201 224
202uploadArchives.repositories.mavenDeployer.pom.withXml { 225uploadArchives.repositories.mavenDeployer.pom.withXml {
203 asNode().dependencies.'*'.findAll() { it.artifactId.text() == 'darcula' } 226 asNode().dependencies.'*'.findAll() { it.artifactId.text() == 'darcula' }
diff --git a/src/main/java/cuchaz/enigma/Deobfuscator.java b/src/main/java/cuchaz/enigma/Deobfuscator.java
deleted file mode 100644
index 32f7aa7c..00000000
--- 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}
diff --git a/src/main/java/cuchaz/enigma/Enigma.java b/src/main/java/cuchaz/enigma/Enigma.java
new file mode 100644
index 00000000..9f88f774
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/Enigma.java
@@ -0,0 +1,114 @@
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.Preconditions;
15import com.google.common.collect.ImmutableMap;
16import cuchaz.enigma.analysis.ClassCache;
17import cuchaz.enigma.analysis.index.JarIndex;
18import cuchaz.enigma.api.EnigmaPlugin;
19import cuchaz.enigma.api.EnigmaPluginContext;
20import cuchaz.enigma.api.service.EnigmaService;
21import cuchaz.enigma.api.service.EnigmaServiceFactory;
22import cuchaz.enigma.api.service.EnigmaServiceType;
23
24import java.io.IOException;
25import java.nio.file.Path;
26import java.util.ServiceLoader;
27
28public class Enigma {
29 private final EnigmaProfile profile;
30 private final EnigmaServices services;
31
32 private Enigma(EnigmaProfile profile, EnigmaServices services) {
33 this.profile = profile;
34 this.services = services;
35 }
36
37 public static Enigma create() {
38 return new Builder().build();
39 }
40
41 public static Builder builder() {
42 return new Builder();
43 }
44
45 public EnigmaProject openJar(Path path, ProgressListener progress) throws IOException {
46 ClassCache classCache = ClassCache.of(path);
47 JarIndex jarIndex = classCache.index(progress);
48
49 return new EnigmaProject(this, classCache, jarIndex);
50 }
51
52 public EnigmaProfile getProfile() {
53 return profile;
54 }
55
56 public EnigmaServices getServices() {
57 return services;
58 }
59
60 public static class Builder {
61 private EnigmaProfile profile = EnigmaProfile.EMPTY;
62 private Iterable<EnigmaPlugin> plugins = ServiceLoader.load(EnigmaPlugin.class);
63
64 private Builder() {
65 }
66
67 public Builder setProfile(EnigmaProfile profile) {
68 Preconditions.checkNotNull(profile, "profile cannot be null");
69 this.profile = profile;
70 return this;
71 }
72
73 public Builder setPlugins(Iterable<EnigmaPlugin> plugins) {
74 Preconditions.checkNotNull(plugins, "plugins cannot be null");
75 this.plugins = plugins;
76 return this;
77 }
78
79 public Enigma build() {
80 PluginContext pluginContext = new PluginContext(profile);
81 for (EnigmaPlugin plugin : plugins) {
82 plugin.init(pluginContext);
83 }
84
85 EnigmaServices services = pluginContext.buildServices();
86 return new Enigma(profile, services);
87 }
88 }
89
90 private static class PluginContext implements EnigmaPluginContext {
91 private final EnigmaProfile profile;
92
93 private final ImmutableMap.Builder<EnigmaServiceType<?>, EnigmaService> services = ImmutableMap.builder();
94
95 PluginContext(EnigmaProfile profile) {
96 this.profile = profile;
97 }
98
99 @Override
100 public <T extends EnigmaService> void registerService(String id, EnigmaServiceType<T> serviceType, EnigmaServiceFactory<T> factory) {
101 EnigmaProfile.Service serviceProfile = profile.getServiceProfile(serviceType);
102
103 // if this service type is not configured, or it is configured to use a different service id, skip
104 if (serviceProfile == null || !serviceProfile.matches(id)) return;
105
106 T service = factory.create(serviceProfile::getArgument);
107 services.put(serviceType, service);
108 }
109
110 EnigmaServices buildServices() {
111 return new EnigmaServices(services.build());
112 }
113 }
114}
diff --git a/src/main/java/cuchaz/enigma/EnigmaProfile.java b/src/main/java/cuchaz/enigma/EnigmaProfile.java
new file mode 100644
index 00000000..9dc5ff22
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/EnigmaProfile.java
@@ -0,0 +1,51 @@
1package cuchaz.enigma;
2
3import com.google.common.collect.ImmutableMap;
4import com.google.gson.Gson;
5import com.google.gson.annotations.SerializedName;
6import cuchaz.enigma.api.service.EnigmaServiceType;
7
8import javax.annotation.Nullable;
9import java.io.Reader;
10import java.util.Map;
11import java.util.Optional;
12
13public final class EnigmaProfile {
14 public static final EnigmaProfile EMPTY = new EnigmaProfile(ImmutableMap.of());
15
16 private static final Gson GSON = new Gson();
17
18 @SerializedName("services")
19 private final Map<String, Service> serviceProfiles;
20
21 private EnigmaProfile(Map<String, Service> serviceProfiles) {
22 this.serviceProfiles = serviceProfiles;
23 }
24
25 public static EnigmaProfile parse(Reader reader) {
26 return GSON.fromJson(reader, EnigmaProfile.class);
27 }
28
29 @Nullable
30 public Service getServiceProfile(EnigmaServiceType<?> serviceType) {
31 return serviceProfiles.get(serviceType.key);
32 }
33
34 public static class Service {
35 private final String id;
36 private final Map<String, String> args;
37
38 Service(String id, Map<String, String> args) {
39 this.id = id;
40 this.args = args;
41 }
42
43 public boolean matches(String id) {
44 return this.id.equals(id);
45 }
46
47 public Optional<String> getArgument(String key) {
48 return Optional.ofNullable(args.get(key));
49 }
50 }
51}
diff --git a/src/main/java/cuchaz/enigma/EnigmaProject.java b/src/main/java/cuchaz/enigma/EnigmaProject.java
new file mode 100644
index 00000000..82fc0bdf
--- /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
40// TODO: Naming?
41public class EnigmaProject {
42 private final Enigma enigma;
43
44 private final ClassCache classCache;
45 private final JarIndex jarIndex;
46
47 private EntryRemapper mapper;
48
49 public EnigmaProject(Enigma enigma, ClassCache classCache, JarIndex jarIndex) {
50 this.enigma = enigma;
51 this.classCache = classCache;
52 this.jarIndex = jarIndex;
53
54 this.mapper = EntryRemapper.empty(jarIndex);
55 }
56
57 public void setMappings(EntryTree<EntryMapping> mappings) {
58 if (mappings != null) {
59 mapper = EntryRemapper.mapped(jarIndex, mappings);
60 } else {
61 mapper = EntryRemapper.empty(jarIndex);
62 }
63 }
64
65 public Enigma getEnigma() {
66 return enigma;
67 }
68
69 public ClassCache getClassCache() {
70 return classCache;
71 }
72
73 public JarIndex getJarIndex() {
74 return jarIndex;
75 }
76
77 public EntryRemapper getMapper() {
78 return mapper;
79 }
80
81 public void dropMappings(ProgressListener progress) {
82 DeltaTrackingTree<EntryMapping> mappings = mapper.getObfToDeobf();
83
84 Collection<Entry<?>> dropped = dropMappings(mappings, progress);
85 for (Entry<?> entry : dropped) {
86 mappings.trackChange(entry);
87 }
88 }
89
90 private Collection<Entry<?>> dropMappings(EntryTree<EntryMapping> mappings, ProgressListener progress) {
91 // drop mappings that don't match the jar
92 MappingsChecker checker = new MappingsChecker(jarIndex, mappings);
93 MappingsChecker.Dropped dropped = checker.dropBrokenMappings(progress);
94
95 Map<Entry<?>, String> droppedMappings = dropped.getDroppedMappings();
96 for (Map.Entry<Entry<?>, String> mapping : droppedMappings.entrySet()) {
97 System.out.println("WARNING: Couldn't find " + mapping.getKey() + " (" + mapping.getValue() + ") in jar. Mapping was dropped.");
98 }
99
100 return droppedMappings.keySet();
101 }
102
103 public boolean isRenamable(Entry<?> obfEntry) {
104 if (obfEntry instanceof MethodEntry) {
105 // HACKHACK: Object methods are not obfuscated identifiers
106 MethodEntry obfMethodEntry = (MethodEntry) obfEntry;
107 String name = obfMethodEntry.getName();
108 String sig = obfMethodEntry.getDesc().toString();
109 if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) {
110 return false;
111 } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) {
112 return false;
113 } else if (name.equals("finalize") && sig.equals("()V")) {
114 return false;
115 } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) {
116 return false;
117 } else if (name.equals("hashCode") && sig.equals("()I")) {
118 return false;
119 } else if (name.equals("notify") && sig.equals("()V")) {
120 return false;
121 } else if (name.equals("notifyAll") && sig.equals("()V")) {
122 return false;
123 } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) {
124 return false;
125 } else if (name.equals("wait") && sig.equals("()V")) {
126 return false;
127 } else if (name.equals("wait") && sig.equals("(J)V")) {
128 return false;
129 } else if (name.equals("wait") && sig.equals("(JI)V")) {
130 return false;
131 }
132 } else if (obfEntry instanceof LocalVariableEntry && !((LocalVariableEntry) obfEntry).isArgument()) {
133 return false;
134 }
135
136 return this.jarIndex.getEntryIndex().hasEntry(obfEntry);
137 }
138
139 public boolean isRenamable(EntryReference<Entry<?>, Entry<?>> obfReference) {
140 return obfReference.isNamed() && isRenamable(obfReference.getNameableEntry());
141 }
142
143 public JarExport exportRemappedJar(ProgressListener progress) {
144 Collection<ClassEntry> classEntries = jarIndex.getEntryIndex().getClasses();
145 Translator deobfuscator = mapper.getDeobfuscator();
146
147 AtomicInteger count = new AtomicInteger();
148 progress.init(classEntries.size(), "Deobfuscating classes...");
149
150 Map<String, ClassNode> compiled = classEntries.parallelStream()
151 .map(entry -> {
152 ClassEntry translatedEntry = deobfuscator.translate(entry);
153 progress.step(count.getAndIncrement(), translatedEntry.toString());
154
155 ClassNode node = classCache.getClassNode(entry.getFullName());
156 if (node != null) {
157 ClassNode translatedNode = new ClassNode();
158 node.accept(new TranslationClassVisitor(deobfuscator, Opcodes.ASM5, translatedNode));
159 return translatedNode;
160 }
161
162 return null;
163 })
164 .filter(Objects::nonNull)
165 .collect(Collectors.toMap(n -> n.name, Functions.identity()));
166
167 return new JarExport(jarIndex, compiled);
168 }
169
170 public static final class JarExport {
171 private final JarIndex jarIndex;
172 private final Map<String, ClassNode> compiled;
173
174 JarExport(JarIndex jarIndex, Map<String, ClassNode> compiled) {
175 this.jarIndex = jarIndex;
176 this.compiled = compiled;
177 }
178
179 public void write(Path path, ProgressListener progress) throws IOException {
180 progress.init(this.compiled.size(), "Writing jar...");
181
182 try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(path))) {
183 AtomicInteger count = new AtomicInteger();
184
185 for (ClassNode node : this.compiled.values()) {
186 progress.step(count.getAndIncrement(), node.name);
187
188 String entryName = node.name.replace('.', '/') + ".class";
189
190 ClassWriter writer = new ClassWriter(0);
191 node.accept(writer);
192
193 out.putNextEntry(new JarEntry(entryName));
194 out.write(writer.toByteArray());
195 out.closeEntry();
196 }
197 }
198 }
199
200 public SourceExport decompile(ProgressListener progress) {
201 Collection<ClassNode> classes = this.compiled.values().stream()
202 .filter(classNode -> classNode.name.indexOf('$') == -1)
203 .collect(Collectors.toList());
204
205 progress.init(classes.size(), "Decompiling classes...");
206
207 //create a common instance outside the loop as mappings shouldn't be changing while this is happening
208 CompiledSourceTypeLoader typeLoader = new CompiledSourceTypeLoader(this.compiled::get);
209 typeLoader.addVisitor(visitor -> new SourceFixVisitor(Opcodes.ASM5, visitor, jarIndex));
210
211 //synchronized to make sure the parallelStream doesn't CME with the cache
212 ITypeLoader synchronizedTypeLoader = new SynchronizedTypeLoader(typeLoader);
213
214 MetadataSystem metadataSystem = new NoRetryMetadataSystem(synchronizedTypeLoader);
215
216 //ensures methods are loaded on classload and prevents race conditions
217 metadataSystem.setEagerMethodLoadingEnabled(true);
218
219 DecompilerSettings settings = SourceProvider.createSettings();
220 SourceProvider sourceProvider = new SourceProvider(settings, synchronizedTypeLoader, metadataSystem);
221
222 AtomicInteger count = new AtomicInteger();
223
224 Collection<ClassSource> decompiled = classes.parallelStream()
225 .map(translatedNode -> {
226 progress.step(count.getAndIncrement(), translatedNode.name);
227
228 String source = decompileClass(translatedNode, sourceProvider);
229 return new ClassSource(translatedNode.name, source);
230 })
231 .collect(Collectors.toList());
232
233 return new SourceExport(decompiled);
234 }
235
236 private String decompileClass(ClassNode translatedNode, SourceProvider sourceProvider) {
237 CompilationUnit sourceTree = sourceProvider.getSources(translatedNode.name);
238
239 StringWriter writer = new StringWriter();
240 sourceProvider.writeSource(writer, sourceTree);
241
242 return writer.toString();
243 }
244 }
245
246 public static final class SourceExport {
247 private final Collection<ClassSource> decompiled;
248
249 SourceExport(Collection<ClassSource> decompiled) {
250 this.decompiled = decompiled;
251 }
252
253 public void write(Path path, ProgressListener progress) throws IOException {
254 progress.init(decompiled.size(), "Writing sources...");
255
256 int count = 0;
257 for (ClassSource source : decompiled) {
258 progress.step(count++, source.name);
259
260 Path sourcePath = source.resolvePath(path);
261 source.writeTo(sourcePath);
262 }
263 }
264 }
265
266 private static class ClassSource {
267 private final String name;
268 private final String source;
269
270 ClassSource(String name, String source) {
271 this.name = name;
272 this.source = source;
273 }
274
275 void writeTo(Path path) throws IOException {
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}
diff --git a/src/main/java/cuchaz/enigma/EnigmaServices.java b/src/main/java/cuchaz/enigma/EnigmaServices.java
new file mode 100644
index 00000000..86507bca
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/EnigmaServices.java
@@ -0,0 +1,21 @@
1package cuchaz.enigma;
2
3import com.google.common.collect.ImmutableMap;
4import cuchaz.enigma.api.service.EnigmaService;
5import cuchaz.enigma.api.service.EnigmaServiceType;
6
7import java.util.Optional;
8
9public final class EnigmaServices {
10 private final ImmutableMap<EnigmaServiceType<?>, EnigmaService> services;
11
12 EnigmaServices(ImmutableMap<EnigmaServiceType<?>, EnigmaService> services) {
13 this.services = services;
14 }
15
16 @SuppressWarnings("unchecked")
17 public <T extends EnigmaService> Optional<T> get(EnigmaServiceType<T> type) {
18 EnigmaService service = services.get(type);
19 return Optional.ofNullable((T) service);
20 }
21}
diff --git a/src/main/java/cuchaz/enigma/Main.java b/src/main/java/cuchaz/enigma/Main.java
index ccfc51f0..76a3fff0 100644
--- a/src/main/java/cuchaz/enigma/Main.java
+++ b/src/main/java/cuchaz/enigma/Main.java
@@ -17,7 +17,6 @@ import cuchaz.enigma.translation.mapping.serde.MappingFormat;
17import java.io.File; 17import java.io.File;
18import java.nio.file.Files; 18import java.nio.file.Files;
19import java.nio.file.Path; 19import java.nio.file.Path;
20import java.util.jar.JarFile;
21 20
22public class Main { 21public class Main {
23 22
@@ -26,7 +25,7 @@ public class Main {
26 25
27 // parse command-line args 26 // parse command-line args
28 if (args.length >= 1) { 27 if (args.length >= 1) {
29 gui.getController().openJar(new JarFile(getFile(args[0]))); 28 gui.getController().openJar(getFile(args[0]).toPath());
30 } 29 }
31 if (args.length >= 2) { 30 if (args.length >= 2) {
32 Path mappingsFile = getFile(args[1]).toPath(); 31 Path mappingsFile = getFile(args[1]).toPath();
diff --git a/src/main/java/cuchaz/enigma/NoRetryMetadataSystem.java b/src/main/java/cuchaz/enigma/NoRetryMetadataSystem.java
new file mode 100644
index 00000000..269d31e1
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/NoRetryMetadataSystem.java
@@ -0,0 +1,38 @@
1package cuchaz.enigma;
2
3import com.strobel.assembler.metadata.ITypeLoader;
4import com.strobel.assembler.metadata.MetadataSystem;
5import com.strobel.assembler.metadata.TypeDefinition;
6import com.strobel.assembler.metadata.TypeReference;
7
8import java.util.Collections;
9import java.util.Set;
10import java.util.concurrent.ConcurrentHashMap;
11
12public final class NoRetryMetadataSystem extends MetadataSystem {
13 private final Set<String> failedTypes = Collections.newSetFromMap(new ConcurrentHashMap<>());
14
15 public NoRetryMetadataSystem(final ITypeLoader typeLoader) {
16 super(typeLoader);
17 }
18
19 @Override
20 protected synchronized TypeDefinition resolveType(final String descriptor, final boolean mightBePrimitive) {
21 if (failedTypes.contains(descriptor)) {
22 return null;
23 }
24
25 final TypeDefinition result = super.resolveType(descriptor, mightBePrimitive);
26
27 if (result == null) {
28 failedTypes.add(descriptor);
29 }
30
31 return result;
32 }
33
34 @Override
35 public synchronized TypeDefinition resolve(final TypeReference type) {
36 return super.resolve(type);
37 }
38}
diff --git a/src/main/java/cuchaz/enigma/ProgressListener.java b/src/main/java/cuchaz/enigma/ProgressListener.java
index ffce297d..6da3b81a 100644
--- a/src/main/java/cuchaz/enigma/ProgressListener.java
+++ b/src/main/java/cuchaz/enigma/ProgressListener.java
@@ -1,15 +1,17 @@
1package cuchaz.enigma; 1package cuchaz.enigma;
2 2
3public interface ProgressListener { 3public interface ProgressListener {
4 ProgressListener VOID = new ProgressListener() { 4 static ProgressListener none() {
5 @Override 5 return new ProgressListener() {
6 public void init(int totalWork, String title) { 6 @Override
7 } 7 public void init(int totalWork, String title) {
8 }
8 9
9 @Override 10 @Override
10 public void step(int numDone, String message) { 11 public void step(int numDone, String message) {
11 } 12 }
12 }; 13 };
14 }
13 15
14 void init(int totalWork, String title); 16 void init(int totalWork, String title);
15 17
diff --git a/src/main/java/cuchaz/enigma/SourceProvider.java b/src/main/java/cuchaz/enigma/SourceProvider.java
index 48e5a590..662f1f92 100644
--- a/src/main/java/cuchaz/enigma/SourceProvider.java
+++ b/src/main/java/cuchaz/enigma/SourceProvider.java
@@ -33,7 +33,7 @@ public class SourceProvider {
33 } 33 }
34 34
35 public SourceProvider(DecompilerSettings settings, ITypeLoader typeLoader) { 35 public SourceProvider(DecompilerSettings settings, ITypeLoader typeLoader) {
36 this(settings, typeLoader, new Deobfuscator.NoRetryMetadataSystem(typeLoader)); 36 this(settings, typeLoader, new NoRetryMetadataSystem(typeLoader));
37 } 37 }
38 38
39 public static DecompilerSettings createSettings() { 39 public static DecompilerSettings createSettings() {
diff --git a/src/main/java/cuchaz/enigma/analysis/ClassCache.java b/src/main/java/cuchaz/enigma/analysis/ClassCache.java
new file mode 100644
index 00000000..0bd78b32
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/ClassCache.java
@@ -0,0 +1,127 @@
1package cuchaz.enigma.analysis;
2
3import com.google.common.cache.Cache;
4import com.google.common.cache.CacheBuilder;
5import com.google.common.collect.ImmutableSet;
6import cuchaz.enigma.CompiledSource;
7import cuchaz.enigma.ProgressListener;
8import cuchaz.enigma.analysis.index.JarIndex;
9import cuchaz.enigma.bytecode.translators.LocalVariableFixVisitor;
10import org.objectweb.asm.ClassReader;
11import org.objectweb.asm.ClassVisitor;
12import org.objectweb.asm.Opcodes;
13import org.objectweb.asm.tree.ClassNode;
14
15import javax.annotation.Nullable;
16import java.io.IOException;
17import java.nio.file.FileSystem;
18import java.nio.file.FileSystems;
19import java.nio.file.Files;
20import java.nio.file.Path;
21import java.util.concurrent.ExecutionException;
22import java.util.concurrent.TimeUnit;
23import java.util.function.Supplier;
24
25public final class ClassCache implements AutoCloseable, CompiledSource {
26 private final FileSystem fileSystem;
27 private final ImmutableSet<String> classNames;
28
29 private final Cache<String, ClassNode> nodeCache = CacheBuilder.newBuilder()
30 .maximumSize(128)
31 .expireAfterAccess(1, TimeUnit.MINUTES)
32 .build();
33
34 private ClassCache(FileSystem fileSystem, ImmutableSet<String> classNames) {
35 this.fileSystem = fileSystem;
36 this.classNames = classNames;
37 }
38
39 public static ClassCache of(Path jarPath) throws IOException {
40 FileSystem fileSystem = FileSystems.newFileSystem(jarPath, null);
41 ImmutableSet<String> classNames = collectClassNames(fileSystem);
42
43 return new ClassCache(fileSystem, classNames);
44 }
45
46 private static ImmutableSet<String> collectClassNames(FileSystem fileSystem) throws IOException {
47 ImmutableSet.Builder<String> classNames = ImmutableSet.builder();
48 for (Path root : fileSystem.getRootDirectories()) {
49 Files.walk(root).map(Path::toString)
50 .forEach(path -> {
51 if (path.endsWith(".class")) {
52 String name = path.substring(1, path.length() - ".class".length());
53 classNames.add(name);
54 }
55 });
56 }
57
58 return classNames.build();
59 }
60
61 @Nullable
62 @Override
63 public ClassNode getClassNode(String name) {
64 if (!classNames.contains(name)) {
65 return null;
66 }
67
68 try {
69 return nodeCache.get(name, () -> parseNode(name));
70 } catch (ExecutionException e) {
71 throw new RuntimeException(e);
72 }
73 }
74
75 private ClassNode parseNode(String name) throws IOException {
76 ClassReader reader = getReader(name);
77
78 ClassNode node = new ClassNode();
79
80 LocalVariableFixVisitor visitor = new LocalVariableFixVisitor(Opcodes.ASM5, node);
81 reader.accept(visitor, 0);
82
83 return node;
84 }
85
86 private ClassReader getReader(String name) throws IOException {
87 Path path = fileSystem.getPath(name + ".class");
88
89 byte[] bytes = Files.readAllBytes(path);
90 return new ClassReader(bytes);
91 }
92
93 public int getClassCount() {
94 return classNames.size();
95 }
96
97 public void visit(Supplier<ClassVisitor> visitorSupplier, int readFlags) {
98 for (String className : classNames) {
99 ClassVisitor visitor = visitorSupplier.get();
100
101 ClassNode cached = nodeCache.getIfPresent(className);
102 if (cached != null) {
103 cached.accept(visitor);
104 continue;
105 }
106
107 try {
108 ClassReader reader = getReader(className);
109 reader.accept(visitor, readFlags);
110 } catch (IOException e) {
111 System.out.println("Failed to visit class " + className);
112 e.printStackTrace();
113 }
114 }
115 }
116
117 @Override
118 public void close() throws IOException {
119 this.fileSystem.close();
120 }
121
122 public JarIndex index(ProgressListener progress) {
123 JarIndex index = JarIndex.empty();
124 index.indexJar(this, progress);
125 return index;
126 }
127}
diff --git a/src/main/java/cuchaz/enigma/analysis/ParsedJar.java b/src/main/java/cuchaz/enigma/analysis/ParsedJar.java
deleted file mode 100644
index ddcda3ed..00000000
--- a/src/main/java/cuchaz/enigma/analysis/ParsedJar.java
+++ /dev/null
@@ -1,129 +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.analysis;
13
14import com.google.common.io.ByteStreams;
15import cuchaz.enigma.CompiledSource;
16import cuchaz.enigma.bytecode.translators.LocalVariableFixVisitor;
17import cuchaz.enigma.translation.representation.entry.ClassEntry;
18import org.objectweb.asm.ClassReader;
19import org.objectweb.asm.ClassVisitor;
20import org.objectweb.asm.Opcodes;
21import org.objectweb.asm.tree.ClassNode;
22
23import javax.annotation.Nullable;
24import java.io.BufferedInputStream;
25import java.io.IOException;
26import java.io.InputStream;
27import java.util.*;
28import java.util.function.Consumer;
29import java.util.function.Function;
30import java.util.jar.JarEntry;
31import java.util.jar.JarFile;
32import java.util.jar.JarInputStream;
33
34public class ParsedJar implements CompiledSource {
35 private final Map<String, byte[]> classBytes;
36 private final Map<String, ClassNode> nodeCache = new HashMap<>();
37
38 public ParsedJar(JarFile jar) throws IOException {
39 Map<String, byte[]> uClassBytes = new LinkedHashMap<>();
40 try {
41 // get the jar entries that correspond to classes
42 Enumeration<JarEntry> entries = jar.entries();
43 while (entries.hasMoreElements()) {
44 JarEntry entry = entries.nextElement();
45 // is this a class file?
46 if (entry.getName().endsWith(".class")) {
47 try (InputStream input = new BufferedInputStream(jar.getInputStream(entry))) {
48 String path = entry.getName().substring(0, entry.getName().length() - ".class".length());
49 uClassBytes.put(path, ByteStreams.toByteArray(input));
50 }
51 }
52 }
53 } finally {
54 jar.close();
55 classBytes = Collections.unmodifiableMap(uClassBytes);
56 }
57 }
58
59 public ParsedJar(JarInputStream jar) throws IOException {
60 Map<String, byte[]> uClassBytes = new LinkedHashMap<>();
61 try {
62 // get the jar entries that correspond to classes
63 JarEntry entry;
64 while ((entry = jar.getNextJarEntry()) != null) {
65 // is this a class file?
66 if (entry.getName().endsWith(".class")) {
67 String path = entry.getName().substring(0, entry.getName().length() - ".class".length());
68 uClassBytes.put(path, ByteStreams.toByteArray(jar));
69 jar.closeEntry();
70 }
71 }
72 } finally {
73 jar.close();
74 classBytes = Collections.unmodifiableMap(uClassBytes);
75 }
76 }
77
78 public void visitReader(Function<String, ClassVisitor> visitorFunction, int options) {
79 for (String s : classBytes.keySet()) {
80 ClassNode nodeCached = nodeCache.get(s);
81 if (nodeCached != null) {
82 nodeCached.accept(visitorFunction.apply(s));
83 } else {
84 new ClassReader(classBytes.get(s)).accept(visitorFunction.apply(s), options);
85 }
86 }
87 }
88
89 public void visitNode(Consumer<ClassNode> consumer) {
90 for (String s : classBytes.keySet()) {
91 consumer.accept(getClassNode(s));
92 }
93 }
94
95 public int getClassCount() {
96 return classBytes.size();
97 }
98
99 @Nullable
100 @Override
101 public ClassNode getClassNode(String name) {
102 return nodeCache.computeIfAbsent(name, (n) -> {
103 byte[] bytes = classBytes.get(name);
104 if (bytes == null) {
105 return null;
106 }
107
108 ClassReader reader = new ClassReader(bytes);
109 ClassNode node = new ClassNode();
110
111 LocalVariableFixVisitor visitor = new LocalVariableFixVisitor(Opcodes.ASM5, node);
112 reader.accept(visitor, 0);
113
114 return node;
115 });
116 }
117
118 public List<ClassEntry> getClassEntries() {
119 List<ClassEntry> entries = new ArrayList<>(classBytes.size());
120 for (String s : classBytes.keySet()) {
121 entries.add(new ClassEntry(s));
122 }
123 return entries;
124 }
125
126 public Map<String, byte[]> getClassDataMap() {
127 return classBytes;
128 }
129}
diff --git a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java
index fd4e618b..300425b8 100644
--- a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java
+++ b/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java
@@ -13,7 +13,8 @@ package cuchaz.enigma.analysis.index;
13 13
14import com.google.common.collect.HashMultimap; 14import com.google.common.collect.HashMultimap;
15import com.google.common.collect.Multimap; 15import com.google.common.collect.Multimap;
16import cuchaz.enigma.analysis.ParsedJar; 16import cuchaz.enigma.ProgressListener;
17import cuchaz.enigma.analysis.ClassCache;
17import cuchaz.enigma.translation.mapping.EntryResolver; 18import cuchaz.enigma.translation.mapping.EntryResolver;
18import cuchaz.enigma.translation.mapping.IndexEntryResolver; 19import cuchaz.enigma.translation.mapping.IndexEntryResolver;
19import cuchaz.enigma.translation.representation.Lambda; 20import cuchaz.enigma.translation.representation.Lambda;
@@ -23,7 +24,6 @@ import org.objectweb.asm.Opcodes;
23 24
24import java.util.Arrays; 25import java.util.Arrays;
25import java.util.Collection; 26import java.util.Collection;
26import java.util.function.Consumer;
27 27
28public class JarIndex implements JarIndexer { 28public class JarIndex implements JarIndexer {
29 private final EntryIndex entryIndex; 29 private final EntryIndex entryIndex;
@@ -56,23 +56,25 @@ public class JarIndex implements JarIndexer {
56 return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); 56 return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex);
57 } 57 }
58 58
59 public void indexJar(ParsedJar jar, Consumer<String> progress) { 59 public void indexJar(ClassCache classCache, ProgressListener progress) {
60 progress.accept("Indexing entries (1/4)"); 60 progress.init(4, "Indexing jar");
61 jar.visitReader(name -> new IndexClassVisitor(this, Opcodes.ASM5), ClassReader.SKIP_CODE);
62 61
63 progress.accept("Indexing entry references (2/4)"); 62 progress.step(1, "Entries");
64 jar.visitReader(name -> new IndexReferenceVisitor(this, Opcodes.ASM5), ClassReader.SKIP_FRAMES); 63 classCache.visit(() -> new IndexClassVisitor(this, Opcodes.ASM5), ClassReader.SKIP_CODE);
65 64
66 progress.accept("Finding bridge methods (3/4)"); 65 progress.step(2, "Entry references");
66 classCache.visit(() -> new IndexReferenceVisitor(this, Opcodes.ASM5), ClassReader.SKIP_FRAMES);
67
68 progress.step(3, "Bridge methods");
67 bridgeMethodIndex.findBridgeMethods(); 69 bridgeMethodIndex.findBridgeMethods();
68 70
69 progress.accept("Processing index (4/4)"); 71 progress.step(4, "Processing");
70 processIndex(this); 72 processIndex(this);
71 } 73 }
72 74
73 @Override 75 @Override
74 public void processIndex(JarIndex index) { 76 public void processIndex(JarIndex index) {
75 indexers.forEach(indexer -> indexer.processIndex(index)); 77 indexers.parallelStream().forEach(indexer -> indexer.processIndex(index));
76 } 78 }
77 79
78 @Override 80 @Override
diff --git a/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java b/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java
new file mode 100644
index 00000000..bdd60150
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java
@@ -0,0 +1,5 @@
1package cuchaz.enigma.api;
2
3public interface EnigmaPlugin {
4 void init(EnigmaPluginContext ctx);
5}
diff --git a/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java b/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java
new file mode 100644
index 00000000..a59051ad
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java
@@ -0,0 +1,9 @@
1package cuchaz.enigma.api;
2
3import cuchaz.enigma.api.service.EnigmaService;
4import cuchaz.enigma.api.service.EnigmaServiceFactory;
5import cuchaz.enigma.api.service.EnigmaServiceType;
6
7public interface EnigmaPluginContext {
8 <T extends EnigmaService> void registerService(String id, EnigmaServiceType<T> serviceType, EnigmaServiceFactory<T> factory);
9}
diff --git a/src/main/java/cuchaz/enigma/api/JarProcessor.java b/src/main/java/cuchaz/enigma/api/JarProcessor.java
deleted file mode 100644
index 965b0c45..00000000
--- a/src/main/java/cuchaz/enigma/api/JarProcessor.java
+++ /dev/null
@@ -1,8 +0,0 @@
1package cuchaz.enigma.api;
2
3import cuchaz.enigma.analysis.ParsedJar;
4import cuchaz.enigma.analysis.index.JarIndex;
5
6public interface JarProcessor {
7 void accept(ParsedJar jar, JarIndex index);
8}
diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaService.java b/src/main/java/cuchaz/enigma/api/service/EnigmaService.java
new file mode 100644
index 00000000..526dda77
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/api/service/EnigmaService.java
@@ -0,0 +1,4 @@
1package cuchaz.enigma.api.service;
2
3public interface EnigmaService {
4}
diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java
new file mode 100644
index 00000000..9e433fb0
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java
@@ -0,0 +1,11 @@
1package cuchaz.enigma.api.service;
2
3import java.util.Optional;
4
5public interface EnigmaServiceContext<T extends EnigmaService> {
6 static <T extends EnigmaService> EnigmaServiceContext<T> empty() {
7 return key -> Optional.empty();
8 }
9
10 Optional<String> getArgument(String key);
11}
diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java
new file mode 100644
index 00000000..7c10ac26
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java
@@ -0,0 +1,5 @@
1package cuchaz.enigma.api.service;
2
3public interface EnigmaServiceFactory<T extends EnigmaService> {
4 T create(EnigmaServiceContext<T> ctx);
5}
diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java
new file mode 100644
index 00000000..358828f0
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java
@@ -0,0 +1,29 @@
1package cuchaz.enigma.api.service;
2
3public final class EnigmaServiceType<T extends EnigmaService> {
4 public final String key;
5
6 private EnigmaServiceType(String key) {
7 this.key = key;
8 }
9
10 public static <T extends EnigmaService> EnigmaServiceType<T> create(String key) {
11 return new EnigmaServiceType<>(key);
12 }
13
14 @Override
15 public int hashCode() {
16 return key.hashCode();
17 }
18
19 @Override
20 public boolean equals(Object obj) {
21 if (obj == this) return true;
22
23 if (obj instanceof EnigmaServiceType) {
24 return ((EnigmaServiceType) obj).key.equals(key);
25 }
26
27 return false;
28 }
29}
diff --git a/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java b/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java
new file mode 100644
index 00000000..0cda1998
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java
@@ -0,0 +1,10 @@
1package cuchaz.enigma.api.service;
2
3import cuchaz.enigma.analysis.ClassCache;
4import cuchaz.enigma.analysis.index.JarIndex;
5
6public interface JarIndexerService extends EnigmaService {
7 EnigmaServiceType<JarIndexerService> TYPE = EnigmaServiceType.create("jar_indexer");
8
9 void acceptJar(ClassCache classCache, JarIndex jarIndex);
10}
diff --git a/src/main/java/cuchaz/enigma/api/EntryNameProposer.java b/src/main/java/cuchaz/enigma/api/service/NameProposalService.java
index 3fc26d27..4c357db1 100644
--- a/src/main/java/cuchaz/enigma/api/EntryNameProposer.java
+++ b/src/main/java/cuchaz/enigma/api/service/NameProposalService.java
@@ -1,10 +1,12 @@
1package cuchaz.enigma.api; 1package cuchaz.enigma.api.service;
2 2
3import cuchaz.enigma.translation.mapping.EntryRemapper; 3import cuchaz.enigma.translation.mapping.EntryRemapper;
4import cuchaz.enigma.translation.representation.entry.Entry; 4import cuchaz.enigma.translation.representation.entry.Entry;
5 5
6import java.util.Optional; 6import java.util.Optional;
7 7
8public interface EntryNameProposer { 8public interface NameProposalService extends EnigmaService {
9 EnigmaServiceType<NameProposalService> TYPE = EnigmaServiceType.create("name_proposal");
10
9 Optional<String> proposeName(Entry<?> obfEntry, EntryRemapper remapper); 11 Optional<String> proposeName(Entry<?> obfEntry, EntryRemapper remapper);
10} 12}
diff --git a/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java b/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java
new file mode 100644
index 00000000..af0cf30b
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java
@@ -0,0 +1,9 @@
1package cuchaz.enigma.api.service;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5public interface ObfuscationTestService extends EnigmaService {
6 EnigmaServiceType<ObfuscationTestService> TYPE = EnigmaServiceType.create("obfuscation_test");
7
8 boolean testDeobfuscated(Entry<?> entry);
9}
diff --git a/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java b/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java
index 7ec7679c..08e73e6a 100644
--- a/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java
+++ b/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java
@@ -1,6 +1,7 @@
1package cuchaz.enigma.command; 1package cuchaz.enigma.command;
2 2
3import cuchaz.enigma.Deobfuscator; 3import cuchaz.enigma.Enigma;
4import cuchaz.enigma.EnigmaProject;
4import cuchaz.enigma.ProgressListener; 5import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.analysis.index.JarIndex; 6import cuchaz.enigma.analysis.index.JarIndex;
6import cuchaz.enigma.translation.mapping.EntryMapping; 7import cuchaz.enigma.translation.mapping.EntryMapping;
@@ -8,10 +9,8 @@ import cuchaz.enigma.translation.mapping.serde.MappingFormat;
8import cuchaz.enigma.translation.mapping.tree.EntryTree; 9import cuchaz.enigma.translation.mapping.tree.EntryTree;
9import cuchaz.enigma.translation.representation.entry.ClassEntry; 10import cuchaz.enigma.translation.representation.entry.ClassEntry;
10 11
11import java.io.File;
12import java.nio.file.Path; 12import java.nio.file.Path;
13import java.util.Set; 13import java.util.Set;
14import java.util.jar.JarFile;
15import java.util.stream.Collectors; 14import java.util.stream.Collectors;
16 15
17public class CheckMappingsCommand extends Command { 16public class CheckMappingsCommand extends Command {
@@ -32,26 +31,39 @@ public class CheckMappingsCommand extends Command {
32 31
33 @Override 32 @Override
34 public void run(String... args) throws Exception { 33 public void run(String... args) throws Exception {
35 File fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)); 34 Path fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)).toPath();
36 Path fileMappings = getReadablePath(getArg(args, 1, "mappings file", true)); 35 Path fileMappings = getReadablePath(getArg(args, 1, "mappings file", true));
37 36
37 Enigma enigma = Enigma.create();
38
38 System.out.println("Reading JAR..."); 39 System.out.println("Reading JAR...");
39 Deobfuscator deobfuscator = new Deobfuscator(new JarFile(fileJarIn)); 40
41 EnigmaProject project = enigma.openJar(fileJarIn, ProgressListener.none());
42
40 System.out.println("Reading mappings..."); 43 System.out.println("Reading mappings...");
41 44
42 MappingFormat format = chooseEnigmaFormat(fileMappings); 45 MappingFormat format = chooseEnigmaFormat(fileMappings);
43 EntryTree<EntryMapping> mappings = format.read(fileMappings, ProgressListener.VOID); 46 EntryTree<EntryMapping> mappings = format.read(fileMappings, ProgressListener.none());
44 deobfuscator.setMappings(mappings); 47 project.setMappings(mappings);
45 48
46 JarIndex idx = deobfuscator.getJarIndex(); 49 JarIndex idx = project.getJarIndex();
47 50
48 boolean error = false; 51 boolean error = false;
49 52
50 for (Set<ClassEntry> partition : idx.getPackageVisibilityIndex().getPartitions()) { 53 for (Set<ClassEntry> partition : idx.getPackageVisibilityIndex().getPartitions()) {
51 long packages = partition.stream().map(deobfuscator.getMapper()::deobfuscate).map(ClassEntry::getPackageName).distinct().count(); 54 long packages = partition.stream()
55 .map(project.getMapper()::deobfuscate)
56 .map(ClassEntry::getPackageName)
57 .distinct()
58 .count();
52 if (packages > 1) { 59 if (packages > 1) {
53 error = true; 60 error = true;
54 System.err.println("ERROR: Must be in one package:\n" + partition.stream().map(deobfuscator.getMapper()::deobfuscate).map(ClassEntry::toString).sorted().collect(Collectors.joining("\n"))); 61 System.err.println("ERROR: Must be in one package:\n" + partition.stream()
62 .map(project.getMapper()::deobfuscate)
63 .map(ClassEntry::toString)
64 .sorted()
65 .collect(Collectors.joining("\n"))
66 );
55 } 67 }
56 } 68 }
57 69
diff --git a/src/main/java/cuchaz/enigma/command/Command.java b/src/main/java/cuchaz/enigma/command/Command.java
index b107fb61..41d7bfae 100644
--- a/src/main/java/cuchaz/enigma/command/Command.java
+++ b/src/main/java/cuchaz/enigma/command/Command.java
@@ -1,6 +1,7 @@
1package cuchaz.enigma.command; 1package cuchaz.enigma.command;
2 2
3import cuchaz.enigma.Deobfuscator; 3import cuchaz.enigma.Enigma;
4import cuchaz.enigma.EnigmaProject;
4import cuchaz.enigma.ProgressListener; 5import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.translation.mapping.EntryMapping; 6import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.serde.MappingFormat; 7import cuchaz.enigma.translation.mapping.serde.MappingFormat;
@@ -10,7 +11,6 @@ import java.io.File;
10import java.nio.file.Files; 11import java.nio.file.Files;
11import java.nio.file.Path; 12import java.nio.file.Path;
12import java.nio.file.Paths; 13import java.nio.file.Paths;
13import java.util.jar.JarFile;
14 14
15public abstract class Command { 15public abstract class Command {
16 public final String name; 16 public final String name;
@@ -25,15 +25,21 @@ public abstract class Command {
25 25
26 public abstract void run(String... args) throws Exception; 26 public abstract void run(String... args) throws Exception;
27 27
28 protected static Deobfuscator getDeobfuscator(Path fileMappings, JarFile jar) throws Exception { 28 protected static EnigmaProject openProject(Path fileJarIn, Path fileMappings) throws Exception {
29 ProgressListener progress = new ConsoleProgressListener();
30
31 Enigma enigma = Enigma.create();
32
29 System.out.println("Reading jar..."); 33 System.out.println("Reading jar...");
30 Deobfuscator deobfuscator = new Deobfuscator(jar); 34 EnigmaProject project = enigma.openJar(fileJarIn, progress);
35
31 if (fileMappings != null) { 36 if (fileMappings != null) {
32 System.out.println("Reading mappings..."); 37 System.out.println("Reading mappings...");
33 EntryTree<EntryMapping> mappings = chooseEnigmaFormat(fileMappings).read(fileMappings, new ConsoleProgressListener()); 38 EntryTree<EntryMapping> mappings = chooseEnigmaFormat(fileMappings).read(fileMappings, progress);
34 deobfuscator.setMappings(mappings); 39 project.setMappings(mappings);
35 } 40 }
36 return deobfuscator; 41
42 return project;
37 } 43 }
38 44
39 protected static MappingFormat chooseEnigmaFormat(Path path) { 45 protected static MappingFormat chooseEnigmaFormat(Path path) {
diff --git a/src/main/java/cuchaz/enigma/command/DecompileCommand.java b/src/main/java/cuchaz/enigma/command/DecompileCommand.java
index a58d9085..bc23d01d 100644
--- a/src/main/java/cuchaz/enigma/command/DecompileCommand.java
+++ b/src/main/java/cuchaz/enigma/command/DecompileCommand.java
@@ -1,10 +1,9 @@
1package cuchaz.enigma.command; 1package cuchaz.enigma.command;
2 2
3import cuchaz.enigma.Deobfuscator; 3import cuchaz.enigma.EnigmaProject;
4import cuchaz.enigma.ProgressListener;
4 5
5import java.io.File;
6import java.nio.file.Path; 6import java.nio.file.Path;
7import java.util.jar.JarFile;
8 7
9public class DecompileCommand extends Command { 8public class DecompileCommand extends Command {
10 9
@@ -24,10 +23,17 @@ public class DecompileCommand extends Command {
24 23
25 @Override 24 @Override
26 public void run(String... args) throws Exception { 25 public void run(String... args) throws Exception {
27 File fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)); 26 Path fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)).toPath();
28 File fileJarOut = getWritableFolder(getArg(args, 1, "out folder", true)); 27 Path fileJarOut = getWritableFolder(getArg(args, 1, "out folder", true)).toPath();
29 Path fileMappings = getReadablePath(getArg(args, 2, "mappings file", false)); 28 Path fileMappings = getReadablePath(getArg(args, 2, "mappings file", false));
30 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); 29
31 deobfuscator.writeSources(fileJarOut.toPath(), new Command.ConsoleProgressListener()); 30 EnigmaProject project = openProject(fileJarIn, fileMappings);
31
32 ProgressListener progress = new ConsoleProgressListener();
33
34 EnigmaProject.JarExport jar = project.exportRemappedJar(progress);
35 EnigmaProject.SourceExport source = jar.decompile(progress);
36
37 source.write(fileJarOut, progress);
32 } 38 }
33} 39}
diff --git a/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java b/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java
index 5d499385..c24e6613 100644
--- a/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java
+++ b/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java
@@ -1,10 +1,9 @@
1package cuchaz.enigma.command; 1package cuchaz.enigma.command;
2 2
3import cuchaz.enigma.Deobfuscator; 3import cuchaz.enigma.EnigmaProject;
4import cuchaz.enigma.ProgressListener;
4 5
5import java.io.File;
6import java.nio.file.Path; 6import java.nio.file.Path;
7import java.util.jar.JarFile;
8 7
9public class DeobfuscateCommand extends Command { 8public class DeobfuscateCommand extends Command {
10 9
@@ -24,10 +23,17 @@ public class DeobfuscateCommand extends Command {
24 23
25 @Override 24 @Override
26 public void run(String... args) throws Exception { 25 public void run(String... args) throws Exception {
27 File fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)); 26 Path fileJarIn = getReadablePath(getArg(args, 0, "in jar", true));
28 File fileJarOut = getWritableFile(getArg(args, 1, "out jar", true)); 27 Path fileJarOut = getWritableFile(getArg(args, 1, "out jar", true)).toPath();
29 Path fileMappings = getReadablePath(getArg(args, 2, "mappings file", false)); 28 Path fileMappings = getReadablePath(getArg(args, 2, "mappings file", false));
30 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); 29
31 deobfuscator.writeTransformedJar(fileJarOut, new Command.ConsoleProgressListener()); 30 EnigmaProject project = openProject(fileJarIn, fileMappings);
31
32 ProgressListener progress = new ConsoleProgressListener();
33
34 EnigmaProject.JarExport jar = project.exportRemappedJar(progress);
35 EnigmaProject.SourceExport source = jar.decompile(progress);
36
37 source.write(fileJarOut, progress);
32 } 38 }
33} 39}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
index 39d0333b..5051032d 100644
--- a/src/main/java/cuchaz/enigma/gui/ClassSelector.java
+++ b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
@@ -155,7 +155,7 @@ public class ClassSelector extends JTree {
155 return; 155 return;
156 } 156 }
157 157
158 Translator translator = controller.getDeobfuscator().getMapper().getDeobfuscator(); 158 Translator translator = controller.project.getMapper().getDeobfuscator();
159 159
160 // build the package names 160 // build the package names
161 Map<String, ClassSelectorPackageNode> packages = Maps.newHashMap(); 161 Map<String, ClassSelectorPackageNode> packages = Maps.newHashMap();
@@ -478,7 +478,7 @@ public class ClassSelector extends JTree {
478 } 478 }
479 479
480 public void insertNode(ClassEntry obfEntry) { 480 public void insertNode(ClassEntry obfEntry) {
481 ClassEntry deobfEntry = controller.getDeobfuscator().deobfuscate(obfEntry); 481 ClassEntry deobfEntry = controller.project.getMapper().deobfuscate(obfEntry);
482 ClassSelectorPackageNode packageNode = getOrCreatePackage(deobfEntry); 482 ClassSelectorPackageNode packageNode = getOrCreatePackage(deobfEntry);
483 483
484 DefaultTreeModel model = (DefaultTreeModel) getModel(); 484 DefaultTreeModel model = (DefaultTreeModel) getModel();
diff --git a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java b/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java
index d7c981ac..44f70f8c 100644
--- a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java
+++ b/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java
@@ -1,12 +1,17 @@
1package cuchaz.enigma.gui; 1package cuchaz.enigma.gui;
2 2
3import cuchaz.enigma.Deobfuscator; 3import cuchaz.enigma.EnigmaProject;
4import cuchaz.enigma.EnigmaServices;
4import cuchaz.enigma.analysis.EntryReference; 5import cuchaz.enigma.analysis.EntryReference;
5import cuchaz.enigma.analysis.SourceIndex; 6import cuchaz.enigma.analysis.SourceIndex;
6import cuchaz.enigma.analysis.Token; 7import cuchaz.enigma.analysis.Token;
8import cuchaz.enigma.api.service.NameProposalService;
7import cuchaz.enigma.gui.highlight.TokenHighlightType; 9import cuchaz.enigma.gui.highlight.TokenHighlightType;
8import cuchaz.enigma.translation.LocalNameGenerator; 10import cuchaz.enigma.translation.LocalNameGenerator;
9import cuchaz.enigma.translation.Translator; 11import cuchaz.enigma.translation.Translator;
12import cuchaz.enigma.translation.mapping.EntryRemapper;
13import cuchaz.enigma.translation.mapping.EntryResolver;
14import cuchaz.enigma.translation.mapping.ResolutionStrategy;
10import cuchaz.enigma.translation.representation.TypeDescriptor; 15import cuchaz.enigma.translation.representation.TypeDescriptor;
11import cuchaz.enigma.translation.representation.entry.ClassEntry; 16import cuchaz.enigma.translation.representation.entry.ClassEntry;
12import cuchaz.enigma.translation.representation.entry.Entry; 17import cuchaz.enigma.translation.representation.entry.Entry;
@@ -34,27 +39,27 @@ public class DecompiledClassSource {
34 return new DecompiledClassSource(classEntry, new SourceIndex(text)); 39 return new DecompiledClassSource(classEntry, new SourceIndex(text));
35 } 40 }
36 41
37 public void remapSource(Deobfuscator deobfuscator, Translator translator) { 42 public void remapSource(EnigmaProject project, Translator translator) {
38 highlightedTokens.clear(); 43 highlightedTokens.clear();
39 44
40 SourceRemapper remapper = new SourceRemapper(obfuscatedIndex.getSource(), obfuscatedIndex.referenceTokens()); 45 SourceRemapper remapper = new SourceRemapper(obfuscatedIndex.getSource(), obfuscatedIndex.referenceTokens());
41 46
42 SourceRemapper.Result remapResult = remapper.remap((token, movedToken) -> remapToken(deobfuscator, token, movedToken, translator)); 47 SourceRemapper.Result remapResult = remapper.remap((token, movedToken) -> remapToken(project, token, movedToken, translator));
43 remappedIndex = obfuscatedIndex.remapTo(remapResult); 48 remappedIndex = obfuscatedIndex.remapTo(remapResult);
44 } 49 }
45 50
46 private String remapToken(Deobfuscator deobfuscator, Token token, Token movedToken, Translator translator) { 51 private String remapToken(EnigmaProject project, Token token, Token movedToken, Translator translator) {
47 EntryReference<Entry<?>, Entry<?>> reference = obfuscatedIndex.getReference(token); 52 EntryReference<Entry<?>, Entry<?>> reference = obfuscatedIndex.getReference(token);
48 53
49 Entry<?> entry = reference.getNameableEntry(); 54 Entry<?> entry = reference.getNameableEntry();
50 Entry<?> translatedEntry = translator.translate(entry); 55 Entry<?> translatedEntry = translator.translate(entry);
51 56
52 if (deobfuscator.isRenamable(reference)) { 57 if (project.isRenamable(reference)) {
53 if (isDeobfuscated(entry, translatedEntry)) { 58 if (isDeobfuscated(entry, translatedEntry)) {
54 highlightToken(movedToken, TokenHighlightType.DEOBFUSCATED); 59 highlightToken(movedToken, TokenHighlightType.DEOBFUSCATED);
55 return translatedEntry.getSourceRemapName(); 60 return translatedEntry.getSourceRemapName();
56 } else { 61 } else {
57 Optional<String> proposedName = proposeName(deobfuscator, entry); 62 Optional<String> proposedName = proposeName(project, entry);
58 if (proposedName.isPresent()) { 63 if (proposedName.isPresent()) {
59 highlightToken(movedToken, TokenHighlightType.PROPOSED); 64 highlightToken(movedToken, TokenHighlightType.PROPOSED);
60 return proposedName.get(); 65 return proposedName.get();
@@ -72,13 +77,22 @@ public class DecompiledClassSource {
72 return null; 77 return null;
73 } 78 }
74 79
75 private Optional<String> proposeName(Deobfuscator deobfuscator, Entry<?> entry) { 80 private Optional<String> proposeName(EnigmaProject project, Entry<?> entry) {
76 Stream<String> proposals = deobfuscator.getNameProposers() 81 EnigmaServices services = project.getEnigma().getServices();
77 .map(plugin -> plugin.proposeName(entry, deobfuscator.getMapper()))
78 .filter(Optional::isPresent)
79 .map(Optional::get);
80 82
81 return proposals.findFirst(); 83 return services.get(NameProposalService.TYPE).flatMap(nameProposalService -> {
84 EntryResolver resolver = project.getMapper().getObfResolver();
85
86 Collection<Entry<?>> resolved = resolver.resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT);
87 EntryRemapper mapper = project.getMapper();
88
89 Stream<String> proposals = resolved.stream()
90 .map(e -> nameProposalService.proposeName(e, mapper))
91 .filter(Optional::isPresent)
92 .map(Optional::get);
93
94 return proposals.findFirst();
95 });
82 } 96 }
83 97
84 @Nullable 98 @Nullable
diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java
index a61f4ddd..f5dd8a04 100644
--- a/src/main/java/cuchaz/enigma/gui/Gui.java
+++ b/src/main/java/cuchaz/enigma/gui/Gui.java
@@ -31,9 +31,7 @@ import cuchaz.enigma.gui.panels.PanelIdentifier;
31import cuchaz.enigma.gui.panels.PanelObf; 31import cuchaz.enigma.gui.panels.PanelObf;
32import cuchaz.enigma.gui.util.History; 32import cuchaz.enigma.gui.util.History;
33import cuchaz.enigma.throwables.IllegalNameException; 33import cuchaz.enigma.throwables.IllegalNameException;
34import cuchaz.enigma.translation.mapping.AccessModifier; 34import cuchaz.enigma.translation.mapping.*;
35import cuchaz.enigma.translation.mapping.EntryResolver;
36import cuchaz.enigma.translation.mapping.ResolutionStrategy;
37import cuchaz.enigma.translation.representation.entry.*; 35import cuchaz.enigma.translation.representation.entry.*;
38import cuchaz.enigma.utils.Utils; 36import cuchaz.enigma.utils.Utils;
39import de.sciss.syntaxpane.DefaultSyntaxKit; 37import de.sciss.syntaxpane.DefaultSyntaxKit;
@@ -312,13 +310,8 @@ public class Gui {
312 return this.controller; 310 return this.controller;
313 } 311 }
314 312
315 public void onStartOpenJar(String message) { 313 public void onStartOpenJar() {
316 this.classesPanel.removeAll(); 314 this.classesPanel.removeAll();
317 JPanel panel = new JPanel();
318 panel.setLayout(new FlowLayout());
319 panel.add(new JLabel(message));
320 this.classesPanel.add(panel);
321
322 redraw(); 315 redraw();
323 } 316 }
324 317
@@ -447,7 +440,7 @@ public class Gui {
447 440
448 this.cursorReference = reference; 441 this.cursorReference = reference;
449 442
450 EntryReference<Entry<?>, Entry<?>> translatedReference = controller.getDeobfuscator().deobfuscate(reference); 443 EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(reference);
451 444
452 infoPanel.removeAll(); 445 infoPanel.removeAll();
453 if (translatedReference.entry instanceof ClassEntry) { 446 if (translatedReference.entry instanceof ClassEntry) {
@@ -509,7 +502,7 @@ public class Gui {
509 } 502 }
510 503
511 private JComboBox<AccessModifier> addModifierComboBox(JPanel container, String name, Entry entry) { 504 private JComboBox<AccessModifier> addModifierComboBox(JPanel container, String name, Entry entry) {
512 if (!getController().entryIsInJar(entry)) 505 if (!getController().project.isRenamable(entry))
513 return null; 506 return null;
514 JPanel panel = new JPanel(); 507 JPanel panel = new JPanel();
515 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); 508 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
@@ -519,8 +512,16 @@ public class Gui {
519 JComboBox<AccessModifier> combo = new JComboBox<>(AccessModifier.values()); 512 JComboBox<AccessModifier> combo = new JComboBox<>(AccessModifier.values());
520 ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT); 513 ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT);
521 combo.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); 514 combo.setPreferredSize(new Dimension(100, label.getPreferredSize().height));
522 combo.setSelectedIndex(getController().getDeobfuscator().getModifier(entry).ordinal()); 515
523 combo.addItemListener(getController()::modifierChange); 516 EntryMapping mapping = controller.project.getMapper().getDeobfMapping(entry);
517 if (mapping != null) {
518 combo.setSelectedIndex(mapping.getAccessModifier().ordinal());
519 } else {
520 combo.setSelectedIndex(AccessModifier.UNCHANGED.ordinal());
521 }
522
523 combo.addItemListener(controller::modifierChange);
524
524 panel.add(combo); 525 panel.add(combo);
525 526
526 container.add(panel); 527 container.add(panel);
@@ -529,6 +530,8 @@ public class Gui {
529 } 530 }
530 531
531 public void onCaretMove(int pos) { 532 public void onCaretMove(int pos) {
533 EntryRemapper mapper = controller.project.getMapper();
534
532 Token token = this.controller.getToken(pos); 535 Token token = this.controller.getToken(pos);
533 boolean isToken = token != null; 536 boolean isToken = token != null;
534 537
@@ -539,7 +542,7 @@ public class Gui {
539 shouldNavigateOnClick = false; 542 shouldNavigateOnClick = false;
540 Entry<?> navigationEntry = referenceEntry; 543 Entry<?> navigationEntry = referenceEntry;
541 if (cursorReference.context == null) { 544 if (cursorReference.context == null) {
542 EntryResolver resolver = controller.getDeobfuscator().getMapper().getObfResolver(); 545 EntryResolver resolver = mapper.getObfResolver();
543 navigationEntry = resolver.resolveFirstEntry(referenceEntry, ResolutionStrategy.RESOLVE_ROOT); 546 navigationEntry = resolver.resolveFirstEntry(referenceEntry, ResolutionStrategy.RESOLVE_ROOT);
544 } 547 }
545 controller.navigateTo(navigationEntry); 548 controller.navigateTo(navigationEntry);
@@ -550,8 +553,7 @@ public class Gui {
550 boolean isFieldEntry = isToken && referenceEntry instanceof FieldEntry; 553 boolean isFieldEntry = isToken && referenceEntry instanceof FieldEntry;
551 boolean isMethodEntry = isToken && referenceEntry instanceof MethodEntry && !((MethodEntry) referenceEntry).isConstructor(); 554 boolean isMethodEntry = isToken && referenceEntry instanceof MethodEntry && !((MethodEntry) referenceEntry).isConstructor();
552 boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor(); 555 boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor();
553 boolean isInJar = isToken && this.controller.entryIsInJar(referenceEntry); 556 boolean isRenamable = isToken && this.controller.project.isRenamable(cursorReference);
554 boolean isRenamable = isToken && this.controller.getDeobfuscator().isRenamable(cursorReference);
555 557
556 if (isToken) { 558 if (isToken) {
557 showCursorReference(cursorReference); 559 showCursorReference(cursorReference);
@@ -564,12 +566,12 @@ public class Gui {
564 this.popupMenu.showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry); 566 this.popupMenu.showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry);
565 this.popupMenu.showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry); 567 this.popupMenu.showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry);
566 this.popupMenu.showCallsSpecificMenu.setEnabled(isMethodEntry); 568 this.popupMenu.showCallsSpecificMenu.setEnabled(isMethodEntry);
567 this.popupMenu.openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry)); 569 this.popupMenu.openEntryMenu.setEnabled(isRenamable && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry));
568 this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousReference()); 570 this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousReference());
569 this.popupMenu.openNextMenu.setEnabled(this.controller.hasNextReference()); 571 this.popupMenu.openNextMenu.setEnabled(this.controller.hasNextReference());
570 this.popupMenu.toggleMappingMenu.setEnabled(isRenamable); 572 this.popupMenu.toggleMappingMenu.setEnabled(isRenamable);
571 573
572 if (isToken && this.controller.getDeobfuscator().isRemapped(referenceEntry)) { 574 if (isToken && !Objects.equals(referenceEntry, mapper.deobfuscate(referenceEntry))) {
573 this.popupMenu.toggleMappingMenu.setText("Reset to obfuscated"); 575 this.popupMenu.toggleMappingMenu.setText("Reset to obfuscated");
574 } else { 576 } else {
575 this.popupMenu.toggleMappingMenu.setText("Mark as deobfuscated"); 577 this.popupMenu.toggleMappingMenu.setText("Mark as deobfuscated");
@@ -581,7 +583,7 @@ public class Gui {
581 // init the text box 583 // init the text box
582 renameTextField = new JTextField(); 584 renameTextField = new JTextField();
583 585
584 EntryReference<Entry<?>, Entry<?>> translatedReference = controller.getDeobfuscator().deobfuscate(cursorReference); 586 EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(cursorReference);
585 renameTextField.setText(translatedReference.getNameableName()); 587 renameTextField.setText(translatedReference.getNameableName());
586 588
587 renameTextField.setPreferredSize(new Dimension(360, renameTextField.getPreferredSize().height)); 589 renameTextField.setPreferredSize(new Dimension(360, renameTextField.getPreferredSize().height));
@@ -728,7 +730,10 @@ public class Gui {
728 } 730 }
729 731
730 public void toggleMapping() { 732 public void toggleMapping() {
731 if (this.controller.getDeobfuscator().isRemapped(cursorReference.entry)) { 733 Entry<?> obfEntry = cursorReference.entry;
734 Entry<?> deobfEntry = controller.project.getMapper().deobfuscate(obfEntry);
735
736 if (!Objects.equals(obfEntry, deobfEntry)) {
732 this.controller.removeMapping(cursorReference); 737 this.controller.removeMapping(cursorReference);
733 } else { 738 } else {
734 this.controller.markAsDeobfuscated(cursorReference); 739 this.controller.markAsDeobfuscated(cursorReference);
diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java
index 16833331..a55d2cd3 100644
--- a/src/main/java/cuchaz/enigma/gui/GuiController.java
+++ b/src/main/java/cuchaz/enigma/gui/GuiController.java
@@ -14,9 +14,13 @@ package cuchaz.enigma.gui;
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import com.google.common.util.concurrent.ThreadFactoryBuilder; 15import com.google.common.util.concurrent.ThreadFactoryBuilder;
16import com.strobel.decompiler.languages.java.ast.CompilationUnit; 16import com.strobel.decompiler.languages.java.ast.CompilationUnit;
17import cuchaz.enigma.Deobfuscator; 17import cuchaz.enigma.CompiledSourceTypeLoader;
18import cuchaz.enigma.Enigma;
19import cuchaz.enigma.EnigmaProject;
18import cuchaz.enigma.SourceProvider; 20import cuchaz.enigma.SourceProvider;
19import cuchaz.enigma.analysis.*; 21import cuchaz.enigma.analysis.*;
22import cuchaz.enigma.api.service.ObfuscationTestService;
23import cuchaz.enigma.bytecode.translators.SourceFixVisitor;
20import cuchaz.enigma.config.Config; 24import cuchaz.enigma.config.Config;
21import cuchaz.enigma.gui.dialog.ProgressDialog; 25import cuchaz.enigma.gui.dialog.ProgressDialog;
22import cuchaz.enigma.gui.util.History; 26import cuchaz.enigma.gui.util.History;
@@ -30,114 +34,150 @@ import cuchaz.enigma.translation.representation.entry.Entry;
30import cuchaz.enigma.translation.representation.entry.FieldEntry; 34import cuchaz.enigma.translation.representation.entry.FieldEntry;
31import cuchaz.enigma.translation.representation.entry.MethodEntry; 35import cuchaz.enigma.translation.representation.entry.MethodEntry;
32import cuchaz.enigma.utils.ReadableToken; 36import cuchaz.enigma.utils.ReadableToken;
37import org.objectweb.asm.Opcodes;
33 38
34import javax.annotation.Nullable; 39import javax.annotation.Nullable;
35import javax.swing.*; 40import javax.swing.*;
36import java.awt.event.ItemEvent; 41import java.awt.event.ItemEvent;
37import java.io.File;
38import java.io.IOException;
39import java.io.PrintWriter; 42import java.io.PrintWriter;
40import java.io.StringWriter; 43import java.io.StringWriter;
41import java.nio.file.Path; 44import java.nio.file.Path;
42import java.util.Collection; 45import java.util.Collection;
43import java.util.List; 46import java.util.List;
47import java.util.Optional;
44import java.util.concurrent.ExecutorService; 48import java.util.concurrent.ExecutorService;
45import java.util.concurrent.Executors; 49import java.util.concurrent.Executors;
46import java.util.jar.JarFile;
47import java.util.stream.Collectors; 50import java.util.stream.Collectors;
51import java.util.stream.Stream;
48 52
49public class GuiController { 53public class GuiController {
50 private static final ExecutorService DECOMPILER_SERVICE = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setDaemon(true).setNameFormat("decompiler-thread").build()); 54 private static final ExecutorService DECOMPILER_SERVICE = Executors.newSingleThreadExecutor(
55 new ThreadFactoryBuilder()
56 .setDaemon(true)
57 .setNameFormat("decompiler-thread")
58 .build()
59 );
51 60
52 private final Gui gui; 61 private final Gui gui;
53 private Deobfuscator deobfuscator; 62 public final Enigma enigma;
54 private DecompiledClassSource currentSource;
55 63
64 public EnigmaProject project;
65 private SourceProvider sourceProvider;
66 private IndexTreeBuilder indexTreeBuilder;
56 67
57 private Path loadedMappingPath; 68 private Path loadedMappingPath;
58 private MappingFormat loadedMappingFormat; 69 private MappingFormat loadedMappingFormat;
59 70
71 private DecompiledClassSource currentSource;
72
60 public GuiController(Gui gui) { 73 public GuiController(Gui gui) {
61 this.gui = gui; 74 this.gui = gui;
75 // TODO: load and set profile
76 this.enigma = Enigma.create();
62 } 77 }
63 78
64 public boolean isDirty() { 79 public boolean isDirty() {
65 if (deobfuscator == null) { 80 return project != null && project.getMapper().isDirty();
66 return false;
67 }
68 return deobfuscator.getMapper().isDirty();
69 } 81 }
70 82
71 public void openJar(final JarFile jar) throws IOException { 83 public void openJar(final Path jarPath) {
72 this.gui.onStartOpenJar("Loading JAR..."); 84 this.gui.onStartOpenJar();
73 this.deobfuscator = new Deobfuscator(jar, this.gui::onStartOpenJar); 85
74 this.gui.onFinishOpenJar(jar.getName()); 86 ProgressDialog.runOffThread(gui.getFrame(), progress -> {
75 refreshClasses(); 87 project = enigma.openJar(jarPath, progress);
88
89 indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex());
90
91 CompiledSourceTypeLoader typeLoader = new CompiledSourceTypeLoader(project.getClassCache());
92 typeLoader.addVisitor(visitor -> new SourceFixVisitor(Opcodes.ASM5, visitor, project.getJarIndex()));
93 sourceProvider = new SourceProvider(SourceProvider.createSettings(), typeLoader);
94
95 gui.onFinishOpenJar(jarPath.getFileName().toString());
96
97 refreshClasses();
98 });
76 } 99 }
77 100
78 public void closeJar() { 101 public void closeJar() {
79 this.deobfuscator = null; 102 this.project = null;
80 this.gui.onCloseJar(); 103 this.gui.onCloseJar();
81 } 104 }
82 105
83 public void openMappings(MappingFormat format, Path path) { 106 public void openMappings(MappingFormat format, Path path) {
84 if (deobfuscator == null) return; 107 if (project == null) return;
85 ProgressDialog.runInThread(this.gui.getFrame(), progress -> { 108
109 gui.setMappingsFile(path);
110
111 ProgressDialog.runOffThread(gui.getFrame(), progress -> {
86 try { 112 try {
87 EntryTree<EntryMapping> mappings = format.read(path, progress); 113 EntryTree<EntryMapping> mappings = format.read(path, progress);
88 deobfuscator.setMappings(mappings, progress); 114 project.setMappings(mappings);
89 115
90 gui.setMappingsFile(path);
91 loadedMappingFormat = format; 116 loadedMappingFormat = format;
92 loadedMappingPath = path; 117 loadedMappingPath = path;
93 118
94 refreshClasses(); 119 refreshClasses();
95 refreshCurrentClass(); 120 refreshCurrentClass();
96 } catch (MappingParseException e) { 121 } catch (MappingParseException e) {
97 JOptionPane.showMessageDialog(this.gui.getFrame(), e.getMessage()); 122 JOptionPane.showMessageDialog(gui.getFrame(), e.getMessage());
98 } 123 }
99 }); 124 });
100 } 125 }
101 126
102 public void saveMappings(Path path) { 127 public void saveMappings(Path path) {
103 saveMappings(loadedMappingFormat, path); 128 if (project == null) return;
129
130 saveMappings(path, loadedMappingFormat);
104 } 131 }
105 132
106 public void saveMappings(MappingFormat format, Path path) { 133 public void saveMappings(Path path, MappingFormat format) {
107 if (deobfuscator == null) return; 134 if (project == null) return;
108 EntryRemapper mapper = deobfuscator.getMapper(); 135
136 ProgressDialog.runOffThread(this.gui.getFrame(), progress -> {
137 EntryRemapper mapper = project.getMapper();
109 138
110 MappingDelta<EntryMapping> delta = mapper.takeMappingDelta(); 139 MappingDelta<EntryMapping> delta = mapper.takeMappingDelta();
111 boolean saveAll = !path.equals(loadedMappingPath); 140 boolean saveAll = !path.equals(loadedMappingPath);
141
142 loadedMappingFormat = format;
143 loadedMappingPath = path;
112 144
113 ProgressDialog.runInThread(this.gui.getFrame(), progress -> {
114 if (saveAll) { 145 if (saveAll) {
115 format.write(mapper.getObfToDeobf(), path, progress); 146 format.write(mapper.getObfToDeobf(), path, progress);
116 } else { 147 } else {
117 format.write(mapper.getObfToDeobf(), delta, path, progress); 148 format.write(mapper.getObfToDeobf(), delta, path, progress);
118 } 149 }
119 }); 150 });
120
121 loadedMappingFormat = format;
122 loadedMappingPath = path;
123 } 151 }
124 152
125 public void closeMappings() { 153 public void closeMappings() {
126 if (deobfuscator == null) return; 154 if (project == null) return;
127 this.deobfuscator.setMappings(null); 155
156 project.setMappings(null);
157
128 this.gui.setMappingsFile(null); 158 this.gui.setMappingsFile(null);
129 refreshClasses(); 159 refreshClasses();
130 refreshCurrentClass(); 160 refreshCurrentClass();
131 } 161 }
132 162
133 public void exportSource(final File dirOut) { 163 public void exportSource(final Path path) {
134 if (deobfuscator == null) return; 164 if (project == null) return;
135 ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut.toPath(), progress)); 165
166 ProgressDialog.runOffThread(this.gui.getFrame(), progress -> {
167 EnigmaProject.JarExport jar = project.exportRemappedJar(progress);
168 EnigmaProject.SourceExport source = jar.decompile(progress);
169
170 source.write(path, progress);
171 });
136 } 172 }
137 173
138 public void exportJar(final File fileOut) { 174 public void exportJar(final Path path) {
139 if (deobfuscator == null) return; 175 if (project == null) return;
140 ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeTransformedJar(fileOut, progress)); 176
177 ProgressDialog.runOffThread(this.gui.getFrame(), progress -> {
178 EnigmaProject.JarExport jar = project.exportRemappedJar(progress);
179 jar.write(path, progress);
180 });
141 } 181 }
142 182
143 public Token getToken(int pos) { 183 public Token getToken(int pos) {
@@ -167,85 +207,9 @@ public class GuiController {
167 ); 207 );
168 } 208 }
169 209
170 public boolean entryIsInJar(Entry<?> entry) {
171 if (entry == null || deobfuscator == null) return false;
172 return this.deobfuscator.isRenamable(entry);
173 }
174
175 public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) {
176 Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
177 ClassInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildClassInheritance(translator, entry);
178 return ClassInheritanceTreeNode.findNode(rootNode, entry);
179 }
180
181 public ClassImplementationsTreeNode getClassImplementations(ClassEntry entry) {
182 Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
183 return this.deobfuscator.getIndexTreeBuilder().buildClassImplementations(translator, entry);
184 }
185
186 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry entry) {
187 Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
188 MethodInheritanceTreeNode rootNode = this.deobfuscator.getIndexTreeBuilder().buildMethodInheritance(translator, entry);
189 return MethodInheritanceTreeNode.findNode(rootNode, entry);
190 }
191
192 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry entry) {
193 Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
194 List<MethodImplementationsTreeNode> rootNodes = this.deobfuscator.getIndexTreeBuilder().buildMethodImplementations(translator, entry);
195 if (rootNodes.isEmpty()) {
196 return null;
197 }
198 if (rootNodes.size() > 1) {
199 System.err.println("WARNING: Method " + entry + " implements multiple interfaces. Only showing first one.");
200 }
201 return MethodImplementationsTreeNode.findNode(rootNodes.get(0), entry);
202 }
203
204 public ClassReferenceTreeNode getClassReferences(ClassEntry entry) {
205 Translator deobfuscator = this.deobfuscator.getMapper().getDeobfuscator();
206 ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, entry);
207 rootNode.load(this.deobfuscator.getJarIndex(), true);
208 return rootNode;
209 }
210
211 public FieldReferenceTreeNode getFieldReferences(FieldEntry entry) {
212 Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
213 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, entry);
214 rootNode.load(this.deobfuscator.getJarIndex(), true);
215 return rootNode;
216 }
217
218 public MethodReferenceTreeNode getMethodReferences(MethodEntry entry, boolean recursive) {
219 Translator translator = this.deobfuscator.getMapper().getDeobfuscator();
220 MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, entry);
221 rootNode.load(this.deobfuscator.getJarIndex(), true, recursive);
222 return rootNode;
223 }
224
225 public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree) {
226 this.deobfuscator.rename(reference.getNameableEntry(), newName);
227
228 if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass())
229 this.gui.moveClassTree(reference, newName);
230 refreshCurrentClass(reference);
231 }
232
233 public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference) {
234 this.deobfuscator.removeMapping(reference.getNameableEntry());
235 if (reference.entry instanceof ClassEntry)
236 this.gui.moveClassTree(reference, false, true);
237 refreshCurrentClass(reference);
238 }
239
240 public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference) {
241 this.deobfuscator.markAsDeobfuscated(reference.getNameableEntry());
242 if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass())
243 this.gui.moveClassTree(reference, true, false);
244 refreshCurrentClass(reference);
245 }
246
247 /** 210 /**
248 * Navigates to the declaration with respect to navigation history 211 * Navigates to the declaration with respect to navigation history
212 *
249 * @param entry the entry whose declaration will be navigated to 213 * @param entry the entry whose declaration will be navigated to
250 */ 214 */
251 public void openDeclaration(Entry<?> entry) { 215 public void openDeclaration(Entry<?> entry) {
@@ -257,6 +221,7 @@ public class GuiController {
257 221
258 /** 222 /**
259 * Navigates to the reference with respect to navigation history 223 * Navigates to the reference with respect to navigation history
224 *
260 * @param reference the reference 225 * @param reference the reference
261 */ 226 */
262 public void openReference(EntryReference<Entry<?>, Entry<?>> reference) { 227 public void openReference(EntryReference<Entry<?>, Entry<?>> reference) {
@@ -275,12 +240,13 @@ public class GuiController {
275 240
276 /** 241 /**
277 * Navigates to the reference without modifying history. If the class is not currently loaded, it will be loaded. 242 * Navigates to the reference without modifying history. If the class is not currently loaded, it will be loaded.
243 *
278 * @param reference the reference 244 * @param reference the reference
279 */ 245 */
280 private void setReference(EntryReference<Entry<?>, Entry<?>> reference) { 246 private void setReference(EntryReference<Entry<?>, Entry<?>> reference) {
281 // get the reference target class 247 // get the reference target class
282 ClassEntry classEntry = reference.getLocationClassEntry(); 248 ClassEntry classEntry = reference.getLocationClassEntry();
283 if (!this.deobfuscator.isRenamable(classEntry)) { 249 if (!project.isRenamable(classEntry)) {
284 throw new IllegalArgumentException("Obfuscated class " + classEntry + " was not found in the jar!"); 250 throw new IllegalArgumentException("Obfuscated class " + classEntry + " was not found in the jar!");
285 } 251 }
286 252
@@ -294,6 +260,7 @@ public class GuiController {
294 260
295 /** 261 /**
296 * Navigates to the reference without modifying history. Assumes the class is loaded. 262 * Navigates to the reference without modifying history. Assumes the class is loaded.
263 *
297 * @param reference 264 * @param reference
298 */ 265 */
299 private void showReference(EntryReference<Entry<?>, Entry<?>> reference) { 266 private void showReference(EntryReference<Entry<?>, Entry<?>> reference) {
@@ -307,7 +274,7 @@ public class GuiController {
307 } 274 }
308 275
309 public Collection<Token> getTokensForReference(EntryReference<Entry<?>, Entry<?>> reference) { 276 public Collection<Token> getTokensForReference(EntryReference<Entry<?>, Entry<?>> reference) {
310 EntryRemapper mapper = this.deobfuscator.getMapper(); 277 EntryRemapper mapper = this.project.getMapper();
311 278
312 SourceIndex index = this.currentSource.getIndex(); 279 SourceIndex index = this.currentSource.getIndex();
313 return mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_CLOSEST) 280 return mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_CLOSEST)
@@ -337,7 +304,7 @@ public class GuiController {
337 } 304 }
338 305
339 public void navigateTo(Entry<?> entry) { 306 public void navigateTo(Entry<?> entry) {
340 if (!entryIsInJar(entry)) { 307 if (!project.isRenamable(entry)) {
341 // entry is not in the jar. Ignore it 308 // entry is not in the jar. Ignore it
342 return; 309 return;
343 } 310 }
@@ -345,7 +312,7 @@ public class GuiController {
345 } 312 }
346 313
347 public void navigateTo(EntryReference<Entry<?>, Entry<?>> reference) { 314 public void navigateTo(EntryReference<Entry<?>, Entry<?>> reference) {
348 if (!entryIsInJar(reference.getLocationClassEntry())) { 315 if (!project.isRenamable(reference.getLocationClassEntry())) {
349 return; 316 return;
350 } 317 }
351 openReference(reference); 318 openReference(reference);
@@ -354,11 +321,38 @@ public class GuiController {
354 private void refreshClasses() { 321 private void refreshClasses() {
355 List<ClassEntry> obfClasses = Lists.newArrayList(); 322 List<ClassEntry> obfClasses = Lists.newArrayList();
356 List<ClassEntry> deobfClasses = Lists.newArrayList(); 323 List<ClassEntry> deobfClasses = Lists.newArrayList();
357 this.deobfuscator.getSeparatedClasses(obfClasses, deobfClasses); 324 this.addSeparatedClasses(obfClasses, deobfClasses);
358 this.gui.setObfClasses(obfClasses); 325 this.gui.setObfClasses(obfClasses);
359 this.gui.setDeobfClasses(deobfClasses); 326 this.gui.setDeobfClasses(deobfClasses);
360 } 327 }
361 328
329 public void addSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) {
330 EntryRemapper mapper = project.getMapper();
331
332 Collection<ClassEntry> classes = project.getJarIndex().getEntryIndex().getClasses();
333 Stream<ClassEntry> visibleClasses = classes.stream()
334 .filter(entry -> !entry.isInnerClass());
335
336 visibleClasses.forEach(entry -> {
337 ClassEntry deobfEntry = mapper.deobfuscate(entry);
338
339 Optional<ObfuscationTestService> obfService = enigma.getServices().get(ObfuscationTestService.TYPE);
340 boolean obfuscated = deobfEntry.equals(entry);
341
342 if (obfuscated && obfService.isPresent()) {
343 if (obfService.get().testDeobfuscated(entry)) {
344 obfuscated = false;
345 }
346 }
347
348 if (obfuscated) {
349 obfClasses.add(entry);
350 } else {
351 deobfClasses.add(entry);
352 }
353 });
354 }
355
362 public void refreshCurrentClass() { 356 public void refreshCurrentClass() {
363 refreshCurrentClass(null); 357 refreshCurrentClass(null);
364 } 358 }
@@ -384,10 +378,10 @@ public class GuiController {
384 DECOMPILER_SERVICE.submit(() -> { 378 DECOMPILER_SERVICE.submit(() -> {
385 try { 379 try {
386 if (requiresDecompile) { 380 if (requiresDecompile) {
387 currentSource = decompileSource(targetClass, deobfuscator.getObfSourceProvider()); 381 currentSource = decompileSource(targetClass);
388 } 382 }
389 383
390 remapSource(deobfuscator.getMapper().getDeobfuscator()); 384 remapSource(project.getMapper().getDeobfuscator());
391 callback.run(); 385 callback.run();
392 } catch (Throwable t) { 386 } catch (Throwable t) {
393 System.err.println("An exception was thrown while decompiling class " + classEntry.getFullName()); 387 System.err.println("An exception was thrown while decompiling class " + classEntry.getFullName());
@@ -396,7 +390,7 @@ public class GuiController {
396 }); 390 });
397 } 391 }
398 392
399 private DecompiledClassSource decompileSource(ClassEntry targetClass, SourceProvider sourceProvider) { 393 private DecompiledClassSource decompileSource(ClassEntry targetClass) {
400 try { 394 try {
401 CompilationUnit sourceTree = sourceProvider.getSources(targetClass.getFullName()); 395 CompilationUnit sourceTree = sourceProvider.getSources(targetClass.getFullName());
402 if (sourceTree == null) { 396 if (sourceTree == null) {
@@ -410,7 +404,7 @@ public class GuiController {
410 String sourceString = sourceProvider.writeSourceToString(sourceTree); 404 String sourceString = sourceProvider.writeSourceToString(sourceTree);
411 405
412 SourceIndex index = SourceIndex.buildIndex(sourceString, sourceTree, true); 406 SourceIndex index = SourceIndex.buildIndex(sourceString, sourceTree, true);
413 index.resolveReferences(deobfuscator.getMapper().getObfResolver()); 407 index.resolveReferences(project.getMapper().getObfResolver());
414 408
415 return new DecompiledClassSource(targetClass, index); 409 return new DecompiledClassSource(targetClass, index);
416 } catch (Throwable t) { 410 } catch (Throwable t) {
@@ -426,20 +420,105 @@ public class GuiController {
426 return; 420 return;
427 } 421 }
428 422
429 currentSource.remapSource(deobfuscator, translator); 423 currentSource.remapSource(project, translator);
430 424
431 gui.setEditorTheme(Config.getInstance().lookAndFeel); 425 gui.setEditorTheme(Config.getInstance().lookAndFeel);
432 gui.setSource(currentSource); 426 gui.setSource(currentSource);
433 } 427 }
434 428
435 public Deobfuscator getDeobfuscator() {
436 return deobfuscator;
437 }
438
439 public void modifierChange(ItemEvent event) { 429 public void modifierChange(ItemEvent event) {
440 if (event.getStateChange() == ItemEvent.SELECTED) { 430 if (event.getStateChange() == ItemEvent.SELECTED) {
441 deobfuscator.changeModifier(gui.cursorReference.entry, (AccessModifier) event.getItem()); 431 EntryRemapper mapper = project.getMapper();
432 Entry<?> entry = gui.cursorReference.entry;
433 AccessModifier modifier = (AccessModifier) event.getItem();
434
435 EntryMapping mapping = mapper.getDeobfMapping(entry);
436 if (mapping != null) {
437 mapper.mapFromObf(entry, new EntryMapping(mapping.getTargetName(), modifier));
438 } else {
439 mapper.mapFromObf(entry, new EntryMapping(entry.getName(), modifier));
440 }
441
442 refreshCurrentClass(); 442 refreshCurrentClass();
443 } 443 }
444 } 444 }
445
446 public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) {
447 Translator translator = project.getMapper().getDeobfuscator();
448 ClassInheritanceTreeNode rootNode = indexTreeBuilder.buildClassInheritance(translator, entry);
449 return ClassInheritanceTreeNode.findNode(rootNode, entry);
450 }
451
452 public ClassImplementationsTreeNode getClassImplementations(ClassEntry entry) {
453 Translator translator = project.getMapper().getDeobfuscator();
454 return this.indexTreeBuilder.buildClassImplementations(translator, entry);
455 }
456
457 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry entry) {
458 Translator translator = project.getMapper().getDeobfuscator();
459 MethodInheritanceTreeNode rootNode = indexTreeBuilder.buildMethodInheritance(translator, entry);
460 return MethodInheritanceTreeNode.findNode(rootNode, entry);
461 }
462
463 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry entry) {
464 Translator translator = project.getMapper().getDeobfuscator();
465 List<MethodImplementationsTreeNode> rootNodes = indexTreeBuilder.buildMethodImplementations(translator, entry);
466 if (rootNodes.isEmpty()) {
467 return null;
468 }
469 if (rootNodes.size() > 1) {
470 System.err.println("WARNING: Method " + entry + " implements multiple interfaces. Only showing first one.");
471 }
472 return MethodImplementationsTreeNode.findNode(rootNodes.get(0), entry);
473 }
474
475 public ClassReferenceTreeNode getClassReferences(ClassEntry entry) {
476 Translator deobfuscator = project.getMapper().getDeobfuscator();
477 ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, entry);
478 rootNode.load(project.getJarIndex(), true);
479 return rootNode;
480 }
481
482 public FieldReferenceTreeNode getFieldReferences(FieldEntry entry) {
483 Translator translator = project.getMapper().getDeobfuscator();
484 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, entry);
485 rootNode.load(project.getJarIndex(), true);
486 return rootNode;
487 }
488
489 public MethodReferenceTreeNode getMethodReferences(MethodEntry entry, boolean recursive) {
490 Translator translator = project.getMapper().getDeobfuscator();
491 MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, entry);
492 rootNode.load(project.getJarIndex(), true, recursive);
493 return rootNode;
494 }
495
496 public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree) {
497 Entry<?> entry = reference.getNameableEntry();
498 project.getMapper().mapFromObf(entry, new EntryMapping(newName));
499
500 if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass())
501 this.gui.moveClassTree(reference, newName);
502
503 refreshCurrentClass(reference);
504 }
505
506 public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference) {
507 project.getMapper().removeByObf(reference.getNameableEntry());
508
509 if (reference.entry instanceof ClassEntry)
510 this.gui.moveClassTree(reference, false, true);
511 refreshCurrentClass(reference);
512 }
513
514 public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference) {
515 EntryRemapper mapper = project.getMapper();
516 Entry<?> entry = reference.getNameableEntry();
517 mapper.mapFromObf(entry, new EntryMapping(mapper.deobfuscate(entry).getName()));
518
519 if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass())
520 this.gui.moveClassTree(reference, true, false);
521
522 refreshCurrentClass(reference);
523 }
445} 524}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java
index 84fe7c88..c135d033 100644
--- a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java
+++ b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java
@@ -57,7 +57,7 @@ public class ProgressDialog implements ProgressListener, AutoCloseable {
57 this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); 57 this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
58 } 58 }
59 59
60 public static void runInThread(final JFrame parent, final ProgressRunnable runnable) { 60 public static void runOffThread(final JFrame parent, final ProgressRunnable runnable) {
61 new Thread(() -> 61 new Thread(() ->
62 { 62 {
63 try (ProgressDialog progress = new ProgressDialog(parent)) { 63 try (ProgressDialog progress = new ProgressDialog(parent)) {
@@ -68,6 +68,7 @@ public class ProgressDialog implements ProgressListener, AutoCloseable {
68 }).start(); 68 }).start();
69 } 69 }
70 70
71 @Override
71 public void close() { 72 public void close() {
72 this.frame.dispose(); 73 this.frame.dispose();
73 } 74 }
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java
index a122bd8a..1657d7b3 100644
--- a/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java
+++ b/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java
@@ -40,7 +40,7 @@ public class SearchDialog {
40 this.parent = parent; 40 this.parent = parent;
41 41
42 deobfClasses = Lists.newArrayList(); 42 deobfClasses = Lists.newArrayList();
43 this.parent.getController().getDeobfuscator().getSeparatedClasses(Lists.newArrayList(), deobfClasses); 43 this.parent.getController().addSeparatedClasses(Lists.newArrayList(), deobfClasses);
44 deobfClasses.removeIf(ClassEntry::isInnerClass); 44 deobfClasses.removeIf(ClassEntry::isInnerClass);
45 } 45 }
46 46
diff --git a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
index 98275b4a..5578325b 100644
--- a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
+++ b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
@@ -13,10 +13,11 @@ import java.awt.event.InputEvent;
13import java.awt.event.KeyEvent; 13import java.awt.event.KeyEvent;
14import java.io.File; 14import java.io.File;
15import java.io.IOException; 15import java.io.IOException;
16import java.net.MalformedURLException;
17import java.net.URISyntaxException; 16import java.net.URISyntaxException;
18import java.net.URL; 17import java.net.URL;
19import java.util.jar.JarFile; 18import java.nio.file.Files;
19import java.nio.file.Path;
20import java.nio.file.Paths;
20 21
21public class MenuBar extends JMenuBar { 22public class MenuBar extends JMenuBar {
22 23
@@ -43,17 +44,9 @@ public class MenuBar extends JMenuBar {
43 menu.add(item); 44 menu.add(item);
44 item.addActionListener(event -> { 45 item.addActionListener(event -> {
45 this.gui.jarFileChooser.setVisible(true); 46 this.gui.jarFileChooser.setVisible(true);
46 File file = new File(this.gui.jarFileChooser.getDirectory() + File.separator + this.gui.jarFileChooser.getFile()); 47 Path path = Paths.get(this.gui.jarFileChooser.getDirectory()).resolve(this.gui.jarFileChooser.getFile());
47 if (file.exists()) { 48 if (Files.exists(path)) {
48 // load the jar in a separate thread 49 gui.getController().openJar(path);
49 new Thread(() ->
50 {
51 try {
52 gui.getController().openJar(new JarFile(file));
53 } catch (IOException ex) {
54 throw new Error(ex);
55 }
56 }).start();
57 } 50 }
58 }); 51 });
59 } 52 }
@@ -106,7 +99,7 @@ public class MenuBar extends JMenuBar {
106 item.addActionListener(event -> { 99 item.addActionListener(event -> {
107 // TODO: Use a specific file chooser for it 100 // TODO: Use a specific file chooser for it
108 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 101 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
109 this.gui.getController().saveMappings(MappingFormat.ENIGMA_FILE, this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); 102 this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), MappingFormat.ENIGMA_FILE);
110 this.saveMappingsMenu.setEnabled(true); 103 this.saveMappingsMenu.setEnabled(true);
111 } 104 }
112 }); 105 });
@@ -118,7 +111,7 @@ public class MenuBar extends JMenuBar {
118 item.addActionListener(event -> { 111 item.addActionListener(event -> {
119 // TODO: Use a specific file chooser for it 112 // TODO: Use a specific file chooser for it
120 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 113 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
121 this.gui.getController().saveMappings(MappingFormat.ENIGMA_DIRECTORY, this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); 114 this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), MappingFormat.ENIGMA_DIRECTORY);
122 this.saveMappingsMenu.setEnabled(true); 115 this.saveMappingsMenu.setEnabled(true);
123 } 116 }
124 }); 117 });
@@ -131,7 +124,7 @@ public class MenuBar extends JMenuBar {
131 item.addActionListener(event -> { 124 item.addActionListener(event -> {
132 // TODO: Use a specific file chooser for it 125 // TODO: Use a specific file chooser for it
133 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 126 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
134 this.gui.getController().saveMappings(MappingFormat.SRG_FILE, this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); 127 this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), MappingFormat.SRG_FILE);
135 this.saveMappingsMenu.setEnabled(true); 128 this.saveMappingsMenu.setEnabled(true);
136 } 129 }
137 }); 130 });
@@ -162,7 +155,7 @@ public class MenuBar extends JMenuBar {
162 menu.add(item); 155 menu.add(item);
163 item.addActionListener(event -> { 156 item.addActionListener(event -> {
164 if (this.gui.exportSourceFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 157 if (this.gui.exportSourceFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
165 this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile()); 158 this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile().toPath());
166 } 159 }
167 }); 160 });
168 this.exportSourceMenu = item; 161 this.exportSourceMenu = item;
@@ -173,8 +166,8 @@ public class MenuBar extends JMenuBar {
173 item.addActionListener(event -> { 166 item.addActionListener(event -> {
174 this.gui.exportJarFileChooser.setVisible(true); 167 this.gui.exportJarFileChooser.setVisible(true);
175 if (this.gui.exportJarFileChooser.getFile() != null) { 168 if (this.gui.exportJarFileChooser.getFile() != null) {
176 File file = new File(this.gui.exportJarFileChooser.getDirectory() + File.separator + this.gui.exportJarFileChooser.getFile()); 169 Path path = Paths.get(this.gui.exportJarFileChooser.getDirectory(), this.gui.exportJarFileChooser.getFile());
177 this.gui.getController().exportJar(file); 170 this.gui.getController().exportJar(path);
178 } 171 }
179 }); 172 });
180 this.exportJarMenu = item; 173 this.exportJarMenu = item;
@@ -202,7 +195,7 @@ public class MenuBar extends JMenuBar {
202 search.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.SHIFT_MASK)); 195 search.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.SHIFT_MASK));
203 menu.add(search); 196 menu.add(search);
204 search.addActionListener(event -> { 197 search.addActionListener(event -> {
205 if (this.gui.getController().getDeobfuscator() != null) { 198 if (this.gui.getController().project != null) {
206 new SearchDialog(this.gui).show(); 199 new SearchDialog(this.gui).show();
207 } 200 }
208 }); 201 });
diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java b/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java
index ff84648c..e8a9cadc 100644
--- a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java
+++ b/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java
@@ -1,6 +1,6 @@
1package cuchaz.enigma.gui.panels; 1package cuchaz.enigma.gui.panels;
2 2
3import cuchaz.enigma.Deobfuscator; 3import cuchaz.enigma.EnigmaProject;
4import cuchaz.enigma.analysis.EntryReference; 4import cuchaz.enigma.analysis.EntryReference;
5import cuchaz.enigma.config.Config; 5import cuchaz.enigma.config.Config;
6import cuchaz.enigma.gui.BrowserCaret; 6import cuchaz.enigma.gui.BrowserCaret;
@@ -94,8 +94,8 @@ public class PanelEditor extends JEditorPane {
94 if (!gui.popupMenu.renameMenu.isEnabled()) return; 94 if (!gui.popupMenu.renameMenu.isEnabled()) return;
95 95
96 if (!event.isControlDown() && !event.isAltDown()) { 96 if (!event.isControlDown() && !event.isAltDown()) {
97 Deobfuscator deobfuscator = gui.getController().getDeobfuscator(); 97 EnigmaProject project = gui.getController().project;
98 EntryReference<Entry<?>, Entry<?>> reference = deobfuscator.deobfuscate(gui.cursorReference); 98 EntryReference<Entry<?>, Entry<?>> reference = project.getMapper().deobfuscate(gui.cursorReference);
99 Entry<?> entry = reference.getNameableEntry(); 99 Entry<?> entry = reference.getNameableEntry();
100 100
101 String name = String.valueOf(event.getKeyChar()); 101 String name = String.valueOf(event.getKeyChar());
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java
index 8c4a3268..c9808cc9 100644
--- a/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java
+++ b/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java
@@ -21,7 +21,7 @@ public class EntryRemapper {
21 21
22 private final MappingValidator validator; 22 private final MappingValidator validator;
23 23
24 public EntryRemapper(JarIndex jarIndex, EntryTree<EntryMapping> obfToDeobf) { 24 private EntryRemapper(JarIndex jarIndex, EntryTree<EntryMapping> obfToDeobf) {
25 this.obfToDeobf = new DeltaTrackingTree<>(obfToDeobf); 25 this.obfToDeobf = new DeltaTrackingTree<>(obfToDeobf);
26 26
27 this.obfResolver = jarIndex.getEntryResolver(); 27 this.obfResolver = jarIndex.getEntryResolver();
@@ -31,8 +31,12 @@ public class EntryRemapper {
31 this.validator = new MappingValidator(obfToDeobf, deobfuscator, jarIndex); 31 this.validator = new MappingValidator(obfToDeobf, deobfuscator, jarIndex);
32 } 32 }
33 33
34 public EntryRemapper(JarIndex jarIndex) { 34 public static EntryRemapper mapped(JarIndex index, EntryTree<EntryMapping> obfToDeobf) {
35 this(jarIndex, new HashEntryTree<>()); 35 return new EntryRemapper(index, obfToDeobf);
36 }
37
38 public static EntryRemapper empty(JarIndex index) {
39 return new EntryRemapper(index, new HashEntryTree<>());
36 } 40 }
37 41
38 public <E extends Entry<?>> void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping) { 42 public <E extends Entry<?>> void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping) {
diff --git a/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java b/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java
index ae5d6d2c..1dc9748b 100644
--- a/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java
+++ b/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java
@@ -11,13 +11,13 @@
11 11
12package cuchaz.enigma; 12package cuchaz.enigma;
13 13
14import cuchaz.enigma.analysis.ParsedJar; 14import cuchaz.enigma.analysis.ClassCache;
15import cuchaz.enigma.analysis.index.JarIndex; 15import cuchaz.enigma.analysis.index.JarIndex;
16import cuchaz.enigma.analysis.index.PackageVisibilityIndex; 16import cuchaz.enigma.analysis.index.PackageVisibilityIndex;
17import cuchaz.enigma.translation.representation.entry.ClassEntry; 17import cuchaz.enigma.translation.representation.entry.ClassEntry;
18import org.junit.Test; 18import org.junit.Test;
19 19
20import java.util.jar.JarFile; 20import java.nio.file.Paths;
21 21
22import static cuchaz.enigma.TestEntryFactory.newClass; 22import static cuchaz.enigma.TestEntryFactory.newClass;
23import static org.hamcrest.MatcherAssert.assertThat; 23import static org.hamcrest.MatcherAssert.assertThat;
@@ -35,10 +35,8 @@ public class PackageVisibilityIndexTest {
35 private final JarIndex jarIndex; 35 private final JarIndex jarIndex;
36 36
37 public PackageVisibilityIndexTest() throws Exception { 37 public PackageVisibilityIndexTest() throws Exception {
38 jarIndex = JarIndex.empty(); 38 ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/packageAccess.jar"));
39 ParsedJar jar = new ParsedJar(new JarFile("build/test-obf/packageAccess.jar")); 39 jarIndex = classCache.index(ProgressListener.none());
40 jarIndex.indexJar(jar, s -> {
41 });
42 } 40 }
43 41
44 @Test 42 @Test
diff --git a/src/test/java/cuchaz/enigma/TestDeobfed.java b/src/test/java/cuchaz/enigma/TestDeobfed.java
index 14b1418d..3d875dfe 100644
--- a/src/test/java/cuchaz/enigma/TestDeobfed.java
+++ b/src/test/java/cuchaz/enigma/TestDeobfed.java
@@ -11,12 +11,12 @@
11 11
12package cuchaz.enigma; 12package cuchaz.enigma;
13 13
14import cuchaz.enigma.analysis.ParsedJar; 14import cuchaz.enigma.analysis.ClassCache;
15import cuchaz.enigma.analysis.index.JarIndex; 15import cuchaz.enigma.analysis.index.JarIndex;
16import org.junit.BeforeClass; 16import org.junit.BeforeClass;
17import org.junit.Test; 17import org.junit.Test;
18 18
19import java.util.jar.JarFile; 19import java.nio.file.Paths;
20 20
21import static cuchaz.enigma.TestEntryFactory.newClass; 21import static cuchaz.enigma.TestEntryFactory.newClass;
22import static org.hamcrest.MatcherAssert.assertThat; 22import static org.hamcrest.MatcherAssert.assertThat;
@@ -24,15 +24,16 @@ import static org.hamcrest.Matchers.containsInAnyOrder;
24 24
25public class TestDeobfed { 25public class TestDeobfed {
26 26
27 private static ParsedJar jar; 27 private static Enigma enigma;
28 private static ClassCache classCache;
28 private static JarIndex index; 29 private static JarIndex index;
29 30
30 @BeforeClass 31 @BeforeClass
31 public static void beforeClass() 32 public static void beforeClass() throws Exception {
32 throws Exception { 33 enigma = Enigma.create();
33 jar = new ParsedJar(new JarFile("build/test-deobf/translation.jar")); 34
34 index = JarIndex.empty(); 35 classCache = ClassCache.of(Paths.get("build/test-deobf/translation.jar"));
35 index.indexJar(jar, s -> {}); 36 index = classCache.index(ProgressListener.none());
36 } 37 }
37 38
38 @Test 39 @Test
@@ -67,8 +68,9 @@ public class TestDeobfed {
67 @Test 68 @Test
68 public void decompile() 69 public void decompile()
69 throws Exception { 70 throws Exception {
70 Deobfuscator deobfuscator = new Deobfuscator(jar); 71 EnigmaProject project = new EnigmaProject(enigma, classCache, index);
71 SourceProvider sourceProvider = deobfuscator.getObfSourceProvider(); 72
73 SourceProvider sourceProvider = project.getObfSourceProvider();
72 sourceProvider.getSources("a"); 74 sourceProvider.getSources("a");
73 sourceProvider.getSources("b"); 75 sourceProvider.getSources("b");
74 sourceProvider.getSources("c"); 76 sourceProvider.getSources("c");
diff --git a/src/test/java/cuchaz/enigma/TestDeobfuscator.java b/src/test/java/cuchaz/enigma/TestDeobfuscator.java
index e070b66f..5b9611cc 100644
--- a/src/test/java/cuchaz/enigma/TestDeobfuscator.java
+++ b/src/test/java/cuchaz/enigma/TestDeobfuscator.java
@@ -23,9 +23,9 @@ import static org.junit.Assert.assertEquals;
23 23
24public class TestDeobfuscator { 24public class TestDeobfuscator {
25 25
26 private Deobfuscator getDeobfuscator() 26 private Enigma getDeobfuscator()
27 throws IOException { 27 throws IOException {
28 return new Deobfuscator(new JarFile("build/test-obf/loneClass.jar")); 28 return new Enigma(new JarFile("build/test-obf/loneClass.jar"));
29 } 29 }
30 30
31 @Test 31 @Test
@@ -37,10 +37,10 @@ public class TestDeobfuscator {
37 @Test 37 @Test
38 public void getClasses() 38 public void getClasses()
39 throws Exception { 39 throws Exception {
40 Deobfuscator deobfuscator = getDeobfuscator(); 40 Enigma enigma = getDeobfuscator();
41 List<ClassEntry> obfClasses = Lists.newArrayList(); 41 List<ClassEntry> obfClasses = Lists.newArrayList();
42 List<ClassEntry> deobfClasses = Lists.newArrayList(); 42 List<ClassEntry> deobfClasses = Lists.newArrayList();
43 deobfuscator.getSeparatedClasses(obfClasses, deobfClasses); 43 enigma.getSeparatedClasses(obfClasses, deobfClasses);
44 assertEquals(1, obfClasses.size()); 44 assertEquals(1, obfClasses.size());
45 assertEquals("a", obfClasses.get(0).getName()); 45 assertEquals("a", obfClasses.get(0).getName());
46 assertEquals(1, deobfClasses.size()); 46 assertEquals(1, deobfClasses.size());
@@ -50,8 +50,8 @@ public class TestDeobfuscator {
50 @Test 50 @Test
51 public void decompileClass() 51 public void decompileClass()
52 throws Exception { 52 throws Exception {
53 Deobfuscator deobfuscator = getDeobfuscator(); 53 Enigma enigma = getDeobfuscator();
54 SourceProvider sourceProvider = deobfuscator.getObfSourceProvider(); 54 SourceProvider sourceProvider = enigma.getObfSourceProvider();
55 sourceProvider.writeSourceToString(sourceProvider.getSources("a")); 55 sourceProvider.writeSourceToString(sourceProvider.getSources("a"));
56 } 56 }
57} 57}
diff --git a/src/test/java/cuchaz/enigma/TestInnerClasses.java b/src/test/java/cuchaz/enigma/TestInnerClasses.java
index 8738fd79..b6e4e2d4 100644
--- a/src/test/java/cuchaz/enigma/TestInnerClasses.java
+++ b/src/test/java/cuchaz/enigma/TestInnerClasses.java
@@ -11,11 +11,12 @@
11 11
12package cuchaz.enigma; 12package cuchaz.enigma;
13 13
14import cuchaz.enigma.analysis.ParsedJar; 14import cuchaz.enigma.analysis.ClassCache;
15import cuchaz.enigma.analysis.index.JarIndex; 15import cuchaz.enigma.analysis.index.JarIndex;
16import cuchaz.enigma.translation.representation.entry.ClassEntry; 16import cuchaz.enigma.translation.representation.entry.ClassEntry;
17import org.junit.Test; 17import org.junit.Test;
18 18
19import java.nio.file.Paths;
19import java.util.jar.JarFile; 20import java.util.jar.JarFile;
20 21
21import static cuchaz.enigma.TestEntryFactory.newClass; 22import static cuchaz.enigma.TestEntryFactory.newClass;
@@ -33,14 +34,14 @@ public class TestInnerClasses {
33 private static final ClassEntry ClassTreeLevel2 = newClass("f$a$a"); 34 private static final ClassEntry ClassTreeLevel2 = newClass("f$a$a");
34 private static final ClassEntry ClassTreeLevel3 = newClass("f$a$a$a"); 35 private static final ClassEntry ClassTreeLevel3 = newClass("f$a$a$a");
35 private JarIndex index; 36 private JarIndex index;
36 private Deobfuscator deobfuscator; 37 private Enigma enigma;
37 38
38 public TestInnerClasses() 39 public TestInnerClasses()
39 throws Exception { 40 throws Exception {
40 index = JarIndex.empty(); 41 ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/innerClasses.jar"));
41 ParsedJar jar = new ParsedJar(new JarFile("build/test-obf/innerClasses.jar")); 42 index = classCache.index(ProgressListener.none());
42 index.indexJar(jar, s -> {}); 43
43 deobfuscator = new Deobfuscator(jar); 44 enigma = new Enigma(jar);
44 } 45 }
45 46
46 @Test 47 @Test
@@ -79,6 +80,6 @@ public class TestInnerClasses {
79 } 80 }
80 81
81 private void decompile(ClassEntry classEntry) { 82 private void decompile(ClassEntry classEntry) {
82 deobfuscator.getObfSourceProvider().getSources(classEntry.getName()); 83 enigma.getObfSourceProvider().getSources(classEntry.getName());
83 } 84 }
84} 85}
diff --git a/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java b/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java
index c3f3b669..0712ccf5 100644
--- a/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java
+++ b/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java
@@ -12,7 +12,6 @@
12package cuchaz.enigma; 12package cuchaz.enigma;
13 13
14import cuchaz.enigma.analysis.EntryReference; 14import cuchaz.enigma.analysis.EntryReference;
15import cuchaz.enigma.analysis.ParsedJar;
16import cuchaz.enigma.analysis.index.JarIndex; 15import cuchaz.enigma.analysis.index.JarIndex;
17import cuchaz.enigma.translation.representation.entry.ClassEntry; 16import cuchaz.enigma.translation.representation.entry.ClassEntry;
18import cuchaz.enigma.translation.representation.entry.MethodDefEntry; 17import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
diff --git a/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java b/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java
index 36595a3b..76e379c3 100644
--- a/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java
+++ b/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java
@@ -11,8 +11,8 @@
11 11
12package cuchaz.enigma; 12package cuchaz.enigma;
13 13
14import cuchaz.enigma.analysis.ClassCache;
14import cuchaz.enigma.analysis.EntryReference; 15import cuchaz.enigma.analysis.EntryReference;
15import cuchaz.enigma.analysis.ParsedJar;
16import cuchaz.enigma.analysis.index.EntryIndex; 16import cuchaz.enigma.analysis.index.EntryIndex;
17import cuchaz.enigma.analysis.index.InheritanceIndex; 17import cuchaz.enigma.analysis.index.InheritanceIndex;
18import cuchaz.enigma.analysis.index.JarIndex; 18import cuchaz.enigma.analysis.index.JarIndex;
@@ -26,8 +26,8 @@ import cuchaz.enigma.translation.representation.entry.MethodEntry;
26import org.junit.Test; 26import org.junit.Test;
27import org.objectweb.asm.Opcodes; 27import org.objectweb.asm.Opcodes;
28 28
29import java.nio.file.Paths;
29import java.util.Collection; 30import java.util.Collection;
30import java.util.jar.JarFile;
31 31
32import static cuchaz.enigma.TestEntryFactory.*; 32import static cuchaz.enigma.TestEntryFactory.*;
33import static org.hamcrest.MatcherAssert.assertThat; 33import static org.hamcrest.MatcherAssert.assertThat;
@@ -46,8 +46,8 @@ public class TestJarIndexInheritanceTree {
46 46
47 public TestJarIndexInheritanceTree() 47 public TestJarIndexInheritanceTree()
48 throws Exception { 48 throws Exception {
49 index = JarIndex.empty(); 49 ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/inheritanceTree.jar"));
50 index.indexJar(new ParsedJar(new JarFile("build/test-obf/inheritanceTree.jar")), s -> {}); 50 index = classCache.index(ProgressListener.none());
51 } 51 }
52 52
53 @Test 53 @Test
diff --git a/src/test/java/cuchaz/enigma/TestSourceIndex.java b/src/test/java/cuchaz/enigma/TestSourceIndex.java
index ce5d6316..8a604f80 100644
--- a/src/test/java/cuchaz/enigma/TestSourceIndex.java
+++ b/src/test/java/cuchaz/enigma/TestSourceIndex.java
@@ -41,17 +41,17 @@ public class TestSourceIndex {
41 mcJar = new File(mcDir, "versions/1.8.3/1.8.3.jar"); 41 mcJar = new File(mcDir, "versions/1.8.3/1.8.3.jar");
42 } 42 }
43 43
44 Deobfuscator deobfuscator = new Deobfuscator(new JarFile(mcJar)); 44 Enigma enigma = new Enigma(new JarFile(mcJar));
45 45
46 // get all classes that aren't inner classes 46 // get all classes that aren't inner classes
47 Set<ClassEntry> classEntries = Sets.newHashSet(); 47 Set<ClassEntry> classEntries = Sets.newHashSet();
48 for (ClassEntry obfClassEntry : deobfuscator.getJarIndex().getEntryIndex().getClasses()) { 48 for (ClassEntry obfClassEntry : enigma.getJarIndex().getEntryIndex().getClasses()) {
49 if (!obfClassEntry.isInnerClass()) { 49 if (!obfClassEntry.isInnerClass()) {
50 classEntries.add(obfClassEntry); 50 classEntries.add(obfClassEntry);
51 } 51 }
52 } 52 }
53 53
54 SourceProvider sourceProvider = deobfuscator.getObfSourceProvider(); 54 SourceProvider sourceProvider = enigma.getObfSourceProvider();
55 for (ClassEntry obfClassEntry : classEntries) { 55 for (ClassEntry obfClassEntry : classEntries) {
56 try { 56 try {
57 CompilationUnit tree = sourceProvider.getSources(obfClassEntry.getName()); 57 CompilationUnit tree = sourceProvider.getSources(obfClassEntry.getName());
diff --git a/src/test/java/cuchaz/enigma/TestTranslator.java b/src/test/java/cuchaz/enigma/TestTranslator.java
index b9781297..a420afe1 100644
--- a/src/test/java/cuchaz/enigma/TestTranslator.java
+++ b/src/test/java/cuchaz/enigma/TestTranslator.java
@@ -23,7 +23,7 @@ public class TestTranslator {
23 public static void beforeClass() 23 public static void beforeClass()
24 throws Exception { 24 throws Exception {
25 //TODO FIx 25 //TODO FIx
26 //deobfuscator = new Deobfuscator(new JarFile("build/test-obf/translation.jar")); 26 //deobfuscator = new Enigma(new JarFile("build/test-obf/translation.jar"));
27 //try (InputStream in = TestTranslator.class.getResourceAsStream("/cuchaz/enigma/resources/translation.mappings")) { 27 //try (InputStream in = TestTranslator.class.getResourceAsStream("/cuchaz/enigma/resources/translation.mappings")) {
28 // mappings = new MappingsJsonReader().read(new InputStreamReader(in)); 28 // mappings = new MappingsJsonReader().read(new InputStreamReader(in));
29 // deobfuscator.setMappings(mappings); 29 // deobfuscator.setMappings(mappings);
diff --git a/src/test/java/cuchaz/enigma/TokenChecker.java b/src/test/java/cuchaz/enigma/TokenChecker.java
index c4670a20..9e0c696c 100644
--- a/src/test/java/cuchaz/enigma/TokenChecker.java
+++ b/src/test/java/cuchaz/enigma/TokenChecker.java
@@ -25,16 +25,16 @@ import java.util.jar.JarFile;
25 25
26public class TokenChecker { 26public class TokenChecker {
27 27
28 private Deobfuscator deobfuscator; 28 private Enigma enigma;
29 29
30 protected TokenChecker(JarFile jarFile) 30 protected TokenChecker(JarFile jarFile)
31 throws IOException { 31 throws IOException {
32 deobfuscator = new Deobfuscator(jarFile); 32 enigma = new Enigma(jarFile);
33 } 33 }
34 34
35 protected String getDeclarationToken(Entry<?> entry) { 35 protected String getDeclarationToken(Entry<?> entry) {
36 // decompile the class 36 // decompile the class
37 SourceProvider sourceProvider = deobfuscator.getObfSourceProvider(); 37 SourceProvider sourceProvider = enigma.getObfSourceProvider();
38 CompilationUnit tree = sourceProvider.getSources(entry.getContainingClass().getFullName()); 38 CompilationUnit tree = sourceProvider.getSources(entry.getContainingClass().getFullName());
39 // DEBUG 39 // DEBUG
40 // tree.acceptVisitor( new TreeDumpVisitor( new File( "tree." + entry.getClassName().replace( '/', '.' ) + ".txt" ) ), null ); 40 // tree.acceptVisitor( new TreeDumpVisitor( new File( "tree." + entry.getClassName().replace( '/', '.' ) + ".txt" ) ), null );
@@ -52,7 +52,7 @@ public class TokenChecker {
52 @SuppressWarnings("unchecked") 52 @SuppressWarnings("unchecked")
53 protected Collection<String> getReferenceTokens(EntryReference<? extends Entry<?>, ? extends Entry<?>> reference) { 53 protected Collection<String> getReferenceTokens(EntryReference<? extends Entry<?>, ? extends Entry<?>> reference) {
54 // decompile the class 54 // decompile the class
55 SourceProvider sourceProvider = deobfuscator.getObfSourceProvider(); 55 SourceProvider sourceProvider = enigma.getObfSourceProvider();
56 CompilationUnit tree = sourceProvider.getSources(reference.context.getContainingClass().getFullName()); 56 CompilationUnit tree = sourceProvider.getSources(reference.context.getContainingClass().getFullName());
57 String source = sourceProvider.writeSourceToString(tree); 57 String source = sourceProvider.writeSourceToString(tree);
58 SourceIndex index = SourceIndex.buildIndex(source, tree, true); 58 SourceIndex index = SourceIndex.buildIndex(source, tree, true);