summaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
authorGravatar Modmuss502019-06-19 18:51:31 +0100
committerGravatar GitHub2019-06-19 18:51:31 +0100
commit546c617598b10c341fe6549678803f6ac0c95619 (patch)
treed818bcebf7634ed5b716ee29619725fdc29a04e8 /src/main/java
parentfix unwanted declaration navigation during Quick Find (diff)
parentParse profile json from cli args (diff)
downloadenigma-fork-546c617598b10c341fe6549678803f6ac0c95619.tar.gz
enigma-fork-546c617598b10c341fe6549678803f6ac0c95619.tar.xz
enigma-fork-546c617598b10c341fe6549678803f6ac0c95619.zip
Merge pull request #135 from gegy1000/proposal-tweak
Plugin rework
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/cuchaz/enigma/Deobfuscator.java432
-rw-r--r--src/main/java/cuchaz/enigma/Enigma.java119
-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.java115
-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.java126
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ParsedJar.java129
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/JarIndex.java20
-rw-r--r--src/main/java/cuchaz/enigma/api/EnigmaPlugin.java17
-rw-r--r--src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java9
-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.java12
-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.java18
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassSelector.java4
-rw-r--r--src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java52
-rw-r--r--src/main/java/cuchaz/enigma/gui/Gui.java49
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiController.java351
-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.java40
-rw-r--r--src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java6
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java10
34 files changed, 1207 insertions, 862 deletions
diff --git a/src/main/java/cuchaz/enigma/Deobfuscator.java b/src/main/java/cuchaz/enigma/Deobfuscator.java
deleted file mode 100644
index b4736d8..0000000
--- a/src/main/java/cuchaz/enigma/Deobfuscator.java
+++ /dev/null
@@ -1,432 +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.strobel.assembler.metadata.ITypeLoader;
17import com.strobel.assembler.metadata.MetadataSystem;
18import com.strobel.assembler.metadata.TypeDefinition;
19import com.strobel.assembler.metadata.TypeReference;
20import com.strobel.decompiler.DecompilerSettings;
21import com.strobel.decompiler.languages.java.ast.CompilationUnit;
22import cuchaz.enigma.analysis.EntryReference;
23import cuchaz.enigma.analysis.IndexTreeBuilder;
24import cuchaz.enigma.analysis.ParsedJar;
25import cuchaz.enigma.analysis.index.JarIndex;
26import cuchaz.enigma.api.EnigmaPlugin;
27import cuchaz.enigma.bytecode.translators.SourceFixVisitor;
28import cuchaz.enigma.bytecode.translators.TranslationClassVisitor;
29import cuchaz.enigma.translation.Translatable;
30import cuchaz.enigma.translation.Translator;
31import cuchaz.enigma.translation.mapping.*;
32import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree;
33import cuchaz.enigma.translation.mapping.tree.EntryTree;
34import cuchaz.enigma.translation.representation.entry.ClassEntry;
35import cuchaz.enigma.translation.representation.entry.Entry;
36import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
37import cuchaz.enigma.translation.representation.entry.MethodEntry;
38import org.objectweb.asm.ClassVisitor;
39import org.objectweb.asm.ClassWriter;
40import org.objectweb.asm.Opcodes;
41import org.objectweb.asm.tree.ClassNode;
42
43import java.io.File;
44import java.io.FileOutputStream;
45import java.io.IOException;
46import java.io.Writer;
47import java.nio.file.Files;
48import java.nio.file.Path;
49import java.util.*;
50import java.util.concurrent.ConcurrentHashMap;
51import java.util.concurrent.atomic.AtomicInteger;
52import java.util.function.Consumer;
53import java.util.jar.JarEntry;
54import java.util.jar.JarFile;
55import java.util.jar.JarOutputStream;
56import java.util.stream.Collectors;
57
58public class Deobfuscator {
59
60 private final ServiceLoader<EnigmaPlugin> plugins = ServiceLoader.load(EnigmaPlugin.class);
61 private final ParsedJar parsedJar;
62 private final JarIndex jarIndex;
63 private final IndexTreeBuilder indexTreeBuilder;
64
65 private final SourceProvider obfSourceProvider;
66
67 private EntryRemapper mapper;
68
69 public Deobfuscator(ParsedJar jar, Consumer<String> listener) {
70 this.parsedJar = jar;
71
72 // build the jar index
73 this.jarIndex = JarIndex.empty();
74 this.jarIndex.indexJar(this.parsedJar, listener);
75
76 listener.accept("Initializing plugins...");
77 for (EnigmaPlugin plugin : getPlugins()) {
78 plugin.onClassesLoaded(parsedJar.getClassDataMap(), parsedJar::getClassNode);
79 }
80
81 this.indexTreeBuilder = new IndexTreeBuilder(jarIndex);
82
83 listener.accept("Preparing...");
84
85 CompiledSourceTypeLoader typeLoader = new CompiledSourceTypeLoader(parsedJar);
86 typeLoader.addVisitor(visitor -> new SourceFixVisitor(Opcodes.ASM5, visitor, jarIndex));
87
88 this.obfSourceProvider = new SourceProvider(SourceProvider.createSettings(), typeLoader);
89
90 // init mappings
91 mapper = new EntryRemapper(jarIndex);
92 }
93
94 public Deobfuscator(JarFile jar, Consumer<String> listener) throws IOException {
95 this(new ParsedJar(jar), listener);
96 }
97
98 public Deobfuscator(ParsedJar jar) {
99 this(jar, (msg) -> {
100 });
101 }
102
103 public Deobfuscator(JarFile jar) throws IOException {
104 this(jar, (msg) -> {
105 });
106 }
107
108 public ServiceLoader<EnigmaPlugin> getPlugins() {
109 return plugins;
110 }
111
112 public ParsedJar getJar() {
113 return this.parsedJar;
114 }
115
116 public JarIndex getJarIndex() {
117 return this.jarIndex;
118 }
119
120 public IndexTreeBuilder getIndexTreeBuilder() {
121 return indexTreeBuilder;
122 }
123
124 public EntryRemapper getMapper() {
125 return this.mapper;
126 }
127
128 public void setMappings(EntryTree<EntryMapping> mappings) {
129 setMappings(mappings, ProgressListener.VOID);
130 }
131
132 public void setMappings(EntryTree<EntryMapping> mappings, ProgressListener progress) {
133 if (mappings != null) {
134 Collection<Entry<?>> dropped = dropMappings(mappings, progress);
135 mapper = new EntryRemapper(jarIndex, mappings);
136
137 DeltaTrackingTree<EntryMapping> obfToDeobf = mapper.getObfToDeobf();
138 for (Entry<?> entry : dropped) {
139 obfToDeobf.trackChange(entry);
140 }
141 } else {
142 mapper = new EntryRemapper(jarIndex);
143 }
144 }
145
146 private Collection<Entry<?>> dropMappings(EntryTree<EntryMapping> mappings, ProgressListener progress) {
147 // drop mappings that don't match the jar
148 MappingsChecker checker = new MappingsChecker(jarIndex, mappings);
149 MappingsChecker.Dropped dropped = checker.dropBrokenMappings(progress);
150
151 Map<Entry<?>, String> droppedMappings = dropped.getDroppedMappings();
152 for (Map.Entry<Entry<?>, String> mapping : droppedMappings.entrySet()) {
153 System.out.println("WARNING: Couldn't find " + mapping.getKey() + " (" + mapping.getValue() + ") in jar. Mapping was dropped.");
154 }
155
156 return droppedMappings.keySet();
157 }
158
159 public void getSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) {
160 for (ClassEntry obfClassEntry : this.jarIndex.getEntryIndex().getClasses()) {
161 // skip inner classes
162 if (obfClassEntry.isInnerClass()) {
163 continue;
164 }
165
166 // separate the classes
167 ClassEntry deobfClassEntry = mapper.deobfuscate(obfClassEntry);
168 if (!deobfClassEntry.equals(obfClassEntry)) {
169 // if the class has a mapping, clearly it's deobfuscated
170 deobfClasses.add(obfClassEntry);
171 } else if (obfClassEntry.getPackageName() != null) {
172 // also call it deobufscated if it's not in the none package
173 deobfClasses.add(obfClassEntry);
174 } else {
175 // otherwise, assume it's still obfuscated
176 obfClasses.add(obfClassEntry);
177 }
178 }
179 }
180
181 public SourceProvider getObfSourceProvider() {
182 return obfSourceProvider;
183 }
184
185 public void writeSources(Path outputDirectory, ProgressListener progress) {
186 // get the classes to decompile
187 Collection<ClassEntry> classEntries = jarIndex.getEntryIndex().getClasses();
188
189 Stopwatch stopwatch = Stopwatch.createStarted();
190
191 try {
192 Translator deobfuscator = mapper.getDeobfuscator();
193
194 // deobfuscate everything first
195 Map<String, ClassNode> translatedNodes = deobfuscateClasses(progress, classEntries, deobfuscator);
196
197 decompileClasses(outputDirectory, progress, translatedNodes);
198 } finally {
199 stopwatch.stop();
200
201 System.out.println("writeSources Done in : " + stopwatch.toString());
202 }
203 }
204
205 private Map<String, ClassNode> deobfuscateClasses(ProgressListener progress, Collection<ClassEntry> classEntries, Translator translator) {
206 AtomicInteger count = new AtomicInteger();
207 if (progress != null) {
208 progress.init(classEntries.size(), "Deobfuscating classes...");
209 }
210
211 return classEntries.parallelStream()
212 .map(entry -> {
213 ClassEntry translatedEntry = translator.translate(entry);
214 if (progress != null) {
215 progress.step(count.getAndIncrement(), translatedEntry.toString());
216 }
217
218 ClassNode node = parsedJar.getClassNode(entry.getFullName());
219 if (node != null) {
220 ClassNode translatedNode = new ClassNode();
221 node.accept(new TranslationClassVisitor(translator, Opcodes.ASM5, translatedNode));
222 return translatedNode;
223 }
224
225 return null;
226 })
227 .filter(Objects::nonNull)
228 .collect(Collectors.toMap(n -> n.name, Functions.identity()));
229 }
230
231 private void decompileClasses(Path outputDirectory, ProgressListener progress, Map<String, ClassNode> translatedClasses) {
232 Collection<ClassNode> decompileClasses = translatedClasses.values().stream()
233 .filter(classNode -> classNode.name.indexOf('$') == -1)
234 .collect(Collectors.toList());
235
236 if (progress != null) {
237 progress.init(decompileClasses.size(), "Decompiling classes...");
238 }
239
240 //create a common instance outside the loop as mappings shouldn't be changing while this is happening
241 CompiledSourceTypeLoader typeLoader = new CompiledSourceTypeLoader(translatedClasses::get);
242 typeLoader.addVisitor(visitor -> new SourceFixVisitor(Opcodes.ASM5, visitor, jarIndex));
243
244 //synchronized to make sure the parallelStream doesn't CME with the cache
245 ITypeLoader synchronizedTypeLoader = new SynchronizedTypeLoader(typeLoader);
246
247 MetadataSystem metadataSystem = new Deobfuscator.NoRetryMetadataSystem(synchronizedTypeLoader);
248
249 //ensures methods are loaded on classload and prevents race conditions
250 metadataSystem.setEagerMethodLoadingEnabled(true);
251
252 DecompilerSettings settings = SourceProvider.createSettings();
253 SourceProvider sourceProvider = new SourceProvider(settings, synchronizedTypeLoader, metadataSystem);
254
255 AtomicInteger count = new AtomicInteger();
256
257 decompileClasses.parallelStream().forEach(translatedNode -> {
258 if (progress != null) {
259 progress.step(count.getAndIncrement(), translatedNode.name);
260 }
261
262 decompileClass(outputDirectory, translatedNode, sourceProvider);
263 });
264 }
265
266 private void decompileClass(Path outputDirectory, ClassNode translatedNode, SourceProvider sourceProvider) {
267 try {
268 // get the source
269 CompilationUnit sourceTree = sourceProvider.getSources(translatedNode.name);
270
271 Path path = outputDirectory.resolve(translatedNode.name.replace('.', '/') + ".java");
272 Files.createDirectories(path.getParent());
273
274 try (Writer writer = Files.newBufferedWriter(path)) {
275 sourceProvider.writeSource(writer, sourceTree);
276 }
277 } catch (Throwable t) {
278 // don't crash the whole world here, just log the error and keep going
279 // TODO: set up logback via log4j
280 System.err.println("Unable to decompile class " + translatedNode.name);
281 t.printStackTrace(System.err);
282 }
283 }
284
285 public void writeTransformedJar(File out, ProgressListener progress) {
286 Translator deobfuscator = mapper.getDeobfuscator();
287 writeTransformedJar(out, progress, (node, visitor) -> {
288 ClassEntry entry = new ClassEntry(node.name);
289 node.accept(new TranslationClassVisitor(deobfuscator, Opcodes.ASM5, visitor));
290 return deobfuscator.translate(entry).getFullName();
291 });
292 }
293
294 public void writeTransformedJar(File out, ProgressListener progress, ClassTransformer transformer) {
295 try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) {
296 if (progress != null) {
297 progress.init(parsedJar.getClassCount(), "Transforming classes...");
298 }
299
300 AtomicInteger count = new AtomicInteger();
301 parsedJar.visitNode(node -> {
302 if (progress != null) {
303 progress.step(count.getAndIncrement(), node.name);
304 }
305
306 try {
307 ClassWriter writer = new ClassWriter(0);
308 String transformedName = transformer.transform(node, writer);
309 outJar.putNextEntry(new JarEntry(transformedName.replace('.', '/') + ".class"));
310 outJar.write(writer.toByteArray());
311 outJar.closeEntry();
312 } catch (Throwable t) {
313 throw new Error("Unable to transform class " + node.name, t);
314 }
315 });
316 } catch (IOException ex) {
317 throw new Error("Unable to write to Jar file!");
318 }
319 }
320
321 public AccessModifier getModifier(Entry<?> entry) {
322 EntryMapping mapping = mapper.getDeobfMapping(entry);
323 if (mapping == null) {
324 return AccessModifier.UNCHANGED;
325 }
326 return mapping.getAccessModifier();
327 }
328
329 public void changeModifier(Entry<?> entry, AccessModifier modifier) {
330 EntryMapping mapping = mapper.getDeobfMapping(entry);
331 if (mapping != null) {
332 mapper.mapFromObf(entry, new EntryMapping(mapping.getTargetName(), modifier));
333 } else {
334 mapper.mapFromObf(entry, new EntryMapping(entry.getName(), modifier));
335 }
336 }
337
338 public boolean isRenamable(Entry<?> obfEntry) {
339 if (obfEntry instanceof MethodEntry) {
340 // HACKHACK: Object methods are not obfuscated identifiers
341 MethodEntry obfMethodEntry = (MethodEntry) obfEntry;
342 String name = obfMethodEntry.getName();
343 String sig = obfMethodEntry.getDesc().toString();
344 if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) {
345 return false;
346 } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) {
347 return false;
348 } else if (name.equals("finalize") && sig.equals("()V")) {
349 return false;
350 } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) {
351 return false;
352 } else if (name.equals("hashCode") && sig.equals("()I")) {
353 return false;
354 } else if (name.equals("notify") && sig.equals("()V")) {
355 return false;
356 } else if (name.equals("notifyAll") && sig.equals("()V")) {
357 return false;
358 } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) {
359 return false;
360 } else if (name.equals("wait") && sig.equals("()V")) {
361 return false;
362 } else if (name.equals("wait") && sig.equals("(J)V")) {
363 return false;
364 } else if (name.equals("wait") && sig.equals("(JI)V")) {
365 return false;
366 }
367 } else if (obfEntry instanceof LocalVariableEntry && !((LocalVariableEntry) obfEntry).isArgument()) {
368 return false;
369 }
370
371 return this.jarIndex.getEntryIndex().hasEntry(obfEntry);
372 }
373
374 public boolean isRenamable(EntryReference<Entry<?>, Entry<?>> obfReference) {
375 return obfReference.isNamed() && isRenamable(obfReference.getNameableEntry());
376 }
377
378 public boolean isRemapped(Entry<?> entry) {
379 EntryResolver resolver = mapper.getObfResolver();
380 DeltaTrackingTree<EntryMapping> mappings = mapper.getObfToDeobf();
381 return resolver.resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT).stream()
382 .anyMatch(mappings::contains);
383 }
384
385 public void rename(Entry<?> obfEntry, String newName) {
386 mapper.mapFromObf(obfEntry, new EntryMapping(newName));
387 }
388
389 public void removeMapping(Entry<?> obfEntry) {
390 mapper.removeByObf(obfEntry);
391 }
392
393 public void markAsDeobfuscated(Entry<?> obfEntry) {
394 mapper.mapFromObf(obfEntry, new EntryMapping(mapper.deobfuscate(obfEntry).getName()));
395 }
396
397 public <T extends Translatable> T deobfuscate(T translatable) {
398 return mapper.deobfuscate(translatable);
399 }
400
401 public interface ClassTransformer {
402 String transform(ClassNode node, ClassVisitor visitor);
403 }
404
405 public static class NoRetryMetadataSystem extends MetadataSystem {
406 private final Set<String> _failedTypes = Collections.newSetFromMap(new ConcurrentHashMap<>());
407
408 public NoRetryMetadataSystem(final ITypeLoader typeLoader) {
409 super(typeLoader);
410 }
411
412 @Override
413 protected synchronized TypeDefinition resolveType(final String descriptor, final boolean mightBePrimitive) {
414 if (_failedTypes.contains(descriptor)) {
415 return null;
416 }
417
418 final TypeDefinition result = super.resolveType(descriptor, mightBePrimitive);
419
420 if (result == null) {
421 _failedTypes.add(descriptor);
422 }
423
424 return result;
425 }
426
427 @Override
428 public synchronized TypeDefinition resolve(final TypeReference type) {
429 return super.resolve(type);
430 }
431 }
432}
diff --git a/src/main/java/cuchaz/enigma/Enigma.java b/src/main/java/cuchaz/enigma/Enigma.java
new file mode 100644
index 0000000..fd23b47
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/Enigma.java
@@ -0,0 +1,119 @@
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;
23import cuchaz.enigma.api.service.JarIndexerService;
24
25import java.io.IOException;
26import java.nio.file.Path;
27import java.util.ServiceLoader;
28
29public class Enigma {
30 private final EnigmaProfile profile;
31 private final EnigmaServices services;
32
33 private Enigma(EnigmaProfile profile, EnigmaServices services) {
34 this.profile = profile;
35 this.services = services;
36 }
37
38 public static Enigma create() {
39 return new Builder().build();
40 }
41
42 public static Builder builder() {
43 return new Builder();
44 }
45
46 public EnigmaProject openJar(Path path, ProgressListener progress) throws IOException {
47 ClassCache classCache = ClassCache.of(path);
48 JarIndex jarIndex = classCache.index(progress);
49
50 services.get(JarIndexerService.TYPE).ifPresent(indexer -> {
51 indexer.acceptJar(classCache, jarIndex);
52 });
53
54 return new EnigmaProject(this, classCache, jarIndex);
55 }
56
57 public EnigmaProfile getProfile() {
58 return profile;
59 }
60
61 public EnigmaServices getServices() {
62 return services;
63 }
64
65 public static class Builder {
66 private EnigmaProfile profile = EnigmaProfile.EMPTY;
67 private Iterable<EnigmaPlugin> plugins = ServiceLoader.load(EnigmaPlugin.class);
68
69 private Builder() {
70 }
71
72 public Builder setProfile(EnigmaProfile profile) {
73 Preconditions.checkNotNull(profile, "profile cannot be null");
74 this.profile = profile;
75 return this;
76 }
77
78 public Builder setPlugins(Iterable<EnigmaPlugin> plugins) {
79 Preconditions.checkNotNull(plugins, "plugins cannot be null");
80 this.plugins = plugins;
81 return this;
82 }
83
84 public Enigma build() {
85 PluginContext pluginContext = new PluginContext(profile);
86 for (EnigmaPlugin plugin : plugins) {
87 plugin.init(pluginContext);
88 }
89
90 EnigmaServices services = pluginContext.buildServices();
91 return new Enigma(profile, services);
92 }
93 }
94
95 private static class PluginContext implements EnigmaPluginContext {
96 private final EnigmaProfile profile;
97
98 private final ImmutableMap.Builder<EnigmaServiceType<?>, EnigmaService> services = ImmutableMap.builder();
99
100 PluginContext(EnigmaProfile profile) {
101 this.profile = profile;
102 }
103
104 @Override
105 public <T extends EnigmaService> void registerService(String id, EnigmaServiceType<T> serviceType, EnigmaServiceFactory<T> factory) {
106 EnigmaProfile.Service serviceProfile = profile.getServiceProfile(serviceType);
107
108 // if this service type is not configured, or it is configured to use a different service id, skip
109 if (serviceProfile == null || !serviceProfile.matches(id)) return;
110
111 T service = factory.create(serviceProfile::getArgument);
112 services.put(serviceType, service);
113 }
114
115 EnigmaServices buildServices() {
116 return new EnigmaServices(services.build());
117 }
118 }
119}
diff --git a/src/main/java/cuchaz/enigma/EnigmaProfile.java b/src/main/java/cuchaz/enigma/EnigmaProfile.java
new file mode 100644
index 0000000..9dc5ff2
--- /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 0000000..bb2f7fa
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/EnigmaProject.java
@@ -0,0 +1,285 @@
1package cuchaz.enigma;
2
3import com.google.common.base.Functions;
4import com.strobel.assembler.metadata.ITypeLoader;
5import com.strobel.assembler.metadata.MetadataSystem;
6import com.strobel.decompiler.DecompilerSettings;
7import com.strobel.decompiler.languages.java.ast.CompilationUnit;
8import cuchaz.enigma.analysis.ClassCache;
9import cuchaz.enigma.analysis.EntryReference;
10import cuchaz.enigma.analysis.index.JarIndex;
11import cuchaz.enigma.bytecode.translators.SourceFixVisitor;
12import cuchaz.enigma.bytecode.translators.TranslationClassVisitor;
13import cuchaz.enigma.translation.Translator;
14import cuchaz.enigma.translation.mapping.EntryMapping;
15import cuchaz.enigma.translation.mapping.EntryRemapper;
16import cuchaz.enigma.translation.mapping.MappingsChecker;
17import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree;
18import cuchaz.enigma.translation.mapping.tree.EntryTree;
19import cuchaz.enigma.translation.representation.entry.ClassEntry;
20import cuchaz.enigma.translation.representation.entry.Entry;
21import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
22import cuchaz.enigma.translation.representation.entry.MethodEntry;
23import org.objectweb.asm.ClassWriter;
24import org.objectweb.asm.Opcodes;
25import org.objectweb.asm.tree.ClassNode;
26
27import java.io.BufferedWriter;
28import java.io.IOException;
29import java.io.StringWriter;
30import java.nio.file.Files;
31import java.nio.file.Path;
32import java.util.Collection;
33import java.util.Map;
34import java.util.Objects;
35import java.util.concurrent.atomic.AtomicInteger;
36import java.util.jar.JarEntry;
37import java.util.jar.JarOutputStream;
38import java.util.stream.Collectors;
39
40public class EnigmaProject {
41 private final Enigma enigma;
42
43 private final ClassCache classCache;
44 private final JarIndex jarIndex;
45
46 private EntryRemapper mapper;
47
48 public EnigmaProject(Enigma enigma, ClassCache classCache, JarIndex jarIndex) {
49 this.enigma = enigma;
50 this.classCache = classCache;
51 this.jarIndex = jarIndex;
52
53 this.mapper = EntryRemapper.empty(jarIndex);
54 }
55
56 public void setMappings(EntryTree<EntryMapping> mappings) {
57 if (mappings != null) {
58 mapper = EntryRemapper.mapped(jarIndex, mappings);
59 } else {
60 mapper = EntryRemapper.empty(jarIndex);
61 }
62 }
63
64 public Enigma getEnigma() {
65 return enigma;
66 }
67
68 public ClassCache getClassCache() {
69 return classCache;
70 }
71
72 public JarIndex getJarIndex() {
73 return jarIndex;
74 }
75
76 public EntryRemapper getMapper() {
77 return mapper;
78 }
79
80 public void dropMappings(ProgressListener progress) {
81 DeltaTrackingTree<EntryMapping> mappings = mapper.getObfToDeobf();
82
83 Collection<Entry<?>> dropped = dropMappings(mappings, progress);
84 for (Entry<?> entry : dropped) {
85 mappings.trackChange(entry);
86 }
87 }
88
89 private Collection<Entry<?>> dropMappings(EntryTree<EntryMapping> mappings, ProgressListener progress) {
90 // drop mappings that don't match the jar
91 MappingsChecker checker = new MappingsChecker(jarIndex, mappings);
92 MappingsChecker.Dropped dropped = checker.dropBrokenMappings(progress);
93
94 Map<Entry<?>, String> droppedMappings = dropped.getDroppedMappings();
95 for (Map.Entry<Entry<?>, String> mapping : droppedMappings.entrySet()) {
96 System.out.println("WARNING: Couldn't find " + mapping.getKey() + " (" + mapping.getValue() + ") in jar. Mapping was dropped.");
97 }
98
99 return droppedMappings.keySet();
100 }
101
102 public boolean isRenamable(Entry<?> obfEntry) {
103 if (obfEntry instanceof MethodEntry) {
104 // HACKHACK: Object methods are not obfuscated identifiers
105 MethodEntry obfMethodEntry = (MethodEntry) obfEntry;
106 String name = obfMethodEntry.getName();
107 String sig = obfMethodEntry.getDesc().toString();
108 if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) {
109 return false;
110 } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) {
111 return false;
112 } else if (name.equals("finalize") && sig.equals("()V")) {
113 return false;
114 } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) {
115 return false;
116 } else if (name.equals("hashCode") && sig.equals("()I")) {
117 return false;
118 } else if (name.equals("notify") && sig.equals("()V")) {
119 return false;
120 } else if (name.equals("notifyAll") && sig.equals("()V")) {
121 return false;
122 } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) {
123 return false;
124 } else if (name.equals("wait") && sig.equals("()V")) {
125 return false;
126 } else if (name.equals("wait") && sig.equals("(J)V")) {
127 return false;
128 } else if (name.equals("wait") && sig.equals("(JI)V")) {
129 return false;
130 }
131 } else if (obfEntry instanceof LocalVariableEntry && !((LocalVariableEntry) obfEntry).isArgument()) {
132 return false;
133 }
134
135 return this.jarIndex.getEntryIndex().hasEntry(obfEntry);
136 }
137
138 public boolean isRenamable(EntryReference<Entry<?>, Entry<?>> obfReference) {
139 return obfReference.isNamed() && isRenamable(obfReference.getNameableEntry());
140 }
141
142 public JarExport exportRemappedJar(ProgressListener progress) {
143 Collection<ClassEntry> classEntries = jarIndex.getEntryIndex().getClasses();
144 Translator deobfuscator = mapper.getDeobfuscator();
145
146 AtomicInteger count = new AtomicInteger();
147 progress.init(classEntries.size(), "Deobfuscating classes...");
148
149 Map<String, ClassNode> compiled = classEntries.parallelStream()
150 .map(entry -> {
151 ClassEntry translatedEntry = deobfuscator.translate(entry);
152 progress.step(count.getAndIncrement(), translatedEntry.toString());
153
154 ClassNode node = classCache.getClassNode(entry.getFullName());
155 if (node != null) {
156 ClassNode translatedNode = new ClassNode();
157 node.accept(new TranslationClassVisitor(deobfuscator, Opcodes.ASM5, translatedNode));
158 return translatedNode;
159 }
160
161 return null;
162 })
163 .filter(Objects::nonNull)
164 .collect(Collectors.toMap(n -> n.name, Functions.identity()));
165
166 return new JarExport(jarIndex, compiled);
167 }
168
169 public static final class JarExport {
170 private final JarIndex jarIndex;
171 private final Map<String, ClassNode> compiled;
172
173 JarExport(JarIndex jarIndex, Map<String, ClassNode> compiled) {
174 this.jarIndex = jarIndex;
175 this.compiled = compiled;
176 }
177
178 public void write(Path path, ProgressListener progress) throws IOException {
179 progress.init(this.compiled.size(), "Writing jar...");
180
181 try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(path))) {
182 AtomicInteger count = new AtomicInteger();
183
184 for (ClassNode node : this.compiled.values()) {
185 progress.step(count.getAndIncrement(), node.name);
186
187 String entryName = node.name.replace('.', '/') + ".class";
188
189 ClassWriter writer = new ClassWriter(0);
190 node.accept(writer);
191
192 out.putNextEntry(new JarEntry(entryName));
193 out.write(writer.toByteArray());
194 out.closeEntry();
195 }
196 }
197 }
198
199 public SourceExport decompile(ProgressListener progress) {
200 Collection<ClassNode> classes = this.compiled.values().stream()
201 .filter(classNode -> classNode.name.indexOf('$') == -1)
202 .collect(Collectors.toList());
203
204 progress.init(classes.size(), "Decompiling classes...");
205
206 //create a common instance outside the loop as mappings shouldn't be changing while this is happening
207 CompiledSourceTypeLoader typeLoader = new CompiledSourceTypeLoader(this.compiled::get);
208 typeLoader.addVisitor(visitor -> new SourceFixVisitor(Opcodes.ASM5, visitor, jarIndex));
209
210 //synchronized to make sure the parallelStream doesn't CME with the cache
211 ITypeLoader synchronizedTypeLoader = new SynchronizedTypeLoader(typeLoader);
212
213 MetadataSystem metadataSystem = new NoRetryMetadataSystem(synchronizedTypeLoader);
214
215 //ensures methods are loaded on classload and prevents race conditions
216 metadataSystem.setEagerMethodLoadingEnabled(true);
217
218 DecompilerSettings settings = SourceProvider.createSettings();
219 SourceProvider sourceProvider = new SourceProvider(settings, synchronizedTypeLoader, metadataSystem);
220
221 AtomicInteger count = new AtomicInteger();
222
223 Collection<ClassSource> decompiled = classes.parallelStream()
224 .map(translatedNode -> {
225 progress.step(count.getAndIncrement(), translatedNode.name);
226
227 String source = decompileClass(translatedNode, sourceProvider);
228 return new ClassSource(translatedNode.name, source);
229 })
230 .collect(Collectors.toList());
231
232 return new SourceExport(decompiled);
233 }
234
235 private String decompileClass(ClassNode translatedNode, SourceProvider sourceProvider) {
236 CompilationUnit sourceTree = sourceProvider.getSources(translatedNode.name);
237
238 StringWriter writer = new StringWriter();
239 sourceProvider.writeSource(writer, sourceTree);
240
241 return writer.toString();
242 }
243 }
244
245 public static final class SourceExport {
246 private final Collection<ClassSource> decompiled;
247
248 SourceExport(Collection<ClassSource> decompiled) {
249 this.decompiled = decompiled;
250 }
251
252 public void write(Path path, ProgressListener progress) throws IOException {
253 progress.init(decompiled.size(), "Writing sources...");
254
255 int count = 0;
256 for (ClassSource source : decompiled) {
257 progress.step(count++, source.name);
258
259 Path sourcePath = source.resolvePath(path);
260 source.writeTo(sourcePath);
261 }
262 }
263 }
264
265 private static class ClassSource {
266 private final String name;
267 private final String source;
268
269 ClassSource(String name, String source) {
270 this.name = name;
271 this.source = source;
272 }
273
274 void writeTo(Path path) throws IOException {
275 Files.createDirectories(path.getParent());
276 try (BufferedWriter writer = Files.newBufferedWriter(path)) {
277 writer.write(source);
278 }
279 }
280
281 Path resolvePath(Path root) {
282 return root.resolve(name.replace('.', '/') + ".java");
283 }
284 }
285}
diff --git a/src/main/java/cuchaz/enigma/EnigmaServices.java b/src/main/java/cuchaz/enigma/EnigmaServices.java
new file mode 100644
index 0000000..86507bc
--- /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 ccfc51f..1f2cb84 100644
--- a/src/main/java/cuchaz/enigma/Main.java
+++ b/src/main/java/cuchaz/enigma/Main.java
@@ -12,49 +12,106 @@
12package cuchaz.enigma; 12package cuchaz.enigma;
13 13
14import cuchaz.enigma.gui.Gui; 14import cuchaz.enigma.gui.Gui;
15import cuchaz.enigma.gui.GuiController;
15import cuchaz.enigma.translation.mapping.serde.MappingFormat; 16import cuchaz.enigma.translation.mapping.serde.MappingFormat;
17import joptsimple.*;
16 18
17import java.io.File; 19import java.io.BufferedReader;
20import java.io.IOException;
18import java.nio.file.Files; 21import java.nio.file.Files;
19import java.nio.file.Path; 22import java.nio.file.Path;
20import java.util.jar.JarFile; 23import java.nio.file.Paths;
21 24
22public class Main { 25public class Main {
23 26
24 public static void main(String[] args) throws Exception { 27 public static void main(String[] args) throws IOException {
25 Gui gui = new Gui(); 28 OptionParser parser = new OptionParser();
26 29
27 // parse command-line args 30 OptionSpec<Path> jar = parser.accepts("jar", "Jar file to open at startup")
28 if (args.length >= 1) { 31 .withRequiredArg()
29 gui.getController().openJar(new JarFile(getFile(args[0]))); 32 .withValuesConvertedBy(PathConverter.INSTANCE);
30 } 33
31 if (args.length >= 2) { 34 OptionSpec<Path> mappings = parser.accepts("mappings", "Mappings file to open at startup")
32 Path mappingsFile = getFile(args[1]).toPath(); 35 .withRequiredArg()
33 if (Files.isDirectory(mappingsFile)) { 36 .withValuesConvertedBy(PathConverter.INSTANCE);
34 gui.getController().openMappings(MappingFormat.ENIGMA_DIRECTORY, mappingsFile); 37
35 } else { 38 OptionSpec<Path> profile = parser.accepts("profile", "Profile json to apply at startup")
36 gui.getController().openMappings(MappingFormat.ENIGMA_FILE, mappingsFile); 39 .withRequiredArg()
40 .withValuesConvertedBy(PathConverter.INSTANCE);
41
42 parser.accepts("help", "Displays help information");
43
44 try {
45 OptionSet options = parser.parse(args);
46
47 if (options.has("help")) {
48 parser.printHelpOn(System.out);
49 return;
50 }
51
52 EnigmaProfile parsedProfile = EnigmaProfile.EMPTY;
53 if (options.has(profile)) {
54 Path profilePath = options.valueOf(profile);
55 try (BufferedReader reader = Files.newBufferedReader(profilePath)) {
56 parsedProfile = EnigmaProfile.parse(reader);
57 }
58 }
59
60 Gui gui = new Gui(parsedProfile);
61 GuiController controller = gui.getController();
62
63 if (options.has(jar)) {
64 Path jarPath = options.valueOf(jar);
65 controller.openJar(jarPath);
37 } 66 }
38 }
39 67
40 // DEBUG 68 if (options.has(mappings)) {
41 //gui.getController().openDeclaration(new ClassEntry("none/byp")); 69 Path mappingsPath = options.valueOf(mappings);
70 if (Files.isDirectory(mappingsPath)) {
71 controller.openMappings(MappingFormat.ENIGMA_DIRECTORY, mappingsPath);
72 } else {
73 controller.openMappings(MappingFormat.ENIGMA_FILE, mappingsPath);
74 }
75 }
76 } catch (OptionException e) {
77 System.out.println("Invalid arguments: " + e.getMessage());
78 System.out.println();
79 parser.printHelpOn(System.out);
80 }
42 } 81 }
43 82
44 private static File getFile(String path) { 83 private static class PathConverter implements ValueConverter<Path> {
45 // expand ~ to the home dir 84 static final ValueConverter<Path> INSTANCE = new PathConverter();
46 if (path.startsWith("~")) { 85
47 // get the home dir 86 PathConverter() {
48 File dirHome = new File(System.getProperty("user.home")); 87 }
49 88
50 // is the path just ~/ or is it ~user/ ? 89 @Override
51 if (path.startsWith("~/")) { 90 public Path convert(String path) {
52 return new File(dirHome, path.substring(2)); 91 // expand ~ to the home dir
53 } else { 92 if (path.startsWith("~")) {
54 return new File(dirHome.getParentFile(), path.substring(1)); 93 // get the home dir
94 Path dirHome = Paths.get(System.getProperty("user.home"));
95
96 // is the path just ~/ or is it ~user/ ?
97 if (path.startsWith("~/")) {
98 return dirHome.resolve(path.substring(2));
99 } else {
100 return dirHome.getParent().resolve(path.substring(1));
101 }
55 } 102 }
103
104 return Paths.get(path);
56 } 105 }
57 106
58 return new File(path); 107 @Override
108 public Class<? extends Path> valueType() {
109 return Path.class;
110 }
111
112 @Override
113 public String valuePattern() {
114 return "path";
115 }
59 } 116 }
60} 117}
diff --git a/src/main/java/cuchaz/enigma/NoRetryMetadataSystem.java b/src/main/java/cuchaz/enigma/NoRetryMetadataSystem.java
new file mode 100644
index 0000000..269d31e
--- /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 ffce297..6da3b81 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 48e5a59..662f1f9 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 0000000..6c8af1c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/ClassCache.java
@@ -0,0 +1,126 @@
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 byte[] bytes = Files.readAllBytes(path);
89 return new ClassReader(bytes);
90 }
91
92 public int getClassCount() {
93 return classNames.size();
94 }
95
96 public void visit(Supplier<ClassVisitor> visitorSupplier, int readFlags) {
97 for (String className : classNames) {
98 ClassVisitor visitor = visitorSupplier.get();
99
100 ClassNode cached = nodeCache.getIfPresent(className);
101 if (cached != null) {
102 cached.accept(visitor);
103 continue;
104 }
105
106 try {
107 ClassReader reader = getReader(className);
108 reader.accept(visitor, readFlags);
109 } catch (IOException e) {
110 System.out.println("Failed to visit class " + className);
111 e.printStackTrace();
112 }
113 }
114 }
115
116 @Override
117 public void close() throws IOException {
118 this.fileSystem.close();
119 }
120
121 public JarIndex index(ProgressListener progress) {
122 JarIndex index = JarIndex.empty();
123 index.indexJar(this, progress);
124 return index;
125 }
126}
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 ddcda3e..0000000
--- 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 fd4e618..763282b 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,17 +56,19 @@ 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
diff --git a/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java b/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java
index 3efe0dc..bdd6015 100644
--- a/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java
+++ b/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java
@@ -1,18 +1,5 @@
1package cuchaz.enigma.api; 1package cuchaz.enigma.api;
2 2
3import org.objectweb.asm.tree.ClassNode; 3public interface EnigmaPlugin {
4 4 void init(EnigmaPluginContext ctx);
5import javax.annotation.Nullable;
6import java.util.Map;
7import java.util.function.Function;
8
9public abstract class EnigmaPlugin {
10 public void onClassesLoaded(Map<String, byte[]> classData, Function<String, ClassNode> classNodeGetter) {
11
12 }
13
14 @Nullable
15 public String proposeFieldName(String owner, String name, String desc) {
16 return null;
17 }
18} 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 0000000..a59051a
--- /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/service/EnigmaService.java b/src/main/java/cuchaz/enigma/api/service/EnigmaService.java
new file mode 100644
index 0000000..526dda7
--- /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 0000000..9e433fb
--- /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 0000000..7c10ac2
--- /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 0000000..358828f
--- /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 0000000..0cda199
--- /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/service/NameProposalService.java b/src/main/java/cuchaz/enigma/api/service/NameProposalService.java
new file mode 100644
index 0000000..4c357db
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/api/service/NameProposalService.java
@@ -0,0 +1,12 @@
1package cuchaz.enigma.api.service;
2
3import cuchaz.enigma.translation.mapping.EntryRemapper;
4import cuchaz.enigma.translation.representation.entry.Entry;
5
6import java.util.Optional;
7
8public interface NameProposalService extends EnigmaService {
9 EnigmaServiceType<NameProposalService> TYPE = EnigmaServiceType.create("name_proposal");
10
11 Optional<String> proposeName(Entry<?> obfEntry, EntryRemapper remapper);
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 0000000..af0cf30
--- /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 7ec7679..08e73e6 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 b107fb6..41d7bfa 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 a58d908..bc23d01 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 5d49938..b0d2a7d 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,15 @@ 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 jar.write(fileJarOut, progress);
32 } 36 }
33} 37}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
index 39d0333..5051032 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 93643ab..44f70f8 100644
--- a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java
+++ b/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java
@@ -1,21 +1,25 @@
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;
7import cuchaz.enigma.api.EnigmaPlugin; 8import cuchaz.enigma.api.service.NameProposalService;
8import cuchaz.enigma.gui.highlight.TokenHighlightType; 9import cuchaz.enigma.gui.highlight.TokenHighlightType;
9import cuchaz.enigma.translation.LocalNameGenerator; 10import cuchaz.enigma.translation.LocalNameGenerator;
10import 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;
11import cuchaz.enigma.translation.representation.TypeDescriptor; 15import cuchaz.enigma.translation.representation.TypeDescriptor;
12import cuchaz.enigma.translation.representation.entry.ClassEntry; 16import cuchaz.enigma.translation.representation.entry.ClassEntry;
13import cuchaz.enigma.translation.representation.entry.Entry; 17import cuchaz.enigma.translation.representation.entry.Entry;
14import cuchaz.enigma.translation.representation.entry.FieldEntry;
15import cuchaz.enigma.translation.representation.entry.LocalVariableDefEntry; 18import cuchaz.enigma.translation.representation.entry.LocalVariableDefEntry;
16 19
17import javax.annotation.Nullable; 20import javax.annotation.Nullable;
18import java.util.*; 21import java.util.*;
22import java.util.stream.Stream;
19 23
20public class DecompiledClassSource { 24public class DecompiledClassSource {
21 private final ClassEntry classEntry; 25 private final ClassEntry classEntry;
@@ -35,30 +39,30 @@ public class DecompiledClassSource {
35 return new DecompiledClassSource(classEntry, new SourceIndex(text)); 39 return new DecompiledClassSource(classEntry, new SourceIndex(text));
36 } 40 }
37 41
38 public void remapSource(Deobfuscator deobfuscator, Translator translator) { 42 public void remapSource(EnigmaProject project, Translator translator) {
39 highlightedTokens.clear(); 43 highlightedTokens.clear();
40 44
41 SourceRemapper remapper = new SourceRemapper(obfuscatedIndex.getSource(), obfuscatedIndex.referenceTokens()); 45 SourceRemapper remapper = new SourceRemapper(obfuscatedIndex.getSource(), obfuscatedIndex.referenceTokens());
42 46
43 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));
44 remappedIndex = obfuscatedIndex.remapTo(remapResult); 48 remappedIndex = obfuscatedIndex.remapTo(remapResult);
45 } 49 }
46 50
47 private String remapToken(Deobfuscator deobfuscator, Token token, Token movedToken, Translator translator) { 51 private String remapToken(EnigmaProject project, Token token, Token movedToken, Translator translator) {
48 EntryReference<Entry<?>, Entry<?>> reference = obfuscatedIndex.getReference(token); 52 EntryReference<Entry<?>, Entry<?>> reference = obfuscatedIndex.getReference(token);
49 53
50 Entry<?> entry = reference.getNameableEntry(); 54 Entry<?> entry = reference.getNameableEntry();
51 Entry<?> translatedEntry = translator.translate(entry); 55 Entry<?> translatedEntry = translator.translate(entry);
52 56
53 if (deobfuscator.isRenamable(reference)) { 57 if (project.isRenamable(reference)) {
54 if (isDeobfuscated(entry, translatedEntry)) { 58 if (isDeobfuscated(entry, translatedEntry)) {
55 highlightToken(movedToken, TokenHighlightType.DEOBFUSCATED); 59 highlightToken(movedToken, TokenHighlightType.DEOBFUSCATED);
56 return translatedEntry.getSourceRemapName(); 60 return translatedEntry.getSourceRemapName();
57 } else { 61 } else {
58 String proposedName = proposeName(deobfuscator, entry); 62 Optional<String> proposedName = proposeName(project, entry);
59 if (proposedName != null) { 63 if (proposedName.isPresent()) {
60 highlightToken(movedToken, TokenHighlightType.PROPOSED); 64 highlightToken(movedToken, TokenHighlightType.PROPOSED);
61 return proposedName; 65 return proposedName.get();
62 } 66 }
63 67
64 highlightToken(movedToken, TokenHighlightType.OBFUSCATED); 68 highlightToken(movedToken, TokenHighlightType.OBFUSCATED);
@@ -73,18 +77,22 @@ public class DecompiledClassSource {
73 return null; 77 return null;
74 } 78 }
75 79
76 @Nullable 80 private Optional<String> proposeName(EnigmaProject project, Entry<?> entry) {
77 private String proposeName(Deobfuscator deobfuscator, Entry<?> entry) { 81 EnigmaServices services = project.getEnigma().getServices();
78 if (entry instanceof FieldEntry) { 82
79 for (EnigmaPlugin plugin : deobfuscator.getPlugins()) { 83 return services.get(NameProposalService.TYPE).flatMap(nameProposalService -> {
80 String owner = entry.getContainingClass().getFullName(); 84 EntryResolver resolver = project.getMapper().getObfResolver();
81 String proposal = plugin.proposeFieldName(owner, entry.getName(), ((FieldEntry) entry).getDesc().toString()); 85
82 if (proposal != null) { 86 Collection<Entry<?>> resolved = resolver.resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT);
83 return proposal; 87 EntryRemapper mapper = project.getMapper();
84 } 88
85 } 89 Stream<String> proposals = resolved.stream()
86 } 90 .map(e -> nameProposalService.proposeName(e, mapper))
87 return null; 91 .filter(Optional::isPresent)
92 .map(Optional::get);
93
94 return proposals.findFirst();
95 });
88 } 96 }
89 97
90 @Nullable 98 @Nullable
diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java
index dceeaa4..5b9a331 100644
--- a/src/main/java/cuchaz/enigma/gui/Gui.java
+++ b/src/main/java/cuchaz/enigma/gui/Gui.java
@@ -13,6 +13,7 @@ package cuchaz.enigma.gui;
13 13
14import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import cuchaz.enigma.Constants; 15import cuchaz.enigma.Constants;
16import cuchaz.enigma.EnigmaProfile;
16import cuchaz.enigma.ExceptionIgnorer; 17import cuchaz.enigma.ExceptionIgnorer;
17import cuchaz.enigma.analysis.*; 18import cuchaz.enigma.analysis.*;
18import cuchaz.enigma.config.Config; 19import cuchaz.enigma.config.Config;
@@ -31,9 +32,7 @@ import cuchaz.enigma.gui.panels.PanelIdentifier;
31import cuchaz.enigma.gui.panels.PanelObf; 32import cuchaz.enigma.gui.panels.PanelObf;
32import cuchaz.enigma.gui.util.History; 33import cuchaz.enigma.gui.util.History;
33import cuchaz.enigma.throwables.IllegalNameException; 34import cuchaz.enigma.throwables.IllegalNameException;
34import cuchaz.enigma.translation.mapping.AccessModifier; 35import cuchaz.enigma.translation.mapping.*;
35import cuchaz.enigma.translation.mapping.EntryResolver;
36import cuchaz.enigma.translation.mapping.ResolutionStrategy;
37import cuchaz.enigma.translation.representation.entry.*; 36import cuchaz.enigma.translation.representation.entry.*;
38import cuchaz.enigma.utils.Utils; 37import cuchaz.enigma.utils.Utils;
39import de.sciss.syntaxpane.DefaultSyntaxKit; 38import de.sciss.syntaxpane.DefaultSyntaxKit;
@@ -95,7 +94,7 @@ public class Gui {
95 } 94 }
96 } 95 }
97 96
98 public Gui() { 97 public Gui(EnigmaProfile profile) {
99 Config.getInstance().lookAndFeel.setGlobalLAF(); 98 Config.getInstance().lookAndFeel.setGlobalLAF();
100 99
101 // init frame 100 // init frame
@@ -114,7 +113,7 @@ public class Gui {
114 }); 113 });
115 } 114 }
116 115
117 this.controller = new GuiController(this); 116 this.controller = new GuiController(this, profile);
118 117
119 // init file choosers 118 // init file choosers
120 this.jarFileChooser = new FileDialog(getFrame(), "Open Jar", FileDialog.LOAD); 119 this.jarFileChooser = new FileDialog(getFrame(), "Open Jar", FileDialog.LOAD);
@@ -312,13 +311,8 @@ public class Gui {
312 return this.controller; 311 return this.controller;
313 } 312 }
314 313
315 public void onStartOpenJar(String message) { 314 public void onStartOpenJar() {
316 this.classesPanel.removeAll(); 315 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(); 316 redraw();
323 } 317 }
324 318
@@ -447,7 +441,7 @@ public class Gui {
447 441
448 this.cursorReference = reference; 442 this.cursorReference = reference;
449 443
450 EntryReference<Entry<?>, Entry<?>> translatedReference = controller.getDeobfuscator().deobfuscate(reference); 444 EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(reference);
451 445
452 infoPanel.removeAll(); 446 infoPanel.removeAll();
453 if (translatedReference.entry instanceof ClassEntry) { 447 if (translatedReference.entry instanceof ClassEntry) {
@@ -509,7 +503,7 @@ public class Gui {
509 } 503 }
510 504
511 private JComboBox<AccessModifier> addModifierComboBox(JPanel container, String name, Entry entry) { 505 private JComboBox<AccessModifier> addModifierComboBox(JPanel container, String name, Entry entry) {
512 if (!getController().entryIsInJar(entry)) 506 if (!getController().project.isRenamable(entry))
513 return null; 507 return null;
514 JPanel panel = new JPanel(); 508 JPanel panel = new JPanel();
515 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); 509 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
@@ -519,8 +513,16 @@ public class Gui {
519 JComboBox<AccessModifier> combo = new JComboBox<>(AccessModifier.values()); 513 JComboBox<AccessModifier> combo = new JComboBox<>(AccessModifier.values());
520 ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT); 514 ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT);
521 combo.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); 515 combo.setPreferredSize(new Dimension(100, label.getPreferredSize().height));
522 combo.setSelectedIndex(getController().getDeobfuscator().getModifier(entry).ordinal()); 516
523 combo.addItemListener(getController()::modifierChange); 517 EntryMapping mapping = controller.project.getMapper().getDeobfMapping(entry);
518 if (mapping != null) {
519 combo.setSelectedIndex(mapping.getAccessModifier().ordinal());
520 } else {
521 combo.setSelectedIndex(AccessModifier.UNCHANGED.ordinal());
522 }
523
524 combo.addItemListener(controller::modifierChange);
525
524 panel.add(combo); 526 panel.add(combo);
525 527
526 container.add(panel); 528 container.add(panel);
@@ -529,6 +531,7 @@ public class Gui {
529 } 531 }
530 532
531 public void onCaretMove(int pos, boolean fromClick) { 533 public void onCaretMove(int pos, boolean fromClick) {
534 EntryRemapper mapper = controller.project.getMapper();
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 1683333..209b5d1 100644
--- a/src/main/java/cuchaz/enigma/gui/GuiController.java
+++ b/src/main/java/cuchaz/enigma/gui/GuiController.java
@@ -14,9 +14,10 @@ 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.*;
18import cuchaz.enigma.SourceProvider;
19import cuchaz.enigma.analysis.*; 18import cuchaz.enigma.analysis.*;
19import cuchaz.enigma.api.service.ObfuscationTestService;
20import cuchaz.enigma.bytecode.translators.SourceFixVisitor;
20import cuchaz.enigma.config.Config; 21import cuchaz.enigma.config.Config;
21import cuchaz.enigma.gui.dialog.ProgressDialog; 22import cuchaz.enigma.gui.dialog.ProgressDialog;
22import cuchaz.enigma.gui.util.History; 23import cuchaz.enigma.gui.util.History;
@@ -30,114 +31,157 @@ import cuchaz.enigma.translation.representation.entry.Entry;
30import cuchaz.enigma.translation.representation.entry.FieldEntry; 31import cuchaz.enigma.translation.representation.entry.FieldEntry;
31import cuchaz.enigma.translation.representation.entry.MethodEntry; 32import cuchaz.enigma.translation.representation.entry.MethodEntry;
32import cuchaz.enigma.utils.ReadableToken; 33import cuchaz.enigma.utils.ReadableToken;
34import org.objectweb.asm.Opcodes;
33 35
34import javax.annotation.Nullable; 36import javax.annotation.Nullable;
35import javax.swing.*; 37import javax.swing.*;
36import java.awt.event.ItemEvent; 38import java.awt.event.ItemEvent;
37import java.io.File;
38import java.io.IOException;
39import java.io.PrintWriter; 39import java.io.PrintWriter;
40import java.io.StringWriter; 40import java.io.StringWriter;
41import java.nio.file.Path; 41import java.nio.file.Path;
42import java.util.Collection; 42import java.util.Collection;
43import java.util.List; 43import java.util.List;
44import java.util.Optional;
44import java.util.concurrent.ExecutorService; 45import java.util.concurrent.ExecutorService;
45import java.util.concurrent.Executors; 46import java.util.concurrent.Executors;
46import java.util.jar.JarFile;
47import java.util.stream.Collectors; 47import java.util.stream.Collectors;
48import java.util.stream.Stream;
48 49
49public class GuiController { 50public class GuiController {
50 private static final ExecutorService DECOMPILER_SERVICE = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setDaemon(true).setNameFormat("decompiler-thread").build()); 51 private static final ExecutorService DECOMPILER_SERVICE = Executors.newSingleThreadExecutor(
52 new ThreadFactoryBuilder()
53 .setDaemon(true)
54 .setNameFormat("decompiler-thread")
55 .build()
56 );
51 57
52 private final Gui gui; 58 private final Gui gui;
53 private Deobfuscator deobfuscator; 59 public final Enigma enigma;
54 private DecompiledClassSource currentSource;
55 60
61 public EnigmaProject project;
62 private SourceProvider sourceProvider;
63 private IndexTreeBuilder indexTreeBuilder;
56 64
57 private Path loadedMappingPath; 65 private Path loadedMappingPath;
58 private MappingFormat loadedMappingFormat; 66 private MappingFormat loadedMappingFormat;
59 67
60 public GuiController(Gui gui) { 68 private DecompiledClassSource currentSource;
69
70 public GuiController(Gui gui, EnigmaProfile profile) {
61 this.gui = gui; 71 this.gui = gui;
72 this.enigma = Enigma.builder()
73 .setProfile(profile)
74 .build();
62 } 75 }
63 76
64 public boolean isDirty() { 77 public boolean isDirty() {
65 if (deobfuscator == null) { 78 return project != null && project.getMapper().isDirty();
66 return false;
67 }
68 return deobfuscator.getMapper().isDirty();
69 } 79 }
70 80
71 public void openJar(final JarFile jar) throws IOException { 81 public void openJar(final Path jarPath) {
72 this.gui.onStartOpenJar("Loading JAR..."); 82 this.gui.onStartOpenJar();
73 this.deobfuscator = new Deobfuscator(jar, this.gui::onStartOpenJar); 83
74 this.gui.onFinishOpenJar(jar.getName()); 84 ProgressDialog.runOffThread(gui.getFrame(), progress -> {
75 refreshClasses(); 85 project = enigma.openJar(jarPath, progress);
86
87 indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex());
88
89 CompiledSourceTypeLoader typeLoader = new CompiledSourceTypeLoader(project.getClassCache());
90 typeLoader.addVisitor(visitor -> new SourceFixVisitor(Opcodes.ASM5, visitor, project.getJarIndex()));
91 sourceProvider = new SourceProvider(SourceProvider.createSettings(), typeLoader);
92
93 gui.onFinishOpenJar(jarPath.getFileName().toString());
94
95 refreshClasses();
96 });
76 } 97 }
77 98
78 public void closeJar() { 99 public void closeJar() {
79 this.deobfuscator = null; 100 this.project = null;
80 this.gui.onCloseJar(); 101 this.gui.onCloseJar();
81 } 102 }
82 103
83 public void openMappings(MappingFormat format, Path path) { 104 public void openMappings(MappingFormat format, Path path) {
84 if (deobfuscator == null) return; 105 if (project == null) return;
85 ProgressDialog.runInThread(this.gui.getFrame(), progress -> { 106
107 gui.setMappingsFile(path);
108
109 ProgressDialog.runOffThread(gui.getFrame(), progress -> {
86 try { 110 try {
87 EntryTree<EntryMapping> mappings = format.read(path, progress); 111 EntryTree<EntryMapping> mappings = format.read(path, progress);
88 deobfuscator.setMappings(mappings, progress); 112 project.setMappings(mappings);
89 113
90 gui.setMappingsFile(path);
91 loadedMappingFormat = format; 114 loadedMappingFormat = format;
92 loadedMappingPath = path; 115 loadedMappingPath = path;
93 116
94 refreshClasses(); 117 refreshClasses();
95 refreshCurrentClass(); 118 refreshCurrentClass();
96 } catch (MappingParseException e) { 119 } catch (MappingParseException e) {
97 JOptionPane.showMessageDialog(this.gui.getFrame(), e.getMessage()); 120 JOptionPane.showMessageDialog(gui.getFrame(), e.getMessage());
98 } 121 }
99 }); 122 });
100 } 123 }
101 124
102 public void saveMappings(Path path) { 125 public void saveMappings(Path path) {
103 saveMappings(loadedMappingFormat, path); 126 if (project == null) return;
127
128 saveMappings(path, loadedMappingFormat);
104 } 129 }
105 130
106 public void saveMappings(MappingFormat format, Path path) { 131 public void saveMappings(Path path, MappingFormat format) {
107 if (deobfuscator == null) return; 132 if (project == null) return;
108 EntryRemapper mapper = deobfuscator.getMapper(); 133
134 ProgressDialog.runOffThread(this.gui.getFrame(), progress -> {
135 EntryRemapper mapper = project.getMapper();
136
137 MappingDelta<EntryMapping> delta = mapper.takeMappingDelta();
138 boolean saveAll = !path.equals(loadedMappingPath);
109 139
110 MappingDelta<EntryMapping> delta = mapper.takeMappingDelta(); 140 loadedMappingFormat = format;
111 boolean saveAll = !path.equals(loadedMappingPath); 141 loadedMappingPath = path;
112 142
113 ProgressDialog.runInThread(this.gui.getFrame(), progress -> {
114 if (saveAll) { 143 if (saveAll) {
115 format.write(mapper.getObfToDeobf(), path, progress); 144 format.write(mapper.getObfToDeobf(), path, progress);
116 } else { 145 } else {
117 format.write(mapper.getObfToDeobf(), delta, path, progress); 146 format.write(mapper.getObfToDeobf(), delta, path, progress);
118 } 147 }
119 }); 148 });
120
121 loadedMappingFormat = format;
122 loadedMappingPath = path;
123 } 149 }
124 150
125 public void closeMappings() { 151 public void closeMappings() {
126 if (deobfuscator == null) return; 152 if (project == null) return;
127 this.deobfuscator.setMappings(null); 153
154 project.setMappings(null);
155
128 this.gui.setMappingsFile(null); 156 this.gui.setMappingsFile(null);
129 refreshClasses(); 157 refreshClasses();
130 refreshCurrentClass(); 158 refreshCurrentClass();
131 } 159 }
132 160
133 public void exportSource(final File dirOut) { 161 public void dropMappings() {
134 if (deobfuscator == null) return; 162 if (project == null) return;
135 ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeSources(dirOut.toPath(), progress)); 163
164 ProgressDialog.runOffThread(this.gui.getFrame(), progress -> project.dropMappings(progress));
136 } 165 }
137 166
138 public void exportJar(final File fileOut) { 167 public void exportSource(final Path path) {
139 if (deobfuscator == null) return; 168 if (project == null) return;
140 ProgressDialog.runInThread(this.gui.getFrame(), progress -> this.deobfuscator.writeTransformedJar(fileOut, progress)); 169
170 ProgressDialog.runOffThread(this.gui.getFrame(), progress -> {
171 EnigmaProject.JarExport jar = project.exportRemappedJar(progress);
172 EnigmaProject.SourceExport source = jar.decompile(progress);
173
174 source.write(path, progress);
175 });
176 }
177
178 public void exportJar(final Path path) {
179 if (project == null) return;
180
181 ProgressDialog.runOffThread(this.gui.getFrame(), progress -> {
182 EnigmaProject.JarExport jar = project.exportRemappedJar(progress);
183 jar.write(path, progress);
184 });
141 } 185 }
142 186
143 public Token getToken(int pos) { 187 public Token getToken(int pos) {
@@ -167,85 +211,9 @@ public class GuiController {
167 ); 211 );
168 } 212 }
169 213
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 /** 214 /**
248 * Navigates to the declaration with respect to navigation history 215 * Navigates to the declaration with respect to navigation history
216 *
249 * @param entry the entry whose declaration will be navigated to 217 * @param entry the entry whose declaration will be navigated to
250 */ 218 */
251 public void openDeclaration(Entry<?> entry) { 219 public void openDeclaration(Entry<?> entry) {
@@ -257,6 +225,7 @@ public class GuiController {
257 225
258 /** 226 /**
259 * Navigates to the reference with respect to navigation history 227 * Navigates to the reference with respect to navigation history
228 *
260 * @param reference the reference 229 * @param reference the reference
261 */ 230 */
262 public void openReference(EntryReference<Entry<?>, Entry<?>> reference) { 231 public void openReference(EntryReference<Entry<?>, Entry<?>> reference) {
@@ -275,12 +244,13 @@ public class GuiController {
275 244
276 /** 245 /**
277 * Navigates to the reference without modifying history. If the class is not currently loaded, it will be loaded. 246 * Navigates to the reference without modifying history. If the class is not currently loaded, it will be loaded.
247 *
278 * @param reference the reference 248 * @param reference the reference
279 */ 249 */
280 private void setReference(EntryReference<Entry<?>, Entry<?>> reference) { 250 private void setReference(EntryReference<Entry<?>, Entry<?>> reference) {
281 // get the reference target class 251 // get the reference target class
282 ClassEntry classEntry = reference.getLocationClassEntry(); 252 ClassEntry classEntry = reference.getLocationClassEntry();
283 if (!this.deobfuscator.isRenamable(classEntry)) { 253 if (!project.isRenamable(classEntry)) {
284 throw new IllegalArgumentException("Obfuscated class " + classEntry + " was not found in the jar!"); 254 throw new IllegalArgumentException("Obfuscated class " + classEntry + " was not found in the jar!");
285 } 255 }
286 256
@@ -294,6 +264,7 @@ public class GuiController {
294 264
295 /** 265 /**
296 * Navigates to the reference without modifying history. Assumes the class is loaded. 266 * Navigates to the reference without modifying history. Assumes the class is loaded.
267 *
297 * @param reference 268 * @param reference
298 */ 269 */
299 private void showReference(EntryReference<Entry<?>, Entry<?>> reference) { 270 private void showReference(EntryReference<Entry<?>, Entry<?>> reference) {
@@ -307,7 +278,7 @@ public class GuiController {
307 } 278 }
308 279
309 public Collection<Token> getTokensForReference(EntryReference<Entry<?>, Entry<?>> reference) { 280 public Collection<Token> getTokensForReference(EntryReference<Entry<?>, Entry<?>> reference) {
310 EntryRemapper mapper = this.deobfuscator.getMapper(); 281 EntryRemapper mapper = this.project.getMapper();
311 282
312 SourceIndex index = this.currentSource.getIndex(); 283 SourceIndex index = this.currentSource.getIndex();
313 return mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_CLOSEST) 284 return mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_CLOSEST)
@@ -337,7 +308,7 @@ public class GuiController {
337 } 308 }
338 309
339 public void navigateTo(Entry<?> entry) { 310 public void navigateTo(Entry<?> entry) {
340 if (!entryIsInJar(entry)) { 311 if (!project.isRenamable(entry)) {
341 // entry is not in the jar. Ignore it 312 // entry is not in the jar. Ignore it
342 return; 313 return;
343 } 314 }
@@ -345,7 +316,7 @@ public class GuiController {
345 } 316 }
346 317
347 public void navigateTo(EntryReference<Entry<?>, Entry<?>> reference) { 318 public void navigateTo(EntryReference<Entry<?>, Entry<?>> reference) {
348 if (!entryIsInJar(reference.getLocationClassEntry())) { 319 if (!project.isRenamable(reference.getLocationClassEntry())) {
349 return; 320 return;
350 } 321 }
351 openReference(reference); 322 openReference(reference);
@@ -354,11 +325,38 @@ public class GuiController {
354 private void refreshClasses() { 325 private void refreshClasses() {
355 List<ClassEntry> obfClasses = Lists.newArrayList(); 326 List<ClassEntry> obfClasses = Lists.newArrayList();
356 List<ClassEntry> deobfClasses = Lists.newArrayList(); 327 List<ClassEntry> deobfClasses = Lists.newArrayList();
357 this.deobfuscator.getSeparatedClasses(obfClasses, deobfClasses); 328 this.addSeparatedClasses(obfClasses, deobfClasses);
358 this.gui.setObfClasses(obfClasses); 329 this.gui.setObfClasses(obfClasses);
359 this.gui.setDeobfClasses(deobfClasses); 330 this.gui.setDeobfClasses(deobfClasses);
360 } 331 }
361 332
333 public void addSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) {
334 EntryRemapper mapper = project.getMapper();
335
336 Collection<ClassEntry> classes = project.getJarIndex().getEntryIndex().getClasses();
337 Stream<ClassEntry> visibleClasses = classes.stream()
338 .filter(entry -> !entry.isInnerClass());
339
340 visibleClasses.forEach(entry -> {
341 ClassEntry deobfEntry = mapper.deobfuscate(entry);
342
343 Optional<ObfuscationTestService> obfService = enigma.getServices().get(ObfuscationTestService.TYPE);
344 boolean obfuscated = deobfEntry.equals(entry);
345
346 if (obfuscated && obfService.isPresent()) {
347 if (obfService.get().testDeobfuscated(entry)) {
348 obfuscated = false;
349 }
350 }
351
352 if (obfuscated) {
353 obfClasses.add(entry);
354 } else {
355 deobfClasses.add(entry);
356 }
357 });
358 }
359
362 public void refreshCurrentClass() { 360 public void refreshCurrentClass() {
363 refreshCurrentClass(null); 361 refreshCurrentClass(null);
364 } 362 }
@@ -384,10 +382,10 @@ public class GuiController {
384 DECOMPILER_SERVICE.submit(() -> { 382 DECOMPILER_SERVICE.submit(() -> {
385 try { 383 try {
386 if (requiresDecompile) { 384 if (requiresDecompile) {
387 currentSource = decompileSource(targetClass, deobfuscator.getObfSourceProvider()); 385 currentSource = decompileSource(targetClass);
388 } 386 }
389 387
390 remapSource(deobfuscator.getMapper().getDeobfuscator()); 388 remapSource(project.getMapper().getDeobfuscator());
391 callback.run(); 389 callback.run();
392 } catch (Throwable t) { 390 } catch (Throwable t) {
393 System.err.println("An exception was thrown while decompiling class " + classEntry.getFullName()); 391 System.err.println("An exception was thrown while decompiling class " + classEntry.getFullName());
@@ -396,7 +394,7 @@ public class GuiController {
396 }); 394 });
397 } 395 }
398 396
399 private DecompiledClassSource decompileSource(ClassEntry targetClass, SourceProvider sourceProvider) { 397 private DecompiledClassSource decompileSource(ClassEntry targetClass) {
400 try { 398 try {
401 CompilationUnit sourceTree = sourceProvider.getSources(targetClass.getFullName()); 399 CompilationUnit sourceTree = sourceProvider.getSources(targetClass.getFullName());
402 if (sourceTree == null) { 400 if (sourceTree == null) {
@@ -410,7 +408,7 @@ public class GuiController {
410 String sourceString = sourceProvider.writeSourceToString(sourceTree); 408 String sourceString = sourceProvider.writeSourceToString(sourceTree);
411 409
412 SourceIndex index = SourceIndex.buildIndex(sourceString, sourceTree, true); 410 SourceIndex index = SourceIndex.buildIndex(sourceString, sourceTree, true);
413 index.resolveReferences(deobfuscator.getMapper().getObfResolver()); 411 index.resolveReferences(project.getMapper().getObfResolver());
414 412
415 return new DecompiledClassSource(targetClass, index); 413 return new DecompiledClassSource(targetClass, index);
416 } catch (Throwable t) { 414 } catch (Throwable t) {
@@ -426,20 +424,105 @@ public class GuiController {
426 return; 424 return;
427 } 425 }
428 426
429 currentSource.remapSource(deobfuscator, translator); 427 currentSource.remapSource(project, translator);
430 428
431 gui.setEditorTheme(Config.getInstance().lookAndFeel); 429 gui.setEditorTheme(Config.getInstance().lookAndFeel);
432 gui.setSource(currentSource); 430 gui.setSource(currentSource);
433 } 431 }
434 432
435 public Deobfuscator getDeobfuscator() {
436 return deobfuscator;
437 }
438
439 public void modifierChange(ItemEvent event) { 433 public void modifierChange(ItemEvent event) {
440 if (event.getStateChange() == ItemEvent.SELECTED) { 434 if (event.getStateChange() == ItemEvent.SELECTED) {
441 deobfuscator.changeModifier(gui.cursorReference.entry, (AccessModifier) event.getItem()); 435 EntryRemapper mapper = project.getMapper();
436 Entry<?> entry = gui.cursorReference.entry;
437 AccessModifier modifier = (AccessModifier) event.getItem();
438
439 EntryMapping mapping = mapper.getDeobfMapping(entry);
440 if (mapping != null) {
441 mapper.mapFromObf(entry, new EntryMapping(mapping.getTargetName(), modifier));
442 } else {
443 mapper.mapFromObf(entry, new EntryMapping(entry.getName(), modifier));
444 }
445
442 refreshCurrentClass(); 446 refreshCurrentClass();
443 } 447 }
444 } 448 }
449
450 public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) {
451 Translator translator = project.getMapper().getDeobfuscator();
452 ClassInheritanceTreeNode rootNode = indexTreeBuilder.buildClassInheritance(translator, entry);
453 return ClassInheritanceTreeNode.findNode(rootNode, entry);
454 }
455
456 public ClassImplementationsTreeNode getClassImplementations(ClassEntry entry) {
457 Translator translator = project.getMapper().getDeobfuscator();
458 return this.indexTreeBuilder.buildClassImplementations(translator, entry);
459 }
460
461 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry entry) {
462 Translator translator = project.getMapper().getDeobfuscator();
463 MethodInheritanceTreeNode rootNode = indexTreeBuilder.buildMethodInheritance(translator, entry);
464 return MethodInheritanceTreeNode.findNode(rootNode, entry);
465 }
466
467 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry entry) {
468 Translator translator = project.getMapper().getDeobfuscator();
469 List<MethodImplementationsTreeNode> rootNodes = indexTreeBuilder.buildMethodImplementations(translator, entry);
470 if (rootNodes.isEmpty()) {
471 return null;
472 }
473 if (rootNodes.size() > 1) {
474 System.err.println("WARNING: Method " + entry + " implements multiple interfaces. Only showing first one.");
475 }
476 return MethodImplementationsTreeNode.findNode(rootNodes.get(0), entry);
477 }
478
479 public ClassReferenceTreeNode getClassReferences(ClassEntry entry) {
480 Translator deobfuscator = project.getMapper().getDeobfuscator();
481 ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, entry);
482 rootNode.load(project.getJarIndex(), true);
483 return rootNode;
484 }
485
486 public FieldReferenceTreeNode getFieldReferences(FieldEntry entry) {
487 Translator translator = project.getMapper().getDeobfuscator();
488 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, entry);
489 rootNode.load(project.getJarIndex(), true);
490 return rootNode;
491 }
492
493 public MethodReferenceTreeNode getMethodReferences(MethodEntry entry, boolean recursive) {
494 Translator translator = project.getMapper().getDeobfuscator();
495 MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, entry);
496 rootNode.load(project.getJarIndex(), true, recursive);
497 return rootNode;
498 }
499
500 public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree) {
501 Entry<?> entry = reference.getNameableEntry();
502 project.getMapper().mapFromObf(entry, new EntryMapping(newName));
503
504 if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass())
505 this.gui.moveClassTree(reference, newName);
506
507 refreshCurrentClass(reference);
508 }
509
510 public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference) {
511 project.getMapper().removeByObf(reference.getNameableEntry());
512
513 if (reference.entry instanceof ClassEntry)
514 this.gui.moveClassTree(reference, false, true);
515 refreshCurrentClass(reference);
516 }
517
518 public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference) {
519 EntryRemapper mapper = project.getMapper();
520 Entry<?> entry = reference.getNameableEntry();
521 mapper.mapFromObf(entry, new EntryMapping(mapper.deobfuscate(entry).getName()));
522
523 if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass())
524 this.gui.moveClassTree(reference, true, false);
525
526 refreshCurrentClass(reference);
527 }
445} 528}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java
index 84fe7c8..c135d03 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 a122bd8..1657d7b 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 98275b4..a3bd2fe 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
@@ -28,6 +29,7 @@ public class MenuBar extends JMenuBar {
28 public final JMenuItem saveMappingEnigmaDirectoryMenu; 29 public final JMenuItem saveMappingEnigmaDirectoryMenu;
29 public final JMenuItem saveMappingsSrgMenu; 30 public final JMenuItem saveMappingsSrgMenu;
30 public final JMenuItem closeMappingsMenu; 31 public final JMenuItem closeMappingsMenu;
32 public final JMenuItem dropMappingsMenu;
31 public final JMenuItem exportSourceMenu; 33 public final JMenuItem exportSourceMenu;
32 public final JMenuItem exportJarMenu; 34 public final JMenuItem exportJarMenu;
33 private final Gui gui; 35 private final Gui gui;
@@ -43,17 +45,9 @@ public class MenuBar extends JMenuBar {
43 menu.add(item); 45 menu.add(item);
44 item.addActionListener(event -> { 46 item.addActionListener(event -> {
45 this.gui.jarFileChooser.setVisible(true); 47 this.gui.jarFileChooser.setVisible(true);
46 File file = new File(this.gui.jarFileChooser.getDirectory() + File.separator + this.gui.jarFileChooser.getFile()); 48 Path path = Paths.get(this.gui.jarFileChooser.getDirectory()).resolve(this.gui.jarFileChooser.getFile());
47 if (file.exists()) { 49 if (Files.exists(path)) {
48 // load the jar in a separate thread 50 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 } 51 }
58 }); 52 });
59 } 53 }
@@ -106,7 +100,7 @@ public class MenuBar extends JMenuBar {
106 item.addActionListener(event -> { 100 item.addActionListener(event -> {
107 // TODO: Use a specific file chooser for it 101 // TODO: Use a specific file chooser for it
108 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 102 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
109 this.gui.getController().saveMappings(MappingFormat.ENIGMA_FILE, this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); 103 this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), MappingFormat.ENIGMA_FILE);
110 this.saveMappingsMenu.setEnabled(true); 104 this.saveMappingsMenu.setEnabled(true);
111 } 105 }
112 }); 106 });
@@ -118,7 +112,7 @@ public class MenuBar extends JMenuBar {
118 item.addActionListener(event -> { 112 item.addActionListener(event -> {
119 // TODO: Use a specific file chooser for it 113 // TODO: Use a specific file chooser for it
120 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 114 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
121 this.gui.getController().saveMappings(MappingFormat.ENIGMA_DIRECTORY, this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); 115 this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), MappingFormat.ENIGMA_DIRECTORY);
122 this.saveMappingsMenu.setEnabled(true); 116 this.saveMappingsMenu.setEnabled(true);
123 } 117 }
124 }); 118 });
@@ -131,7 +125,7 @@ public class MenuBar extends JMenuBar {
131 item.addActionListener(event -> { 125 item.addActionListener(event -> {
132 // TODO: Use a specific file chooser for it 126 // TODO: Use a specific file chooser for it
133 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 127 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
134 this.gui.getController().saveMappings(MappingFormat.SRG_FILE, this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); 128 this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), MappingFormat.SRG_FILE);
135 this.saveMappingsMenu.setEnabled(true); 129 this.saveMappingsMenu.setEnabled(true);
136 } 130 }
137 }); 131 });
@@ -156,13 +150,19 @@ public class MenuBar extends JMenuBar {
156 }); 150 });
157 this.closeMappingsMenu = item; 151 this.closeMappingsMenu = item;
158 } 152 }
153 {
154 JMenuItem item = new JMenuItem("Drop Invalid Mappings");
155 menu.add(item);
156 item.addActionListener(event -> this.gui.getController().dropMappings());
157 this.dropMappingsMenu = item;
158 }
159 menu.addSeparator(); 159 menu.addSeparator();
160 { 160 {
161 JMenuItem item = new JMenuItem("Export Source..."); 161 JMenuItem item = new JMenuItem("Export Source...");
162 menu.add(item); 162 menu.add(item);
163 item.addActionListener(event -> { 163 item.addActionListener(event -> {
164 if (this.gui.exportSourceFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { 164 if (this.gui.exportSourceFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
165 this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile()); 165 this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile().toPath());
166 } 166 }
167 }); 167 });
168 this.exportSourceMenu = item; 168 this.exportSourceMenu = item;
@@ -173,8 +173,8 @@ public class MenuBar extends JMenuBar {
173 item.addActionListener(event -> { 173 item.addActionListener(event -> {
174 this.gui.exportJarFileChooser.setVisible(true); 174 this.gui.exportJarFileChooser.setVisible(true);
175 if (this.gui.exportJarFileChooser.getFile() != null) { 175 if (this.gui.exportJarFileChooser.getFile() != null) {
176 File file = new File(this.gui.exportJarFileChooser.getDirectory() + File.separator + this.gui.exportJarFileChooser.getFile()); 176 Path path = Paths.get(this.gui.exportJarFileChooser.getDirectory(), this.gui.exportJarFileChooser.getFile());
177 this.gui.getController().exportJar(file); 177 this.gui.getController().exportJar(path);
178 } 178 }
179 }); 179 });
180 this.exportJarMenu = item; 180 this.exportJarMenu = item;
@@ -202,7 +202,7 @@ public class MenuBar extends JMenuBar {
202 search.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.SHIFT_MASK)); 202 search.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.SHIFT_MASK));
203 menu.add(search); 203 menu.add(search);
204 search.addActionListener(event -> { 204 search.addActionListener(event -> {
205 if (this.gui.getController().getDeobfuscator() != null) { 205 if (this.gui.getController().project != null) {
206 new SearchDialog(this.gui).show(); 206 new SearchDialog(this.gui).show();
207 } 207 }
208 }); 208 });
diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java b/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java
index 0a85f0a..123238f 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;
@@ -107,8 +107,8 @@ public class PanelEditor extends JEditorPane {
107 if (!gui.popupMenu.renameMenu.isEnabled()) return; 107 if (!gui.popupMenu.renameMenu.isEnabled()) return;
108 108
109 if (!event.isControlDown() && !event.isAltDown()) { 109 if (!event.isControlDown() && !event.isAltDown()) {
110 Deobfuscator deobfuscator = gui.getController().getDeobfuscator(); 110 EnigmaProject project = gui.getController().project;
111 EntryReference<Entry<?>, Entry<?>> reference = deobfuscator.deobfuscate(gui.cursorReference); 111 EntryReference<Entry<?>, Entry<?>> reference = project.getMapper().deobfuscate(gui.cursorReference);
112 Entry<?> entry = reference.getNameableEntry(); 112 Entry<?> entry = reference.getNameableEntry();
113 113
114 String name = String.valueOf(event.getKeyChar()); 114 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 8c4a326..c9808cc 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) {