summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/gui/GuiController.java
diff options
context:
space:
mode:
authorGravatar Joseph Burton2020-05-03 21:06:38 +0100
committerGravatar GitHub2020-05-03 21:06:38 +0100
commit854f4d49407e45d67dd5754afd21a7e59970ca5b (patch)
tree582e3245786f9723b5895b0c8d41087b0e6bb416 /src/main/java/cuchaz/enigma/gui/GuiController.java
parentRewrite search dialog (#233) (diff)
downloadenigma-fork-854f4d49407e45d67dd5754afd21a7e59970ca5b.tar.gz
enigma-fork-854f4d49407e45d67dd5754afd21a7e59970ca5b.tar.xz
enigma-fork-854f4d49407e45d67dd5754afd21a7e59970ca5b.zip
Multiplayer support (#221)
* First pass on multiplayer * Apply review suggestions * Dedicated Enigma server * Don't jump to references when other users do stuff * Better UI + translations * french translation * Apply review suggestions * Document the protocol * Fix most issues with scrolling. * Apply review suggestions * Fix zip hash issues + add a bit more logging * Optimize zip hash * Fix a couple of login bugs * Add message log and user list * Make Message an abstract class * Make status bar work, add chat box * Hide message log/users list when not connected * Fix status bar not resetting entirely * Run stop server task on server thread to prevent multithreading race conditions * Add c2s message to packet id list * Fix message scroll bar not scrolling to the end * Formatting * User list size -> ushort * Combine contains and remove check * Check removal before sending packet * Add password to login packet * Fix the GUI closing the rename text field when someone else renames something * Update fr_fr.json * oups * Make connection/server create dialogs not useless if it fails once * Refactor UI state updating * Fix imports * Fix Collab menu * Fix NPE when rename not allowed * Make the log file a configurable option * Don't use modified UTF * Update fr_fr.json * Bump version to 0.15.4 * Apparently I can't spell neither words nor semantic versions Co-authored-by: Yanis48 <doublecraft.official@gmail.com> Co-authored-by: 2xsaiko <git@dblsaiko.net>
Diffstat (limited to 'src/main/java/cuchaz/enigma/gui/GuiController.java')
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiController.java150
1 files changed, 137 insertions, 13 deletions
diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java
index 742d6b8..cccc9e8 100644
--- a/src/main/java/cuchaz/enigma/gui/GuiController.java
+++ b/src/main/java/cuchaz/enigma/gui/GuiController.java
@@ -24,24 +24,33 @@ import cuchaz.enigma.gui.dialog.ProgressDialog;
24import cuchaz.enigma.gui.stats.StatsGenerator; 24import cuchaz.enigma.gui.stats.StatsGenerator;
25import cuchaz.enigma.gui.stats.StatsMember; 25import cuchaz.enigma.gui.stats.StatsMember;
26import cuchaz.enigma.gui.util.History; 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;
27import cuchaz.enigma.source.*; 33import cuchaz.enigma.source.*;
28import cuchaz.enigma.throwables.MappingParseException; 34import cuchaz.enigma.throwables.MappingParseException;
29import cuchaz.enigma.translation.Translator; 35import cuchaz.enigma.translation.Translator;
30import cuchaz.enigma.translation.mapping.*; 36import cuchaz.enigma.translation.mapping.*;
31import cuchaz.enigma.translation.mapping.serde.MappingFormat; 37import cuchaz.enigma.translation.mapping.serde.MappingFormat;
32import cuchaz.enigma.translation.mapping.tree.EntryTree; 38import cuchaz.enigma.translation.mapping.tree.EntryTree;
39import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
33import cuchaz.enigma.translation.representation.entry.ClassEntry; 40import cuchaz.enigma.translation.representation.entry.ClassEntry;
34import cuchaz.enigma.translation.representation.entry.Entry; 41import cuchaz.enigma.translation.representation.entry.Entry;
35import cuchaz.enigma.translation.representation.entry.FieldEntry; 42import cuchaz.enigma.translation.representation.entry.FieldEntry;
36import cuchaz.enigma.translation.representation.entry.MethodEntry; 43import cuchaz.enigma.translation.representation.entry.MethodEntry;
37import cuchaz.enigma.utils.I18n; 44import cuchaz.enigma.utils.I18n;
45import cuchaz.enigma.utils.Message;
38import cuchaz.enigma.utils.ReadableToken; 46import cuchaz.enigma.utils.ReadableToken;
39import cuchaz.enigma.utils.Utils; 47import cuchaz.enigma.utils.Utils;
40import org.objectweb.asm.tree.ClassNode; 48import org.objectweb.asm.tree.ClassNode;
41 49
42import javax.annotation.Nullable; 50import javax.annotation.Nullable;
43import javax.swing.JOptionPane; 51import javax.swing.JOptionPane;
44import java.awt.Desktop; 52import javax.swing.SwingUtilities;
53import java.awt.*;
45import java.awt.event.ItemEvent; 54import java.awt.event.ItemEvent;
46import java.io.*; 55import java.io.*;
47import java.nio.file.Path; 56import java.nio.file.Path;
@@ -76,6 +85,9 @@ public class GuiController {
76 private DecompiledClassSource currentSource; 85 private DecompiledClassSource currentSource;
77 private Source uncommentedSource; 86 private Source uncommentedSource;
78 87
88 private EnigmaClient client;
89 private EnigmaServer server;
90
79 public GuiController(Gui gui, EnigmaProfile profile) { 91 public GuiController(Gui gui, EnigmaProfile profile) {
80 this.gui = gui; 92 this.gui = gui;
81 this.enigma = Enigma.builder() 93 this.enigma = Enigma.builder()
@@ -143,6 +155,14 @@ public class GuiController {
143 }); 155 });
144 } 156 }
145 157
158 public void openMappings(EntryTree<EntryMapping> mappings) {
159 if (project == null) return;
160
161 project.setMappings(mappings);
162 refreshClasses();
163 refreshCurrentClass();
164 }
165
146 public CompletableFuture<Void> saveMappings(Path path) { 166 public CompletableFuture<Void> saveMappings(Path path) {
147 return saveMappings(path, loadedMappingFormat); 167 return saveMappings(path, loadedMappingFormat);
148 } 168 }
@@ -388,11 +408,39 @@ public class GuiController {
388 408
389 private void refreshCurrentClass(EntryReference<Entry<?>, Entry<?>> reference, RefreshMode mode) { 409 private void refreshCurrentClass(EntryReference<Entry<?>, Entry<?>> reference, RefreshMode mode) {
390 if (currentSource != null) { 410 if (currentSource != null) {
391 loadClass(currentSource.getEntry(), () -> { 411 if (reference == null) {
392 if (reference != null) { 412 int obfSelectionStart = currentSource.getObfuscatedOffset(gui.editor.getSelectionStart());
393 showReference(reference); 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);
394 } 422 }
395 }, mode); 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 }
396 } 444 }
397 } 445 }
398 446
@@ -528,43 +576,59 @@ public class GuiController {
528 } 576 }
529 577
530 public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree) { 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) {
531 Entry<?> entry = reference.getNameableEntry(); 583 Entry<?> entry = reference.getNameableEntry();
532 project.getMapper().mapFromObf(entry, new EntryMapping(newName)); 584 project.getMapper().mapFromObf(entry, new EntryMapping(newName));
533 585
534 if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) 586 if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass())
535 this.gui.moveClassTree(reference, newName); 587 this.gui.moveClassTree(reference, newName);
536 588
537 refreshCurrentClass(reference); 589 refreshCurrentClass(jumpToReference ? reference : null);
538 } 590 }
539 591
540 public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference) { 592 public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference) {
593 removeMapping(reference, true);
594 }
595
596 public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference) {
541 project.getMapper().removeByObf(reference.getNameableEntry()); 597 project.getMapper().removeByObf(reference.getNameableEntry());
542 598
543 if (reference.entry instanceof ClassEntry) 599 if (reference.entry instanceof ClassEntry)
544 this.gui.moveClassTree(reference, false, true); 600 this.gui.moveClassTree(reference, false, true);
545 refreshCurrentClass(reference); 601 refreshCurrentClass(jumpToReference ? reference : null);
546 } 602 }
547 603
548 public void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs) { 604 public void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs) {
549 changeDoc(reference.entry, updatedDocs); 605 changeDocs(reference, updatedDocs, true);
606 }
550 607
551 refreshCurrentClass(reference, RefreshMode.JAVADOCS); 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);
552 } 612 }
553 613
554 public void changeDoc(Entry<?> obfEntry, String newDoc) { 614 private void changeDoc(Entry<?> obfEntry, String newDoc) {
555 EntryRemapper mapper = project.getMapper(); 615 EntryRemapper mapper = project.getMapper();
556 if (mapper.getDeobfMapping(obfEntry) == null) { 616 if (mapper.getDeobfMapping(obfEntry) == null) {
557 markAsDeobfuscated(obfEntry,false); // NPE 617 markAsDeobfuscated(obfEntry, false); // NPE
558 } 618 }
559 mapper.mapFromObf(obfEntry, mapper.getDeobfMapping(obfEntry).withDocs(newDoc), false); 619 mapper.mapFromObf(obfEntry, mapper.getDeobfMapping(obfEntry).withDocs(newDoc), false);
560 } 620 }
561 621
562 public void markAsDeobfuscated(Entry<?> obfEntry, boolean renaming) { 622 private void markAsDeobfuscated(Entry<?> obfEntry, boolean renaming) {
563 EntryRemapper mapper = project.getMapper(); 623 EntryRemapper mapper = project.getMapper();
564 mapper.mapFromObf(obfEntry, new EntryMapping(mapper.deobfuscate(obfEntry).getName()), renaming); 624 mapper.mapFromObf(obfEntry, new EntryMapping(mapper.deobfuscate(obfEntry).getName()), renaming);
565 } 625 }
566 626
567 public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference) { 627 public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference) {
628 markAsDeobfuscated(reference, true);
629 }
630
631 public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference) {
568 EntryRemapper mapper = project.getMapper(); 632 EntryRemapper mapper = project.getMapper();
569 Entry<?> entry = reference.getNameableEntry(); 633 Entry<?> entry = reference.getNameableEntry();
570 mapper.mapFromObf(entry, new EntryMapping(mapper.deobfuscate(entry).getName())); 634 mapper.mapFromObf(entry, new EntryMapping(mapper.deobfuscate(entry).getName()));
@@ -572,7 +636,7 @@ public class GuiController {
572 if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) 636 if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass())
573 this.gui.moveClassTree(reference, true, false); 637 this.gui.moveClassTree(reference, true, false);
574 638
575 refreshCurrentClass(reference); 639 refreshCurrentClass(jumpToReference ? reference : null);
576 } 640 }
577 641
578 public void openStats(Set<StatsMember> includedMembers) { 642 public void openStats(Set<StatsMember> includedMembers) {
@@ -602,4 +666,64 @@ public class GuiController {
602 decompiler = createDecompiler(); 666 decompiler = createDecompiler();
603 refreshCurrentClass(null, RefreshMode.FULL); 667 refreshCurrentClass(null, RefreshMode.FULL);
604 } 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
605} 729}