diff options
| author | 2020-05-03 21:06:38 +0100 | |
|---|---|---|
| committer | 2020-05-03 21:06:38 +0100 | |
| commit | 854f4d49407e45d67dd5754afd21a7e59970ca5b (patch) | |
| tree | 582e3245786f9723b5895b0c8d41087b0e6bb416 /src/main/java/cuchaz/enigma/gui/GuiController.java | |
| parent | Rewrite search dialog (#233) (diff) | |
| download | enigma-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.java | 150 |
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; | |||
| 24 | import cuchaz.enigma.gui.stats.StatsGenerator; | 24 | import cuchaz.enigma.gui.stats.StatsGenerator; |
| 25 | import cuchaz.enigma.gui.stats.StatsMember; | 25 | import cuchaz.enigma.gui.stats.StatsMember; |
| 26 | import cuchaz.enigma.gui.util.History; | 26 | import cuchaz.enigma.gui.util.History; |
| 27 | import cuchaz.enigma.network.EnigmaClient; | ||
| 28 | import cuchaz.enigma.network.EnigmaServer; | ||
| 29 | import cuchaz.enigma.network.IntegratedEnigmaServer; | ||
| 30 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 31 | import cuchaz.enigma.network.packet.LoginC2SPacket; | ||
| 32 | import cuchaz.enigma.network.packet.Packet; | ||
| 27 | import cuchaz.enigma.source.*; | 33 | import cuchaz.enigma.source.*; |
| 28 | import cuchaz.enigma.throwables.MappingParseException; | 34 | import cuchaz.enigma.throwables.MappingParseException; |
| 29 | import cuchaz.enigma.translation.Translator; | 35 | import cuchaz.enigma.translation.Translator; |
| 30 | import cuchaz.enigma.translation.mapping.*; | 36 | import cuchaz.enigma.translation.mapping.*; |
| 31 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | 37 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; |
| 32 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | 38 | import cuchaz.enigma.translation.mapping.tree.EntryTree; |
| 39 | import cuchaz.enigma.translation.mapping.tree.HashEntryTree; | ||
| 33 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | 40 | import cuchaz.enigma.translation.representation.entry.ClassEntry; |
| 34 | import cuchaz.enigma.translation.representation.entry.Entry; | 41 | import cuchaz.enigma.translation.representation.entry.Entry; |
| 35 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | 42 | import cuchaz.enigma.translation.representation.entry.FieldEntry; |
| 36 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | 43 | import cuchaz.enigma.translation.representation.entry.MethodEntry; |
| 37 | import cuchaz.enigma.utils.I18n; | 44 | import cuchaz.enigma.utils.I18n; |
| 45 | import cuchaz.enigma.utils.Message; | ||
| 38 | import cuchaz.enigma.utils.ReadableToken; | 46 | import cuchaz.enigma.utils.ReadableToken; |
| 39 | import cuchaz.enigma.utils.Utils; | 47 | import cuchaz.enigma.utils.Utils; |
| 40 | import org.objectweb.asm.tree.ClassNode; | 48 | import org.objectweb.asm.tree.ClassNode; |
| 41 | 49 | ||
| 42 | import javax.annotation.Nullable; | 50 | import javax.annotation.Nullable; |
| 43 | import javax.swing.JOptionPane; | 51 | import javax.swing.JOptionPane; |
| 44 | import java.awt.Desktop; | 52 | import javax.swing.SwingUtilities; |
| 53 | import java.awt.*; | ||
| 45 | import java.awt.event.ItemEvent; | 54 | import java.awt.event.ItemEvent; |
| 46 | import java.io.*; | 55 | import java.io.*; |
| 47 | import java.nio.file.Path; | 56 | import 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 | } |