summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/gui/GuiController.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/cuchaz/enigma/gui/GuiController.java')
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiController.java729
1 files changed, 0 insertions, 729 deletions
diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java
deleted file mode 100644
index cccc9e8..0000000
--- a/src/main/java/cuchaz/enigma/gui/GuiController.java
+++ /dev/null
@@ -1,729 +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.gui;
13
14import com.google.common.collect.Lists;
15import com.google.common.util.concurrent.ThreadFactoryBuilder;
16import cuchaz.enigma.Enigma;
17import cuchaz.enigma.EnigmaProfile;
18import cuchaz.enigma.EnigmaProject;
19import cuchaz.enigma.analysis.*;
20import cuchaz.enigma.api.service.ObfuscationTestService;
21import cuchaz.enigma.bytecode.translators.SourceFixVisitor;
22import cuchaz.enigma.config.Config;
23import cuchaz.enigma.gui.dialog.ProgressDialog;
24import cuchaz.enigma.gui.stats.StatsGenerator;
25import cuchaz.enigma.gui.stats.StatsMember;
26import cuchaz.enigma.gui.util.History;
27import cuchaz.enigma.network.EnigmaClient;
28import cuchaz.enigma.network.EnigmaServer;
29import cuchaz.enigma.network.IntegratedEnigmaServer;
30import cuchaz.enigma.network.ServerPacketHandler;
31import cuchaz.enigma.network.packet.LoginC2SPacket;
32import cuchaz.enigma.network.packet.Packet;
33import cuchaz.enigma.source.*;
34import cuchaz.enigma.throwables.MappingParseException;
35import cuchaz.enigma.translation.Translator;
36import cuchaz.enigma.translation.mapping.*;
37import cuchaz.enigma.translation.mapping.serde.MappingFormat;
38import cuchaz.enigma.translation.mapping.tree.EntryTree;
39import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
40import cuchaz.enigma.translation.representation.entry.ClassEntry;
41import cuchaz.enigma.translation.representation.entry.Entry;
42import cuchaz.enigma.translation.representation.entry.FieldEntry;
43import cuchaz.enigma.translation.representation.entry.MethodEntry;
44import cuchaz.enigma.utils.I18n;
45import cuchaz.enigma.utils.Message;
46import cuchaz.enigma.utils.ReadableToken;
47import cuchaz.enigma.utils.Utils;
48import org.objectweb.asm.tree.ClassNode;
49
50import javax.annotation.Nullable;
51import javax.swing.JOptionPane;
52import javax.swing.SwingUtilities;
53import java.awt.*;
54import java.awt.event.ItemEvent;
55import java.io.*;
56import java.nio.file.Path;
57import java.util.Collection;
58import java.util.List;
59import java.util.Set;
60import java.util.concurrent.CompletableFuture;
61import java.util.concurrent.ExecutorService;
62import java.util.concurrent.Executors;
63import java.util.stream.Collectors;
64import java.util.stream.Stream;
65
66public class GuiController {
67 private static final ExecutorService DECOMPILER_SERVICE = Executors.newSingleThreadExecutor(
68 new ThreadFactoryBuilder()
69 .setDaemon(true)
70 .setNameFormat("decompiler-thread")
71 .build()
72 );
73
74 private final Gui gui;
75 public final Enigma enigma;
76
77 public EnigmaProject project;
78 private DecompilerService decompilerService;
79 private Decompiler decompiler;
80 private IndexTreeBuilder indexTreeBuilder;
81
82 private Path loadedMappingPath;
83 private MappingFormat loadedMappingFormat;
84
85 private DecompiledClassSource currentSource;
86 private Source uncommentedSource;
87
88 private EnigmaClient client;
89 private EnigmaServer server;
90
91 public GuiController(Gui gui, EnigmaProfile profile) {
92 this.gui = gui;
93 this.enigma = Enigma.builder()
94 .setProfile(profile)
95 .build();
96
97 decompilerService = Config.getInstance().decompiler.service;
98 }
99
100 public boolean isDirty() {
101 return project != null && project.getMapper().isDirty();
102 }
103
104 public CompletableFuture<Void> openJar(final Path jarPath) {
105 this.gui.onStartOpenJar();
106
107 return ProgressDialog.runOffThread(gui.getFrame(), progress -> {
108 project = enigma.openJar(jarPath, progress);
109 indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex());
110 decompiler = createDecompiler();
111 gui.onFinishOpenJar(jarPath.getFileName().toString());
112 refreshClasses();
113 });
114 }
115
116 private Decompiler createDecompiler() {
117 return decompilerService.create(name -> {
118 ClassNode node = project.getClassCache().getClassNode(name);
119
120 if (node == null) {
121 return null;
122 }
123
124 ClassNode fixedNode = new ClassNode();
125 node.accept(new SourceFixVisitor(Utils.ASM_VERSION, fixedNode, project.getJarIndex()));
126 return fixedNode;
127 }, new SourceSettings(true, true));
128 }
129
130 public void closeJar() {
131 this.project = null;
132 this.gui.onCloseJar();
133 }
134
135 public CompletableFuture<Void> openMappings(MappingFormat format, Path path) {
136 if (project == null) return CompletableFuture.completedFuture(null);
137
138 gui.setMappingsFile(path);
139
140 return ProgressDialog.runOffThread(gui.getFrame(), progress -> {
141 try {
142 MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters();
143
144 EntryTree<EntryMapping> mappings = format.read(path, progress, saveParameters);
145 project.setMappings(mappings);
146
147 loadedMappingFormat = format;
148 loadedMappingPath = path;
149
150 refreshClasses();
151 refreshCurrentClass();
152 } catch (MappingParseException e) {
153 JOptionPane.showMessageDialog(gui.getFrame(), e.getMessage());
154 }
155 });
156 }
157
158 public void openMappings(EntryTree<EntryMapping> mappings) {
159 if (project == null) return;
160
161 project.setMappings(mappings);
162 refreshClasses();
163 refreshCurrentClass();
164 }
165
166 public CompletableFuture<Void> saveMappings(Path path) {
167 return saveMappings(path, loadedMappingFormat);
168 }
169
170 public CompletableFuture<Void> saveMappings(Path path, MappingFormat format) {
171 if (project == null) return CompletableFuture.completedFuture(null);
172
173 return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> {
174 EntryRemapper mapper = project.getMapper();
175 MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters();
176
177 MappingDelta<EntryMapping> delta = mapper.takeMappingDelta();
178 boolean saveAll = !path.equals(loadedMappingPath);
179
180 loadedMappingFormat = format;
181 loadedMappingPath = path;
182
183 if (saveAll) {
184 format.write(mapper.getObfToDeobf(), path, progress, saveParameters);
185 } else {
186 format.write(mapper.getObfToDeobf(), delta, path, progress, saveParameters);
187 }
188 });
189 }
190
191 public void closeMappings() {
192 if (project == null) return;
193
194 project.setMappings(null);
195
196 this.gui.setMappingsFile(null);
197 refreshClasses();
198 refreshCurrentClass();
199 }
200
201 public CompletableFuture<Void> dropMappings() {
202 if (project == null) return CompletableFuture.completedFuture(null);
203
204 return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> project.dropMappings(progress));
205 }
206
207 public CompletableFuture<Void> exportSource(final Path path) {
208 if (project == null) return CompletableFuture.completedFuture(null);
209
210 return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> {
211 EnigmaProject.JarExport jar = project.exportRemappedJar(progress);
212 EnigmaProject.SourceExport source = jar.decompile(progress, decompilerService);
213
214 source.write(path, progress);
215 });
216 }
217
218 public CompletableFuture<Void> exportJar(final Path path) {
219 if (project == null) return CompletableFuture.completedFuture(null);
220
221 return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> {
222 EnigmaProject.JarExport jar = project.exportRemappedJar(progress);
223 jar.write(path, progress);
224 });
225 }
226
227 public Token getToken(int pos) {
228 if (this.currentSource == null) {
229 return null;
230 }
231 return this.currentSource.getIndex().getReferenceToken(pos);
232 }
233
234 @Nullable
235 public EntryReference<Entry<?>, Entry<?>> getReference(Token token) {
236 if (this.currentSource == null) {
237 return null;
238 }
239 return this.currentSource.getIndex().getReference(token);
240 }
241
242 public ReadableToken getReadableToken(Token token) {
243 if (this.currentSource == null) {
244 return null;
245 }
246
247 SourceIndex index = this.currentSource.getIndex();
248 return new ReadableToken(
249 index.getLineNumber(token.start),
250 index.getColumnNumber(token.start),
251 index.getColumnNumber(token.end)
252 );
253 }
254
255 /**
256 * Navigates to the declaration with respect to navigation history
257 *
258 * @param entry the entry whose declaration will be navigated to
259 */
260 public void openDeclaration(Entry<?> entry) {
261 if (entry == null) {
262 throw new IllegalArgumentException("Entry cannot be null!");
263 }
264 openReference(new EntryReference<>(entry, entry.getName()));
265 }
266
267 /**
268 * Navigates to the reference with respect to navigation history
269 *
270 * @param reference the reference
271 */
272 public void openReference(EntryReference<Entry<?>, Entry<?>> reference) {
273 if (reference == null) {
274 throw new IllegalArgumentException("Reference cannot be null!");
275 }
276 if (this.gui.referenceHistory == null) {
277 this.gui.referenceHistory = new History<>(reference);
278 } else {
279 if (!reference.equals(this.gui.referenceHistory.getCurrent())) {
280 this.gui.referenceHistory.push(reference);
281 }
282 }
283 setReference(reference);
284 }
285
286 /**
287 * Navigates to the reference without modifying history. If the class is not currently loaded, it will be loaded.
288 *
289 * @param reference the reference
290 */
291 private void setReference(EntryReference<Entry<?>, Entry<?>> reference) {
292 // get the reference target class
293 ClassEntry classEntry = reference.getLocationClassEntry();
294 if (!project.isRenamable(classEntry)) {
295 throw new IllegalArgumentException("Obfuscated class " + classEntry + " was not found in the jar!");
296 }
297
298 if (this.currentSource == null || !this.currentSource.getEntry().equals(classEntry)) {
299 // deobfuscate the class, then navigate to the reference
300 loadClass(classEntry, () -> showReference(reference));
301 } else {
302 showReference(reference);
303 }
304 }
305
306 /**
307 * Navigates to the reference without modifying history. Assumes the class is loaded.
308 *
309 * @param reference
310 */
311 private void showReference(EntryReference<Entry<?>, Entry<?>> reference) {
312 Collection<Token> tokens = getTokensForReference(reference);
313 if (tokens.isEmpty()) {
314 // DEBUG
315 System.err.println(String.format("WARNING: no tokens found for %s in %s", reference, this.currentSource.getEntry()));
316 } else {
317 this.gui.showTokens(tokens);
318 }
319 }
320
321 public Collection<Token> getTokensForReference(EntryReference<Entry<?>, Entry<?>> reference) {
322 EntryRemapper mapper = this.project.getMapper();
323
324 SourceIndex index = this.currentSource.getIndex();
325 return mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_CLOSEST)
326 .stream()
327 .flatMap(r -> index.getReferenceTokens(r).stream())
328 .collect(Collectors.toList());
329 }
330
331 public void openPreviousReference() {
332 if (hasPreviousReference()) {
333 setReference(gui.referenceHistory.goBack());
334 }
335 }
336
337 public boolean hasPreviousReference() {
338 return gui.referenceHistory != null && gui.referenceHistory.canGoBack();
339 }
340
341 public void openNextReference() {
342 if (hasNextReference()) {
343 setReference(gui.referenceHistory.goForward());
344 }
345 }
346
347 public boolean hasNextReference() {
348 return gui.referenceHistory != null && gui.referenceHistory.canGoForward();
349 }
350
351 public void navigateTo(Entry<?> entry) {
352 if (!project.isRenamable(entry)) {
353 // entry is not in the jar. Ignore it
354 return;
355 }
356 openDeclaration(entry);
357 }
358
359 public void navigateTo(EntryReference<Entry<?>, Entry<?>> reference) {
360 if (!project.isRenamable(reference.getLocationClassEntry())) {
361 return;
362 }
363 openReference(reference);
364 }
365
366 private void refreshClasses() {
367 List<ClassEntry> obfClasses = Lists.newArrayList();
368 List<ClassEntry> deobfClasses = Lists.newArrayList();
369 this.addSeparatedClasses(obfClasses, deobfClasses);
370 this.gui.setObfClasses(obfClasses);
371 this.gui.setDeobfClasses(deobfClasses);
372 }
373
374 public void addSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) {
375 EntryRemapper mapper = project.getMapper();
376
377 Collection<ClassEntry> classes = project.getJarIndex().getEntryIndex().getClasses();
378 Stream<ClassEntry> visibleClasses = classes.stream()
379 .filter(entry -> !entry.isInnerClass());
380
381 visibleClasses.forEach(entry -> {
382 ClassEntry deobfEntry = mapper.deobfuscate(entry);
383
384 List<ObfuscationTestService> obfService = enigma.getServices().get(ObfuscationTestService.TYPE);
385 boolean obfuscated = deobfEntry.equals(entry);
386
387 if (obfuscated && !obfService.isEmpty()) {
388 if (obfService.stream().anyMatch(service -> service.testDeobfuscated(entry))) {
389 obfuscated = false;
390 }
391 }
392
393 if (obfuscated) {
394 obfClasses.add(entry);
395 } else {
396 deobfClasses.add(entry);
397 }
398 });
399 }
400
401 public void refreshCurrentClass() {
402 refreshCurrentClass(null);
403 }
404
405 private void refreshCurrentClass(EntryReference<Entry<?>, Entry<?>> reference) {
406 refreshCurrentClass(reference, RefreshMode.MINIMAL);
407 }
408
409 private void refreshCurrentClass(EntryReference<Entry<?>, Entry<?>> reference, RefreshMode mode) {
410 if (currentSource != null) {
411 if (reference == null) {
412 int obfSelectionStart = currentSource.getObfuscatedOffset(gui.editor.getSelectionStart());
413 int obfSelectionEnd = currentSource.getObfuscatedOffset(gui.editor.getSelectionEnd());
414
415 Rectangle viewportBounds = gui.sourceScroller.getViewport().getViewRect();
416 // Here we pick an "anchor position", which we want to stay in the same vertical location on the screen after the new text has been set
417 int anchorModelPos = gui.editor.getSelectionStart();
418 Rectangle anchorViewPos = Utils.safeModelToView(gui.editor, anchorModelPos);
419 if (anchorViewPos.y < viewportBounds.y || anchorViewPos.y >= viewportBounds.y + viewportBounds.height) {
420 anchorModelPos = gui.editor.viewToModel(new Point(0, viewportBounds.y));
421 anchorViewPos = Utils.safeModelToView(gui.editor, anchorModelPos);
422 }
423 int obfAnchorPos = currentSource.getObfuscatedOffset(anchorModelPos);
424 Rectangle anchorViewPos_f = anchorViewPos;
425 int scrollX = gui.sourceScroller.getHorizontalScrollBar().getValue();
426
427 loadClass(currentSource.getEntry(), () -> SwingUtilities.invokeLater(() -> {
428 int newAnchorModelPos = currentSource.getDeobfuscatedOffset(obfAnchorPos);
429 Rectangle newAnchorViewPos = Utils.safeModelToView(gui.editor, newAnchorModelPos);
430 int newScrollY = newAnchorViewPos.y - (anchorViewPos_f.y - viewportBounds.y);
431
432 gui.editor.select(currentSource.getDeobfuscatedOffset(obfSelectionStart), currentSource.getDeobfuscatedOffset(obfSelectionEnd));
433 // Changing the selection scrolls to the caret position inside a SwingUtilities.invokeLater call, so
434 // we need to wrap our change to the scroll position inside another invokeLater so it happens after
435 // the caret's own scrolling.
436 SwingUtilities.invokeLater(() -> {
437 gui.sourceScroller.getHorizontalScrollBar().setValue(Math.min(scrollX, gui.sourceScroller.getHorizontalScrollBar().getMaximum()));
438 gui.sourceScroller.getVerticalScrollBar().setValue(Math.min(newScrollY, gui.sourceScroller.getVerticalScrollBar().getMaximum()));
439 });
440 }), mode);
441 } else {
442 loadClass(currentSource.getEntry(), () -> showReference(reference), mode);
443 }
444 }
445 }
446
447 private void loadClass(ClassEntry classEntry, Runnable callback) {
448 loadClass(classEntry, callback, RefreshMode.MINIMAL);
449 }
450
451 private void loadClass(ClassEntry classEntry, Runnable callback, RefreshMode mode) {
452 ClassEntry targetClass = classEntry.getOutermostClass();
453
454 boolean requiresDecompile = mode == RefreshMode.FULL || currentSource == null || !currentSource.getEntry().equals(targetClass);
455 if (requiresDecompile) {
456 currentSource = null; // Or the GUI may try to find a nonexistent token
457 gui.setEditorText(I18n.translate("info_panel.editor.class.decompiling"));
458 }
459
460 DECOMPILER_SERVICE.submit(() -> {
461 try {
462 if (requiresDecompile || mode == RefreshMode.JAVADOCS) {
463 currentSource = decompileSource(targetClass, mode == RefreshMode.JAVADOCS);
464 }
465
466 remapSource(project.getMapper().getDeobfuscator());
467 callback.run();
468 } catch (Throwable t) {
469 System.err.println("An exception was thrown while decompiling class " + classEntry.getFullName());
470 t.printStackTrace(System.err);
471 }
472 });
473 }
474
475 private DecompiledClassSource decompileSource(ClassEntry targetClass, boolean onlyRefreshJavadocs) {
476 try {
477 if (!onlyRefreshJavadocs || currentSource == null || !currentSource.getEntry().equals(targetClass)) {
478 uncommentedSource = decompiler.getSource(targetClass.getFullName());
479 }
480
481 Source source = uncommentedSource.addJavadocs(project.getMapper());
482
483 if (source == null) {
484 gui.setEditorText(I18n.translate("info_panel.editor.class.not_found") + " " + targetClass);
485 return DecompiledClassSource.text(targetClass, "Unable to find class");
486 }
487
488 SourceIndex index = source.index();
489 index.resolveReferences(project.getMapper().getObfResolver());
490
491 return new DecompiledClassSource(targetClass, index);
492 } catch (Throwable t) {
493 StringWriter traceWriter = new StringWriter();
494 t.printStackTrace(new PrintWriter(traceWriter));
495
496 return DecompiledClassSource.text(targetClass, traceWriter.toString());
497 }
498 }
499
500 private void remapSource(Translator translator) {
501 if (currentSource == null) {
502 return;
503 }
504
505 currentSource.remapSource(project, translator);
506
507 gui.setEditorTheme(Config.getInstance().lookAndFeel);
508 gui.setSource(currentSource);
509 }
510
511 public void modifierChange(ItemEvent event) {
512 if (event.getStateChange() == ItemEvent.SELECTED) {
513 EntryRemapper mapper = project.getMapper();
514 Entry<?> entry = gui.cursorReference.entry;
515 AccessModifier modifier = (AccessModifier) event.getItem();
516
517 EntryMapping mapping = mapper.getDeobfMapping(entry);
518 if (mapping != null) {
519 mapper.mapFromObf(entry, new EntryMapping(mapping.getTargetName(), modifier));
520 } else {
521 mapper.mapFromObf(entry, new EntryMapping(entry.getName(), modifier));
522 }
523
524 refreshCurrentClass();
525 }
526 }
527
528 public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) {
529 Translator translator = project.getMapper().getDeobfuscator();
530 ClassInheritanceTreeNode rootNode = indexTreeBuilder.buildClassInheritance(translator, entry);
531 return ClassInheritanceTreeNode.findNode(rootNode, entry);
532 }
533
534 public ClassImplementationsTreeNode getClassImplementations(ClassEntry entry) {
535 Translator translator = project.getMapper().getDeobfuscator();
536 return this.indexTreeBuilder.buildClassImplementations(translator, entry);
537 }
538
539 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry entry) {
540 Translator translator = project.getMapper().getDeobfuscator();
541 MethodInheritanceTreeNode rootNode = indexTreeBuilder.buildMethodInheritance(translator, entry);
542 return MethodInheritanceTreeNode.findNode(rootNode, entry);
543 }
544
545 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry entry) {
546 Translator translator = project.getMapper().getDeobfuscator();
547 List<MethodImplementationsTreeNode> rootNodes = indexTreeBuilder.buildMethodImplementations(translator, entry);
548 if (rootNodes.isEmpty()) {
549 return null;
550 }
551 if (rootNodes.size() > 1) {
552 System.err.println("WARNING: Method " + entry + " implements multiple interfaces. Only showing first one.");
553 }
554 return MethodImplementationsTreeNode.findNode(rootNodes.get(0), entry);
555 }
556
557 public ClassReferenceTreeNode getClassReferences(ClassEntry entry) {
558 Translator deobfuscator = project.getMapper().getDeobfuscator();
559 ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, entry);
560 rootNode.load(project.getJarIndex(), true);
561 return rootNode;
562 }
563
564 public FieldReferenceTreeNode getFieldReferences(FieldEntry entry) {
565 Translator translator = project.getMapper().getDeobfuscator();
566 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, entry);
567 rootNode.load(project.getJarIndex(), true);
568 return rootNode;
569 }
570
571 public MethodReferenceTreeNode getMethodReferences(MethodEntry entry, boolean recursive) {
572 Translator translator = project.getMapper().getDeobfuscator();
573 MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, entry);
574 rootNode.load(project.getJarIndex(), true, recursive);
575 return rootNode;
576 }
577
578 public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree) {
579 rename(reference, newName, refreshClassTree, true);
580 }
581
582 public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree, boolean jumpToReference) {
583 Entry<?> entry = reference.getNameableEntry();
584 project.getMapper().mapFromObf(entry, new EntryMapping(newName));
585
586 if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass())
587 this.gui.moveClassTree(reference, newName);
588
589 refreshCurrentClass(jumpToReference ? reference : null);
590 }
591
592 public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference) {
593 removeMapping(reference, true);
594 }
595
596 public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference) {
597 project.getMapper().removeByObf(reference.getNameableEntry());
598
599 if (reference.entry instanceof ClassEntry)
600 this.gui.moveClassTree(reference, false, true);
601 refreshCurrentClass(jumpToReference ? reference : null);
602 }
603
604 public void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs) {
605 changeDocs(reference, updatedDocs, true);
606 }
607
608 public void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs, boolean jumpToReference) {
609 changeDoc(reference.entry, Utils.isBlank(updatedDocs) ? null : updatedDocs);
610
611 refreshCurrentClass(jumpToReference ? reference : null, RefreshMode.JAVADOCS);
612 }
613
614 private void changeDoc(Entry<?> obfEntry, String newDoc) {
615 EntryRemapper mapper = project.getMapper();
616 if (mapper.getDeobfMapping(obfEntry) == null) {
617 markAsDeobfuscated(obfEntry, false); // NPE
618 }
619 mapper.mapFromObf(obfEntry, mapper.getDeobfMapping(obfEntry).withDocs(newDoc), false);
620 }
621
622 private void markAsDeobfuscated(Entry<?> obfEntry, boolean renaming) {
623 EntryRemapper mapper = project.getMapper();
624 mapper.mapFromObf(obfEntry, new EntryMapping(mapper.deobfuscate(obfEntry).getName()), renaming);
625 }
626
627 public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference) {
628 markAsDeobfuscated(reference, true);
629 }
630
631 public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference) {
632 EntryRemapper mapper = project.getMapper();
633 Entry<?> entry = reference.getNameableEntry();
634 mapper.mapFromObf(entry, new EntryMapping(mapper.deobfuscate(entry).getName()));
635
636 if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass())
637 this.gui.moveClassTree(reference, true, false);
638
639 refreshCurrentClass(jumpToReference ? reference : null);
640 }
641
642 public void openStats(Set<StatsMember> includedMembers) {
643 ProgressDialog.runOffThread(gui.getFrame(), progress -> {
644 String data = new StatsGenerator(project).generate(progress, includedMembers);
645
646 try {
647 File statsFile = File.createTempFile("stats", ".html");
648
649 try (FileWriter w = new FileWriter(statsFile)) {
650 w.write(
651 Utils.readResourceToString("/stats.html")
652 .replace("/*data*/", data)
653 );
654 }
655
656 Desktop.getDesktop().open(statsFile);
657 } catch (IOException e) {
658 throw new Error(e);
659 }
660 });
661 }
662
663 public void setDecompiler(DecompilerService service) {
664 uncommentedSource = null;
665 decompilerService = service;
666 decompiler = createDecompiler();
667 refreshCurrentClass(null, RefreshMode.FULL);
668 }
669
670 public EnigmaClient getClient() {
671 return client;
672 }
673
674 public EnigmaServer getServer() {
675 return server;
676 }
677
678 public void createClient(String username, String ip, int port, char[] password) throws IOException {
679 client = new EnigmaClient(this, ip, port);
680 client.connect();
681 client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, username));
682 gui.setConnectionState(ConnectionState.CONNECTED);
683 }
684
685 public void createServer(int port, char[] password) throws IOException {
686 server = new IntegratedEnigmaServer(project.getJarChecksum(), password, EntryRemapper.mapped(project.getJarIndex(), new HashEntryTree<>(project.getMapper().getObfToDeobf())), port);
687 server.start();
688 client = new EnigmaClient(this, "127.0.0.1", port);
689 client.connect();
690 client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, EnigmaServer.OWNER_USERNAME));
691 gui.setConnectionState(ConnectionState.HOSTING);
692 }
693
694 public synchronized void disconnectIfConnected(String reason) {
695 if (client == null && server == null) {
696 return;
697 }
698
699 if (client != null) {
700 client.disconnect();
701 }
702 if (server != null) {
703 server.stop();
704 }
705 client = null;
706 server = null;
707 SwingUtilities.invokeLater(() -> {
708 if (reason != null) {
709 JOptionPane.showMessageDialog(gui.getFrame(), I18n.translate(reason), I18n.translate("disconnect.disconnected"), JOptionPane.INFORMATION_MESSAGE);
710 }
711 gui.setConnectionState(ConnectionState.NOT_CONNECTED);
712 });
713 }
714
715 public void sendPacket(Packet<ServerPacketHandler> packet) {
716 if (client != null) {
717 client.sendPacket(packet);
718 }
719 }
720
721 public void addMessage(Message message) {
722 gui.addMessage(message);
723 }
724
725 public void updateUserList(List<String> users) {
726 gui.setUserList(users);
727 }
728
729}