From 8efb62490ec153246d467a1a72c513781db887bf Mon Sep 17 00:00:00 2001 From: 2xsaiko Date: Thu, 8 Jul 2021 16:30:39 +0200 Subject: Entry Changes (#364) * Initial refactor: Allow EntryMapping to have null targetName, add EntryChange in favor of entry changing methods in GuiController * Fix resetting name not actually removing it, and renaming a class causing name collisions with itself Closes #246. * Use name proposer for setting default deobf name Closes #314 * Make network protocol use EntryChange directly * Handle writing other data correctly when the deobf name is null * b * Add some new abstraction stuff * Use pattern matching instanceof * Move classes out of newabstraction package * Make EntryChange final * Regenerate equals and hashCode * Convert EntryMapping to record * Make TristateChange final * Safety guard null accessModifier initialization--- enigma-server/docs/protocol.md | 164 ++++++++------------- .../cuchaz/enigma/network/ClientPacketHandler.java | 12 +- .../java/cuchaz/enigma/network/EnigmaServer.java | 9 +- .../enigma/network/packet/ChangeDocsC2SPacket.java | 64 -------- .../enigma/network/packet/ChangeDocsS2CPacket.java | 51 ------- .../network/packet/EntryChangeC2SPacket.java | 66 +++++++++ .../network/packet/EntryChangeS2CPacket.java | 42 ++++++ .../network/packet/MarkDeobfuscatedC2SPacket.java | 56 ------- .../network/packet/MarkDeobfuscatedS2CPacket.java | 47 ------ .../cuchaz/enigma/network/packet/PacketHelper.java | 130 +++++++++++----- .../enigma/network/packet/PacketRegistry.java | 16 +- .../network/packet/RemoveMappingC2SPacket.java | 56 ------- .../network/packet/RemoveMappingS2CPacket.java | 47 ------ .../enigma/network/packet/RenameC2SPacket.java | 66 --------- .../enigma/network/packet/RenameS2CPacket.java | 55 ------- .../network/packet/SyncMappingsS2CPacket.java | 41 ++---- .../src/main/java/cuchaz/enigma/gui/Gui.java | 22 ++- .../main/java/cuchaz/enigma/gui/GuiController.java | 99 +++++-------- .../cuchaz/enigma/gui/dialog/JavadocDialog.java | 22 +-- .../enigma/gui/newabstraction/EntryValidation.java | 22 +++ .../java/cuchaz/enigma/gui/panels/DeobfPanel.java | 2 +- .../cuchaz/enigma/gui/panels/IdentifierPanel.java | 19 +-- .../java/cuchaz/enigma/gui/panels/ObfPanel.java | 2 +- .../enigma/source/DecompiledClassSource.java | 2 +- .../cuchaz/enigma/source/cfr/EnigmaDumper.java | 12 +- .../transformers/AddJavadocsAstTransform.java | 7 +- .../enigma/translation/mapping/EntryChange.java | 97 ++++++++++++ .../enigma/translation/mapping/EntryMapping.java | 67 +++------ .../enigma/translation/mapping/EntryRemapper.java | 53 ++++--- .../enigma/translation/mapping/EntryUtil.java | 42 ++++++ .../translation/mapping/MappingValidator.java | 13 +- .../translation/mapping/MappingsChecker.java | 2 +- .../translation/mapping/serde/RawEntryMapping.java | 12 +- .../mapping/serde/enigma/EnigmaMappingsReader.java | 56 +++---- .../mapping/serde/enigma/EnigmaMappingsWriter.java | 96 ++++++------ .../mapping/serde/tiny/TinyMappingsWriter.java | 9 +- .../mapping/serde/tinyv2/TinyV2Writer.java | 46 +++--- .../translation/mapping/tree/HashEntryTree.java | 2 + .../enigma/translation/representation/Lambda.java | 6 +- .../representation/entry/ClassDefEntry.java | 11 +- .../representation/entry/ClassEntry.java | 8 +- .../translation/representation/entry/Entry.java | 28 +++- .../representation/entry/FieldDefEntry.java | 14 +- .../representation/entry/FieldEntry.java | 10 +- .../entry/LocalVariableDefEntry.java | 10 +- .../representation/entry/LocalVariableEntry.java | 10 +- .../representation/entry/MethodDefEntry.java | 12 +- .../representation/entry/MethodEntry.java | 10 +- .../representation/entry/ParentedEntry.java | 5 +- .../java/cuchaz/enigma/utils/TristateChange.java | 78 ++++++++++ .../enigma/utils/validation/PrintValidatable.java | 10 +- .../enigma/utils/validation/ValidationContext.java | 14 ++ .../translation/mapping/TestReadWriteCycle.java | 20 +-- 53 files changed, 872 insertions(+), 1000 deletions(-) delete mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java delete mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/EntryChangeC2SPacket.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/EntryChangeS2CPacket.java delete mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java delete mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java delete mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java delete mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java delete mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java delete mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/newabstraction/EntryValidation.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryChange.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryUtil.java create mode 100644 enigma/src/main/java/cuchaz/enigma/utils/TristateChange.java diff --git a/enigma-server/docs/protocol.md b/enigma-server/docs/protocol.md index c14ecb81..83ef4c01 100644 --- a/enigma-server/docs/protocol.md +++ b/enigma-server/docs/protocol.md @@ -75,21 +75,15 @@ struct Packet { The IDs for client-to-server packets are as follows: - 0: `Login` - 1: `ConfirmChange` -- 2: `Rename` -- 3: `RemoveMapping` -- 4: `ChangeDocs` -- 5: `MarkDeobfuscated` - 6: `Message` +- 7: `EntryChange` The IDs for server-to-client packets are as follows: - 0: `Kick` - 1: `SyncMappings` -- 2: `Rename` -- 3: `RemoveMapping` -- 4: `ChangeDocs` -- 5: `MarkDeobfuscated` - 6: `Message` - 7: `UserList` +- 8: `EntryChange` ### The utf struct ```c @@ -196,6 +190,45 @@ struct Message { - `entry`: The entry that was modified. - `new_name`: The new name for the entry. +### The entry_change struct +```c +typedef enum tristate_change { + TRISTATE_CHANGE_UNCHANGED = 0, + TRISTATE_CHANGE_RESET = 1, + TRISTATE_CHANGE_SET = 2 +} tristate_change_t; + +typedef enum access_modifier { + ACCESS_MODIFIER_UNCHANGED = 0, + ACCESS_MODIFIER_PUBLIC = 1, + ACCESS_MODIFIER_PROTECTED = 2, + ACCESS_MODIFIER_PRIVATE = 3 +} access_modifier_t; + +// Contains 4 packed values: +// bitmask type +// 00000011 tristate_change_t deobf_name_change; +// 00001100 tristate_change_t access_change; +// 00110000 tristate_change_t javadoc_change; +// 11000000 access_modifier_t access_modifiers; +typedef uint8_t entry_change_flags; + +struct entry_change { + Entry entry; + entry_change_flags flags; + if { + utf deobf_name; + } + if { + utf javadoc; + } +} +``` +- `entry`: The entry this change gets applied to. +- `flags`: See definition of `entry_change_flags`. +- `deobf_name`: The new deobfuscated name, if deobf_name_change == TRISTATE_CHANGE_SET +- `javadoc`: The new javadoc, if javadoc_change == TRISTATE_CHANGE_SET +- `access_modifiers`: The new access modifier, if access_change == TRISTATE_CHANGE_SET (otherwise 0) ### Login (client-to-server) ```c @@ -223,44 +256,6 @@ struct ConfirmChangeC2SPacket { ``` - `sync_id`: the sync ID to confirm. -### Rename (client-to-server) -```c -struct RenameC2SPacket { - Entry obf_entry; - utf new_name; - boolean refresh_class_tree; -} -``` -- `obf_entry`: the obfuscated name and descriptor of the entry to rename. -- `new_name`: what to rename the entry to. -- `refresh_class_tree`: whether the class tree on the sidebar of Enigma needs refreshing as a result of this change. - -### RemoveMapping (client-to-server) -```c -struct RemoveMappingC2SPacket { - Entry obf_entry; -} -``` -- `obf_entry`: the obfuscated name and descriptor of the entry to remove the mapping for. - -### ChangeDocs (client-to-server) -```c -struct ChangeDocsC2SPacket { - Entry obf_entry; - utf new_docs; -} -``` -- `obf_entry`: the obfuscated name and descriptor of the entry to change the documentation for. -- `new_docs`: the new documentation for this entry, or an empty string to remove the documentation. - -### MarkDeobfuscated (client-to-server) -```c -struct MarkDeobfuscatedC2SPacket { - Entry obf_entry; -} -``` -- `obf_entry`: the obfuscated name and descriptor of the entry to mark as deobfuscated. - ### Message (client-to-server) ```c struct MessageC2SPacket { @@ -269,6 +264,14 @@ struct MessageC2SPacket { ``` - `message`: The text message the user sent. +### EntryChange (client-to-server) +```c +struct EntryChangeC2SPacket { + entry_change change; +} +``` +- `change`: The change to apply. + ### Kick (server-to-client) ```c struct KickS2CPacket { @@ -286,13 +289,8 @@ struct SyncMappingsS2CPacket { struct MappingNode { NoParentEntry obf_entry; boolean is_named; - if { - utf name; - boolean has_javadoc; - if { - utf javadoc; - } - } + utf name; + utf javadoc; unsigned short children_count; MappingNode children[children_count]; } @@ -300,56 +298,10 @@ typedef { Entry but without the has_parent or parent fields } NoParentEntry; ``` - `roots`: The root mapping nodes, containing all the entries without parents. - `obf_entry`: The value of a node, containing the obfuscated name and descriptor of the entry. -- `name`: The deobfuscated name of the entry, if it has a mapping. -- `javadoc`: The documentation for the entry, if it is named and has documentation. +- `name`: The deobfuscated name of the entry, if it exists, otherwise the empty string. +- `javadoc`: The documentation for the entry, if it exists, otherwise the empty string. - `children`: The children of this node -### Rename (server-to-client) -```c -struct RenameS2CPacket { - unsigned short sync_id; - Entry obf_entry; - utf new_name; - boolean refresh_class_tree; -} -``` -- `sync_id`: the sync ID of the change for locking purposes. -- `obf_entry`: the obfuscated name and descriptor of the entry to rename. -- `new_name`: what to rename the entry to. -- `refresh_class_tree`: whether the class tree on the sidebar of Enigma needs refreshing as a result of this change. - -### RemoveMapping (server-to-client) -```c -struct RemoveMappingS2CPacket { - unsigned short sync_id; - Entry obf_entry; -} -``` -- `sync_id`: the sync ID of the change for locking purposes. -- `obf_entry`: the obfuscated name and descriptor of the entry to remove the mapping for. - -### ChangeDocs (server-to-client) -```c -struct ChangeDocsS2CPacket { - unsigned short sync_id; - Entry obf_entry; - utf new_docs; -} -``` -- `sync_id`: the sync ID of the change for locking purposes. -- `obf_entry`: the obfuscated name and descriptor of the entry to change the documentation for. -- `new_docs`: the new documentation for this entry, or an empty string to remove the documentation. - -### MarkDeobfuscated (server-to-client) -```c -struct MarkDeobfuscatedS2CPacket { - unsigned short sync_id; - Entry obf_entry; -} -``` -- `sync_id`: the sync ID of the change for locking purposes. -- `obf_entry`: the obfuscated name and descriptor of the entry to mark as deobfuscated. - ### Message (server-to-client) ```c struct MessageS2CPacket { @@ -364,3 +316,13 @@ struct UserListS2CPacket { utf user[len]; } ``` + +### EntryChange (server-to-client) +```c +struct EntryChangeS2CPacket { + uint16_t sync_id; + entry_change change; +} +``` +- `sync_id`: The sync ID of the change for locking purposes. +- `change`: The change to apply. \ No newline at end of file diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java b/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java index 1b0191be..a651fe84 100644 --- a/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java +++ b/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java @@ -1,24 +1,16 @@ package cuchaz.enigma.network; -import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.translation.mapping.EntryChange; import cuchaz.enigma.translation.mapping.EntryMapping; import cuchaz.enigma.translation.mapping.tree.EntryTree; import cuchaz.enigma.network.packet.Packet; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.utils.validation.ValidationContext; import java.util.List; public interface ClientPacketHandler { void openMappings(EntryTree mappings); - void rename(ValidationContext vc, EntryReference, Entry> reference, String newName, boolean refreshClassTree); - - void removeMapping(ValidationContext vc, EntryReference, Entry> reference); - - void changeDocs(ValidationContext vc, EntryReference, Entry> reference, String updatedDocs); - - void markAsDeobfuscated(ValidationContext vc, EntryReference, Entry> reference); + boolean applyChangeFromServer(EntryChange change); void disconnectIfConnected(String reason); diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java b/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java index 75981c3b..1ce359b6 100644 --- a/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java +++ b/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java @@ -8,6 +8,7 @@ import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; import cuchaz.enigma.network.packet.*; +import cuchaz.enigma.translation.mapping.EntryChange; import cuchaz.enigma.translation.mapping.EntryMapping; import cuchaz.enigma.translation.mapping.EntryRemapper; import cuchaz.enigma.translation.representation.entry.Entry; @@ -16,7 +17,7 @@ public abstract class EnigmaServer { // https://discordapp.com/channels/507304429255393322/566418023372816394/700292322918793347 public static final int DEFAULT_PORT = 34712; - public static final int PROTOCOL_VERSION = 0; + public static final int PROTOCOL_VERSION = 1; public static final int CHECKSUM_SIZE = 20; public static final int MAX_PASSWORD_LENGTH = 255; // length is written as a byte in the login packet @@ -234,11 +235,11 @@ public abstract class EnigmaServer { public void sendCorrectMapping(Socket client, Entry entry, boolean refreshClassTree) { EntryMapping oldMapping = mappings.getDeobfMapping(entry); - String oldName = oldMapping == null ? null : oldMapping.getTargetName(); + String oldName = oldMapping.targetName(); if (oldName == null) { - sendPacket(client, new RemoveMappingS2CPacket(DUMMY_SYNC_ID, entry)); + sendPacket(client, new EntryChangeS2CPacket(DUMMY_SYNC_ID, EntryChange.modify(entry).clearDeobfName())); } else { - sendPacket(client, new RenameS2CPacket(0, entry, oldName, refreshClassTree)); + sendPacket(client, new EntryChangeS2CPacket(0, EntryChange.modify(entry).withDeobfName(oldName))); } } diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java deleted file mode 100644 index f3e07c2d..00000000 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java +++ /dev/null @@ -1,64 +0,0 @@ -package cuchaz.enigma.network.packet; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.network.EnigmaServer; -import cuchaz.enigma.network.Message; -import cuchaz.enigma.network.ServerPacketHandler; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.utils.validation.PrintValidatable; -import cuchaz.enigma.utils.validation.ValidationContext; - -public class ChangeDocsC2SPacket implements Packet { - private Entry entry; - private String newDocs; - - ChangeDocsC2SPacket() { - } - - public ChangeDocsC2SPacket(Entry entry, String newDocs) { - this.entry = entry; - this.newDocs = newDocs; - } - - @Override - public void read(DataInput input) throws IOException { - this.entry = PacketHelper.readEntry(input); - this.newDocs = PacketHelper.readString(input); - } - - @Override - public void write(DataOutput output) throws IOException { - PacketHelper.writeEntry(output, entry); - PacketHelper.writeString(output, newDocs); - } - - @Override - public void handle(ServerPacketHandler handler) { - ValidationContext vc = new ValidationContext(); - vc.setActiveElement(PrintValidatable.INSTANCE); - - EntryMapping mapping = handler.getServer().getMappings().getDeobfMapping(entry); - - boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); - if (!valid) { - String oldDocs = mapping == null ? null : mapping.getJavadoc(); - handler.getServer().sendPacket(handler.getClient(), new ChangeDocsS2CPacket(EnigmaServer.DUMMY_SYNC_ID, entry, oldDocs == null ? "" : oldDocs)); - return; - } - - if (mapping == null) { - mapping = new EntryMapping(handler.getServer().getMappings().deobfuscate(entry).getName()); - } - handler.getServer().getMappings().mapFromObf(vc, entry, mapping.withDocs(newDocs.isBlank() ? null : newDocs)); - - if (!vc.canProceed()) return; - - int syncId = handler.getServer().lockEntry(handler.getClient(), entry); - handler.getServer().sendToAllExcept(handler.getClient(), new ChangeDocsS2CPacket(syncId, entry, newDocs)); - handler.getServer().sendMessage(Message.editDocs(handler.getServer().getUsername(handler.getClient()), entry)); - } -} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java deleted file mode 100644 index 78fa4fa9..00000000 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java +++ /dev/null @@ -1,51 +0,0 @@ -package cuchaz.enigma.network.packet; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.network.ClientPacketHandler; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.utils.validation.PrintValidatable; -import cuchaz.enigma.utils.validation.ValidationContext; - -public class ChangeDocsS2CPacket implements Packet { - private int syncId; - private Entry entry; - private String newDocs; - - ChangeDocsS2CPacket() { - } - - public ChangeDocsS2CPacket(int syncId, Entry entry, String newDocs) { - this.syncId = syncId; - this.entry = entry; - this.newDocs = newDocs; - } - - @Override - public void read(DataInput input) throws IOException { - this.syncId = input.readUnsignedShort(); - this.entry = PacketHelper.readEntry(input); - this.newDocs = PacketHelper.readString(input); - } - - @Override - public void write(DataOutput output) throws IOException { - output.writeShort(syncId); - PacketHelper.writeEntry(output, entry); - PacketHelper.writeString(output, newDocs); - } - - @Override - public void handle(ClientPacketHandler controller) { - ValidationContext vc = new ValidationContext(); - vc.setActiveElement(PrintValidatable.INSTANCE); - - controller.changeDocs(vc, new EntryReference<>(entry, entry.getName()), newDocs); - - if (!vc.canProceed()) return; - controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); - } -} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/EntryChangeC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/EntryChangeC2SPacket.java new file mode 100644 index 00000000..b97877c6 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/EntryChangeC2SPacket.java @@ -0,0 +1,66 @@ +package cuchaz.enigma.network.packet; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import cuchaz.enigma.network.Message; +import cuchaz.enigma.network.ServerPacketHandler; +import cuchaz.enigma.translation.mapping.EntryChange; +import cuchaz.enigma.translation.mapping.EntryUtil; +import cuchaz.enigma.utils.validation.PrintValidatable; +import cuchaz.enigma.utils.validation.ValidationContext; + +public class EntryChangeC2SPacket implements Packet { + + private EntryChange change; + + EntryChangeC2SPacket() { + } + + public EntryChangeC2SPacket(EntryChange change) { + this.change = change; + } + + @Override + public void read(DataInput input) throws IOException { + this.change = PacketHelper.readEntryChange(input); + } + + @Override + public void write(DataOutput output) throws IOException { + PacketHelper.writeEntryChange(output, change); + } + + @Override + public void handle(ServerPacketHandler handler) { + ValidationContext vc = new ValidationContext(); + vc.setActiveElement(PrintValidatable.INSTANCE); + + boolean valid = handler.getServer().canModifyEntry(handler.getClient(), this.change.getTarget()); + + if (valid) { + EntryUtil.applyChange(vc, handler.getServer().getMappings(), this.change); + valid = vc.canProceed(); + } + + if (!valid) { + handler.getServer().sendCorrectMapping(handler.getClient(), this.change.getTarget(), true); + return; + } + + int syncId = handler.getServer().lockEntry(handler.getClient(), this.change.getTarget()); + handler.getServer().sendToAllExcept(handler.getClient(), new EntryChangeS2CPacket(syncId, this.change)); + + if (this.change.getDeobfName().isSet()) { + handler.getServer().sendMessage(Message.rename(handler.getServer().getUsername(handler.getClient()), this.change.getTarget(), this.change.getDeobfName().getNewValue())); + } else if (this.change.getDeobfName().isReset()) { + handler.getServer().sendMessage(Message.removeMapping(handler.getServer().getUsername(handler.getClient()), this.change.getTarget())); + } + + if (!this.change.getJavadoc().isUnchanged()) { + handler.getServer().sendMessage(Message.editDocs(handler.getServer().getUsername(handler.getClient()), this.change.getTarget())); + } + } + +} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/EntryChangeS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/EntryChangeS2CPacket.java new file mode 100644 index 00000000..a237b916 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/EntryChangeS2CPacket.java @@ -0,0 +1,42 @@ +package cuchaz.enigma.network.packet; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import cuchaz.enigma.network.ClientPacketHandler; +import cuchaz.enigma.translation.mapping.EntryChange; + +public class EntryChangeS2CPacket implements Packet { + + private int syncId; + private EntryChange change; + + public EntryChangeS2CPacket(int syncId, EntryChange change) { + this.syncId = syncId; + this.change = change; + } + + EntryChangeS2CPacket() { + } + + @Override + public void read(DataInput input) throws IOException { + this.syncId = input.readUnsignedShort(); + this.change = PacketHelper.readEntryChange(input); + } + + @Override + public void write(DataOutput output) throws IOException { + output.writeShort(this.syncId); + PacketHelper.writeEntryChange(output, this.change); + } + + @Override + public void handle(ClientPacketHandler handler) { + if (handler.applyChangeFromServer(this.change)) { + handler.sendPacket(new ConfirmChangeC2SPacket(this.syncId)); + } + } + +} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java deleted file mode 100644 index 732c7448..00000000 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java +++ /dev/null @@ -1,56 +0,0 @@ -package cuchaz.enigma.network.packet; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -import cuchaz.enigma.network.Message; -import cuchaz.enigma.network.ServerPacketHandler; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.utils.validation.PrintValidatable; -import cuchaz.enigma.utils.validation.ValidationContext; - -public class MarkDeobfuscatedC2SPacket implements Packet { - private Entry entry; - - MarkDeobfuscatedC2SPacket() { - } - - public MarkDeobfuscatedC2SPacket(Entry entry) { - this.entry = entry; - } - - @Override - public void read(DataInput input) throws IOException { - this.entry = PacketHelper.readEntry(input); - } - - @Override - public void write(DataOutput output) throws IOException { - PacketHelper.writeEntry(output, entry); - } - - @Override - public void handle(ServerPacketHandler handler) { - ValidationContext vc = new ValidationContext(); - vc.setActiveElement(PrintValidatable.INSTANCE); - - boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); - - if (!valid) { - handler.getServer().sendCorrectMapping(handler.getClient(), entry, true); - return; - } - - handler.getServer().getMappings().mapFromObf(vc, entry, new EntryMapping(handler.getServer().getMappings().deobfuscate(entry).getName())); - - if (!vc.canProceed()) return; - - handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " marked " + entry + " as deobfuscated"); - - int syncId = handler.getServer().lockEntry(handler.getClient(), entry); - handler.getServer().sendToAllExcept(handler.getClient(), new MarkDeobfuscatedS2CPacket(syncId, entry)); - handler.getServer().sendMessage(Message.markDeobf(handler.getServer().getUsername(handler.getClient()), entry)); - } -} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java deleted file mode 100644 index 969d13c5..00000000 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java +++ /dev/null @@ -1,47 +0,0 @@ -package cuchaz.enigma.network.packet; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.network.ClientPacketHandler; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.utils.validation.PrintValidatable; -import cuchaz.enigma.utils.validation.ValidationContext; - -public class MarkDeobfuscatedS2CPacket implements Packet { - private int syncId; - private Entry entry; - - MarkDeobfuscatedS2CPacket() { - } - - public MarkDeobfuscatedS2CPacket(int syncId, Entry entry) { - this.syncId = syncId; - this.entry = entry; - } - - @Override - public void read(DataInput input) throws IOException { - this.syncId = input.readUnsignedShort(); - this.entry = PacketHelper.readEntry(input); - } - - @Override - public void write(DataOutput output) throws IOException { - output.writeShort(syncId); - PacketHelper.writeEntry(output, entry); - } - - @Override - public void handle(ClientPacketHandler controller) { - ValidationContext vc = new ValidationContext(); - vc.setActiveElement(PrintValidatable.INSTANCE); - - controller.markAsDeobfuscated(vc, new EntryReference<>(entry, entry.getName())); - - if (!vc.canProceed()) return; - controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); - } -} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java index 464606e0..2649cdc4 100644 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java @@ -1,18 +1,17 @@ package cuchaz.enigma.network.packet; -import cuchaz.enigma.translation.representation.MethodDescriptor; -import cuchaz.enigma.translation.representation.TypeDescriptor; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.translation.representation.entry.FieldEntry; -import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; - import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.nio.charset.StandardCharsets; +import cuchaz.enigma.translation.mapping.AccessModifier; +import cuchaz.enigma.translation.mapping.EntryChange; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.*; +import cuchaz.enigma.utils.TristateChange; + public class PacketHelper { private static final int ENTRY_CLASS = 0, ENTRY_FIELD = 1, ENTRY_METHOD = 2, ENTRY_LOCAL_VAR = 3; @@ -37,35 +36,40 @@ public class PacketHelper { } switch (type) { - case ENTRY_CLASS: { - if (parent != null && !(parent instanceof ClassEntry)) { - throw new IOException("Class requires class parent"); + case ENTRY_CLASS: { + if (parent != null && !(parent instanceof ClassEntry)) { + throw new IOException("Class requires class parent"); + } + + return new ClassEntry((ClassEntry) parent, name, javadocs); } - return new ClassEntry((ClassEntry) parent, name, javadocs); - } - case ENTRY_FIELD: { - if (!(parent instanceof ClassEntry)) { - throw new IOException("Field requires class parent"); + case ENTRY_FIELD: { + if (!(parent instanceof ClassEntry parentClass)) { + throw new IOException("Field requires class parent"); + } + + TypeDescriptor desc = new TypeDescriptor(readString(input)); + return new FieldEntry(parentClass, name, desc, javadocs); } - TypeDescriptor desc = new TypeDescriptor(readString(input)); - return new FieldEntry((ClassEntry) parent, name, desc, javadocs); - } - case ENTRY_METHOD: { - if (!(parent instanceof ClassEntry)) { - throw new IOException("Method requires class parent"); + case ENTRY_METHOD: { + if (!(parent instanceof ClassEntry parentClass)) { + throw new IOException("Method requires class parent"); + } + + MethodDescriptor desc = new MethodDescriptor(readString(input)); + return new MethodEntry(parentClass, name, desc, javadocs); } - MethodDescriptor desc = new MethodDescriptor(readString(input)); - return new MethodEntry((ClassEntry) parent, name, desc, javadocs); - } - case ENTRY_LOCAL_VAR: { - if (!(parent instanceof MethodEntry)) { - throw new IOException("Local variable requires method parent"); + case ENTRY_LOCAL_VAR: { + if (!(parent instanceof MethodEntry parentMethod)) { + throw new IOException("Local variable requires method parent"); + } + + int index = input.readUnsignedShort(); + boolean parameter = input.readBoolean(); + return new LocalVariableEntry(parentMethod, index, name, parameter, javadocs); } - int index = input.readUnsignedShort(); - boolean parameter = input.readBoolean(); - return new LocalVariableEntry((MethodEntry) parent, index, name, parameter, javadocs); - } - default: throw new IOException("Received unknown entry type " + type); + default: + throw new IOException("Received unknown entry type " + type); } } @@ -132,4 +136,64 @@ public class PacketHelper { output.write(bytes); } + public static EntryChange readEntryChange(DataInput input) throws IOException { + Entry e = readEntry(input); + EntryChange change = EntryChange.modify(e); + + int flags = input.readUnsignedByte(); + TristateChange.Type deobfNameT = TristateChange.Type.values()[flags & 0x3]; + TristateChange.Type accessT = TristateChange.Type.values()[flags >> 2 & 0x3]; + TristateChange.Type javadocT = TristateChange.Type.values()[flags >> 4 & 0x3]; + + switch (deobfNameT) { + case RESET: + change = change.clearDeobfName(); + break; + case SET: + change = change.withDeobfName(readString(input)); + break; + } + + switch (accessT) { + case RESET: + change = change.clearAccess(); + break; + case SET: + change = change.withAccess(AccessModifier.values()[flags >> 6 & 0x3]); + break; + } + + switch (javadocT) { + case RESET: + change = change.clearJavadoc(); + break; + case SET: + change = change.withJavadoc(readString(input)); + break; + } + + return change; + } + + public static void writeEntryChange(DataOutput output, EntryChange change) throws IOException { + writeEntry(output, change.getTarget()); + int flags = change.getDeobfName().getType().ordinal() | + change.getAccess().getType().ordinal() << 2 | + change.getJavadoc().getType().ordinal() << 4; + + if (change.getAccess().isSet()) { + flags |= change.getAccess().getNewValue().ordinal() << 6; + } + + output.writeByte(flags); + + if (change.getDeobfName().isSet()) { + writeString(output, change.getDeobfName().getNewValue()); + } + + if (change.getJavadoc().isSet()) { + writeString(output, change.getJavadoc().getNewValue()); + } + } + } diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java index 3b8af81c..59999ccc 100644 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java @@ -1,12 +1,12 @@ package cuchaz.enigma.network.packet; -import cuchaz.enigma.network.ClientPacketHandler; -import cuchaz.enigma.network.ServerPacketHandler; - import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; +import cuchaz.enigma.network.ClientPacketHandler; +import cuchaz.enigma.network.ServerPacketHandler; + public class PacketRegistry { private static final Map>, Integer> c2sPacketIds = new HashMap<>(); @@ -27,20 +27,14 @@ public class PacketRegistry { static { registerC2S(0, LoginC2SPacket.class, LoginC2SPacket::new); registerC2S(1, ConfirmChangeC2SPacket.class, ConfirmChangeC2SPacket::new); - registerC2S(2, RenameC2SPacket.class, RenameC2SPacket::new); - registerC2S(3, RemoveMappingC2SPacket.class, RemoveMappingC2SPacket::new); - registerC2S(4, ChangeDocsC2SPacket.class, ChangeDocsC2SPacket::new); - registerC2S(5, MarkDeobfuscatedC2SPacket.class, MarkDeobfuscatedC2SPacket::new); registerC2S(6, MessageC2SPacket.class, MessageC2SPacket::new); + registerC2S(7, EntryChangeC2SPacket.class, EntryChangeC2SPacket::new); registerS2C(0, KickS2CPacket.class, KickS2CPacket::new); registerS2C(1, SyncMappingsS2CPacket.class, SyncMappingsS2CPacket::new); - registerS2C(2, RenameS2CPacket.class, RenameS2CPacket::new); - registerS2C(3, RemoveMappingS2CPacket.class, RemoveMappingS2CPacket::new); - registerS2C(4, ChangeDocsS2CPacket.class, ChangeDocsS2CPacket::new); - registerS2C(5, MarkDeobfuscatedS2CPacket.class, MarkDeobfuscatedS2CPacket::new); registerS2C(6, MessageS2CPacket.class, MessageS2CPacket::new); registerS2C(7, UserListS2CPacket.class, UserListS2CPacket::new); + registerS2C(8, EntryChangeS2CPacket.class, EntryChangeS2CPacket::new); } public static int getC2SId(Packet packet) { diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java deleted file mode 100644 index 298e674f..00000000 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java +++ /dev/null @@ -1,56 +0,0 @@ -package cuchaz.enigma.network.packet; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -import cuchaz.enigma.network.Message; -import cuchaz.enigma.network.ServerPacketHandler; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.utils.validation.PrintValidatable; -import cuchaz.enigma.utils.validation.ValidationContext; - -public class RemoveMappingC2SPacket implements Packet { - private Entry entry; - - RemoveMappingC2SPacket() { - } - - public RemoveMappingC2SPacket(Entry entry) { - this.entry = entry; - } - - @Override - public void read(DataInput input) throws IOException { - this.entry = PacketHelper.readEntry(input); - } - - @Override - public void write(DataOutput output) throws IOException { - PacketHelper.writeEntry(output, entry); - } - - @Override - public void handle(ServerPacketHandler handler) { - ValidationContext vc = new ValidationContext(); - vc.setActiveElement(PrintValidatable.INSTANCE); - - boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); - - if (valid) { - handler.getServer().getMappings().removeByObf(vc, entry); - valid = vc.canProceed(); - } - - if (!valid) { - handler.getServer().sendCorrectMapping(handler.getClient(), entry, true); - return; - } - - handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " removed the mapping for " + entry); - - int syncId = handler.getServer().lockEntry(handler.getClient(), entry); - handler.getServer().sendToAllExcept(handler.getClient(), new RemoveMappingS2CPacket(syncId, entry)); - handler.getServer().sendMessage(Message.removeMapping(handler.getServer().getUsername(handler.getClient()), entry)); - } -} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java deleted file mode 100644 index e336c7b2..00000000 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java +++ /dev/null @@ -1,47 +0,0 @@ -package cuchaz.enigma.network.packet; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.network.ClientPacketHandler; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.utils.validation.PrintValidatable; -import cuchaz.enigma.utils.validation.ValidationContext; - -public class RemoveMappingS2CPacket implements Packet { - private int syncId; - private Entry entry; - - RemoveMappingS2CPacket() { - } - - public RemoveMappingS2CPacket(int syncId, Entry entry) { - this.syncId = syncId; - this.entry = entry; - } - - @Override - public void read(DataInput input) throws IOException { - this.syncId = input.readUnsignedShort(); - this.entry = PacketHelper.readEntry(input); - } - - @Override - public void write(DataOutput output) throws IOException { - output.writeShort(syncId); - PacketHelper.writeEntry(output, entry); - } - - @Override - public void handle(ClientPacketHandler controller) { - ValidationContext vc = new ValidationContext(); - vc.setActiveElement(PrintValidatable.INSTANCE); - - controller.removeMapping(vc, new EntryReference<>(entry, entry.getName())); - - if (!vc.canProceed()) return; - controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); - } -} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java deleted file mode 100644 index 694d4321..00000000 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java +++ /dev/null @@ -1,66 +0,0 @@ -package cuchaz.enigma.network.packet; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -import cuchaz.enigma.network.Message; -import cuchaz.enigma.network.ServerPacketHandler; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.utils.validation.PrintValidatable; -import cuchaz.enigma.utils.validation.ValidationContext; - -public class RenameC2SPacket implements Packet { - private Entry entry; - private String newName; - private boolean refreshClassTree; - - RenameC2SPacket() { - } - - public RenameC2SPacket(Entry entry, String newName, boolean refreshClassTree) { - this.entry = entry; - this.newName = newName; - this.refreshClassTree = refreshClassTree; - } - - @Override - public void read(DataInput input) throws IOException { - this.entry = PacketHelper.readEntry(input); - this.newName = PacketHelper.readString(input); - this.refreshClassTree = input.readBoolean(); - } - - @Override - public void write(DataOutput output) throws IOException { - PacketHelper.writeEntry(output, entry); - PacketHelper.writeString(output, newName); - output.writeBoolean(refreshClassTree); - } - - @Override - public void handle(ServerPacketHandler handler) { - ValidationContext vc = new ValidationContext(); - vc.setActiveElement(PrintValidatable.INSTANCE); - - boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); - - if (valid) { - EntryMapping previous = handler.getServer().getMappings().getDeobfMapping(entry); - handler.getServer().getMappings().mapFromObf(vc, entry, previous != null ? previous.withName(newName) : new EntryMapping(newName)); - valid = vc.canProceed(); - } - - if (!valid) { - handler.getServer().sendCorrectMapping(handler.getClient(), entry, refreshClassTree); - return; - } - - handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " renamed " + entry + " to " + newName); - - int syncId = handler.getServer().lockEntry(handler.getClient(), entry); - handler.getServer().sendToAllExcept(handler.getClient(), new RenameS2CPacket(syncId, entry, newName, refreshClassTree)); - handler.getServer().sendMessage(Message.rename(handler.getServer().getUsername(handler.getClient()), entry, newName)); - } -} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java deleted file mode 100644 index fdf06540..00000000 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java +++ /dev/null @@ -1,55 +0,0 @@ -package cuchaz.enigma.network.packet; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.network.ClientPacketHandler; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.utils.validation.PrintValidatable; -import cuchaz.enigma.utils.validation.ValidationContext; - -public class RenameS2CPacket implements Packet { - private int syncId; - private Entry entry; - private String newName; - private boolean refreshClassTree; - - RenameS2CPacket() { - } - - public RenameS2CPacket(int syncId, Entry entry, String newName, boolean refreshClassTree) { - this.syncId = syncId; - this.entry = entry; - this.newName = newName; - this.refreshClassTree = refreshClassTree; - } - - @Override - public void read(DataInput input) throws IOException { - this.syncId = input.readUnsignedShort(); - this.entry = PacketHelper.readEntry(input); - this.newName = PacketHelper.readString(input); - this.refreshClassTree = input.readBoolean(); - } - - @Override - public void write(DataOutput output) throws IOException { - output.writeShort(syncId); - PacketHelper.writeEntry(output, entry); - PacketHelper.writeString(output, newName); - output.writeBoolean(refreshClassTree); - } - - @Override - public void handle(ClientPacketHandler controller) { - ValidationContext vc = new ValidationContext(); - vc.setActiveElement(PrintValidatable.INSTANCE); - - controller.rename(vc, new EntryReference<>(entry, entry.getName()), newName, refreshClassTree); - - if (!vc.canProceed()) return; - controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); - } -} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java index 92a78748..6d9c0bcb 100644 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java @@ -1,19 +1,19 @@ package cuchaz.enigma.network.packet; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.tree.EntryTree; -import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; -import cuchaz.enigma.translation.mapping.tree.HashEntryTree; -import cuchaz.enigma.network.ClientPacketHandler; -import cuchaz.enigma.network.EnigmaServer; -import cuchaz.enigma.translation.representation.entry.Entry; - import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.Collection; import java.util.List; +import cuchaz.enigma.network.ClientPacketHandler; +import cuchaz.enigma.network.EnigmaServer; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; +import cuchaz.enigma.translation.mapping.tree.HashEntryTree; +import cuchaz.enigma.translation.representation.entry.Entry; + public class SyncMappingsS2CPacket implements Packet { private EntryTree mappings; @@ -35,16 +35,9 @@ public class SyncMappingsS2CPacket implements Packet { private void readEntryTreeNode(DataInput input, Entry parent) throws IOException { Entry entry = PacketHelper.readEntry(input, parent, false); - EntryMapping mapping = null; - if (input.readBoolean()) { - String name = input.readUTF(); - if (input.readBoolean()) { - String javadoc = input.readUTF(); - mapping = new EntryMapping(name, javadoc); - } else { - mapping = new EntryMapping(name); - } - } + String name = PacketHelper.readString(input); + String javadoc = PacketHelper.readString(input); + EntryMapping mapping = new EntryMapping(!name.isEmpty() ? name : null, !javadoc.isEmpty() ? javadoc : null); mappings.insert(entry, mapping); int size = input.readUnsignedShort(); for (int i = 0; i < size; i++) { @@ -64,14 +57,10 @@ public class SyncMappingsS2CPacket implements Packet { private static void writeEntryTreeNode(DataOutput output, EntryTreeNode node) throws IOException { PacketHelper.writeEntry(output, node.getEntry(), false); EntryMapping value = node.getValue(); - output.writeBoolean(value != null); - if (value != null) { - PacketHelper.writeString(output, value.getTargetName()); - output.writeBoolean(value.getJavadoc() != null); - if (value.getJavadoc() != null) { - PacketHelper.writeString(output, value.getJavadoc()); - } - } + if (value == null) value = EntryMapping.DEFAULT; + + PacketHelper.writeString(output, value.targetName() != null ? value.targetName() : ""); + PacketHelper.writeString(output, value.javadoc() != null ? value.javadoc() : ""); Collection> children = node.getChildNodes(); output.writeShort(children.size()); for (EntryTreeNode child : children) { diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java index a657d171..cddedad7 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java @@ -49,11 +49,9 @@ import cuchaz.enigma.gui.renderer.InheritanceTreeCellRenderer; import cuchaz.enigma.gui.renderer.MessageListCellRenderer; import cuchaz.enigma.gui.util.*; import cuchaz.enigma.network.Message; -import cuchaz.enigma.network.packet.MarkDeobfuscatedC2SPacket; import cuchaz.enigma.network.packet.MessageC2SPacket; -import cuchaz.enigma.network.packet.RemoveMappingC2SPacket; -import cuchaz.enigma.network.packet.RenameC2SPacket; import cuchaz.enigma.source.Token; +import cuchaz.enigma.translation.mapping.EntryChange; import cuchaz.enigma.translation.mapping.EntryRemapper; import cuchaz.enigma.translation.representation.entry.ClassEntry; import cuchaz.enigma.translation.representation.entry.Entry; @@ -740,12 +738,10 @@ public class Gui implements LanguageChangeListener { Entry obfEntry = cursorReference.entry; - if (controller.project.getMapper().extendedDeobfuscate(obfEntry).isDeobfuscated()) { - if (!validateImmediateAction(vc -> this.controller.removeMapping(vc, cursorReference))) return; - this.controller.sendPacket(new RemoveMappingC2SPacket(cursorReference.getNameableEntry())); + if (this.controller.project.getMapper().getDeobfMapping(obfEntry).targetName() != null) { + validateImmediateAction(vc -> this.controller.applyChange(vc, EntryChange.modify(obfEntry).clearDeobfName())); } else { - if (!validateImmediateAction(vc -> this.controller.markAsDeobfuscated(vc, cursorReference))) return; - this.controller.sendPacket(new MarkDeobfuscatedC2SPacket(cursorReference.getNameableEntry())); + validateImmediateAction(vc -> this.controller.applyChange(vc, EntryChange.modify(obfEntry).withDefaultDeobfName(this.getController().project))); } } @@ -813,7 +809,7 @@ public class Gui implements LanguageChangeListener { this.frame.repaint(); } - public void onPanelRename(ValidationContext vc, Object prevData, Object data, DefaultMutableTreeNode node) { + public void onRenameFromClassTree(ValidationContext vc, Object prevData, Object data, DefaultMutableTreeNode node) { if (data instanceof String) { // package rename for (int i = 0; i < node.getChildCount(); i++) { @@ -821,7 +817,7 @@ public class Gui implements LanguageChangeListener { ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); - onPanelRename(vc, prevDataChild, dataChild, node); + onRenameFromClassTree(vc, prevDataChild, dataChild, node); } node.setUserObject(data); // Ob package will never be modified, just reload deob view @@ -839,9 +835,9 @@ public class Gui implements LanguageChangeListener { .filter(e -> mapper.deobfuscate(e).equals(deobf)) .findAny().orElse(deobf); - this.controller.rename(vc, new EntryReference<>(obf, obf.getFullName()), ((ClassEntry) data).getFullName(), false); - if (!vc.canProceed()) return; - this.controller.sendPacket(new RenameC2SPacket(obf, ((ClassEntry) data).getFullName(), false)); + this.controller.applyChange(vc, EntryChange.modify(obf).withDeobfName(((ClassEntry) data).getFullName())); + } else { + throw new IllegalStateException(String.format("unhandled rename object data: '%s'", data)); } } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java index 2b75655a..4a15b418 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -39,10 +40,12 @@ import cuchaz.enigma.classprovider.ClasspathClassProvider; import cuchaz.enigma.gui.config.NetConfig; import cuchaz.enigma.gui.config.UiConfig; import cuchaz.enigma.gui.dialog.ProgressDialog; +import cuchaz.enigma.gui.newabstraction.EntryValidation; import cuchaz.enigma.gui.stats.StatsGenerator; import cuchaz.enigma.gui.stats.StatsMember; import cuchaz.enigma.gui.util.History; import cuchaz.enigma.network.*; +import cuchaz.enigma.network.packet.EntryChangeC2SPacket; import cuchaz.enigma.network.packet.LoginC2SPacket; import cuchaz.enigma.network.packet.Packet; import cuchaz.enigma.source.DecompiledClassSource; @@ -62,6 +65,7 @@ import cuchaz.enigma.translation.representation.entry.FieldEntry; import cuchaz.enigma.translation.representation.entry.MethodEntry; import cuchaz.enigma.utils.I18n; import cuchaz.enigma.utils.Utils; +import cuchaz.enigma.utils.validation.PrintValidatable; import cuchaz.enigma.utils.validation.ValidationContext; public class GuiController implements ClientPacketHandler { @@ -401,19 +405,6 @@ public class GuiController implements ClientPacketHandler { }); } - public void onModifierChanged(ValidationContext vc, Entry entry, AccessModifier modifier) { - EntryRemapper mapper = project.getMapper(); - - EntryMapping mapping = mapper.getDeobfMapping(entry); - if (mapping != null) { - mapper.mapFromObf(vc, entry, new EntryMapping(mapping.getTargetName(), modifier)); - } else { - mapper.mapFromObf(vc, entry, new EntryMapping(entry.getName(), modifier)); - } - - chp.invalidateMapped(); - } - public StructureTreeNode getClassStructure(ClassEntry entry, StructureTreeOptions options) { StructureTreeNode rootNode = new StructureTreeNode(this.project, entry, entry); rootNode.load(this.project, options); @@ -471,74 +462,54 @@ public class GuiController implements ClientPacketHandler { } @Override - public void rename(ValidationContext vc, EntryReference, Entry> reference, String newName, boolean refreshClassTree) { - rename(vc, reference, newName, refreshClassTree, false); - } - - public void rename(ValidationContext vc, EntryReference, Entry> reference, String newName, boolean refreshClassTree, boolean validateOnly) { - Entry entry = reference.getNameableEntry(); - EntryMapping previous = project.getMapper().getDeobfMapping(entry); - project.getMapper().mapFromObf(vc, entry, previous != null ? previous.withName(newName) : new EntryMapping(newName), true, validateOnly); + public boolean applyChangeFromServer(EntryChange change) { + ValidationContext vc = new ValidationContext(); + vc.setActiveElement(PrintValidatable.INSTANCE); + this.applyChange0(vc, change); gui.showStructure(gui.getActiveEditor()); - if (validateOnly || !vc.canProceed()) return; + return vc.canProceed(); + } - if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) - this.gui.moveClassTree(reference.entry, newName); + public void validateChange(ValidationContext vc, EntryChange change) { + if (change.getDeobfName().isSet()) { + EntryValidation.validateRename(vc, this.project, change.getTarget(), change.getDeobfName().getNewValue()); + } - chp.invalidateMapped(); + if (change.getJavadoc().isSet()) { + EntryValidation.validateJavadoc(vc, change.getJavadoc().getNewValue()); + } } - @Override - public void removeMapping(ValidationContext vc, EntryReference, Entry> reference) { - project.getMapper().removeByObf(vc, reference.getNameableEntry()); + public void applyChange(ValidationContext vc, EntryChange change) { + this.applyChange0(vc, change); gui.showStructure(gui.getActiveEditor()); - if (!vc.canProceed()) return; - - if (reference.entry instanceof ClassEntry) - this.gui.moveClassTree(reference.entry, false, true); - - chp.invalidateMapped(); + this.sendPacket(new EntryChangeC2SPacket(change)); } - @Override - public void changeDocs(ValidationContext vc, EntryReference, Entry> reference, String updatedDocs) { - changeDocs(vc, reference, updatedDocs, false); - } - - public void changeDocs(ValidationContext vc, EntryReference, Entry> reference, String updatedDocs, boolean validateOnly) { - changeDoc(vc, reference.entry, updatedDocs, validateOnly); - - if (validateOnly || !vc.canProceed()) return; + private void applyChange0(ValidationContext vc, EntryChange change) { + validateChange(vc, change); + if (!vc.canProceed()) return; - chp.invalidateJavadoc(reference.getLocationClassEntry()); - } + Entry target = change.getTarget(); + EntryMapping prev = this.project.getMapper().getDeobfMapping(target); + EntryMapping mapping = EntryUtil.applyChange(vc, this.project.getMapper(), change); - private void changeDoc(ValidationContext vc, Entry obfEntry, String newDoc, boolean validateOnly) { - EntryRemapper mapper = project.getMapper(); + boolean renamed = !change.getDeobfName().isUnchanged(); - EntryMapping deobfMapping = mapper.getDeobfMapping(obfEntry); - if (deobfMapping == null) { - deobfMapping = new EntryMapping(mapper.deobfuscate(obfEntry).getName()); + if (renamed && target instanceof ClassEntry && !((ClassEntry) target).isInnerClass()) { + this.gui.moveClassTree(target, prev.targetName() == null, mapping.targetName() == null); } - mapper.mapFromObf(vc, obfEntry, deobfMapping.withDocs(newDoc), false, validateOnly); - } + if (!Objects.equals(prev.targetName(), mapping.targetName())) { + this.chp.invalidateMapped(); + } - @Override - public void markAsDeobfuscated(ValidationContext vc, EntryReference, Entry> reference) { - EntryRemapper mapper = project.getMapper(); - Entry entry = reference.getNameableEntry(); - mapper.mapFromObf(vc, entry, new EntryMapping(mapper.deobfuscate(entry).getName())); + if (!Objects.equals(prev.javadoc(), mapping.javadoc())) { + this.chp.invalidateJavadoc(target.getTopLevelClass()); + } gui.showStructure(gui.getActiveEditor()); - - if (!vc.canProceed()) return; - - if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) - this.gui.moveClassTree(reference.entry, true, false); - - chp.invalidateMapped(); } public void openStats(Set includedMembers, String topLevelPackage, boolean includeSynthetic) { diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java index 2fc67476..9470e11c 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java @@ -29,23 +29,22 @@ import cuchaz.enigma.gui.config.UiConfig; import cuchaz.enigma.gui.elements.ValidatableTextArea; import cuchaz.enigma.gui.util.GuiUtil; import cuchaz.enigma.gui.util.ScaleUtil; -import cuchaz.enigma.network.packet.ChangeDocsC2SPacket; +import cuchaz.enigma.translation.mapping.EntryChange; import cuchaz.enigma.translation.representation.entry.Entry; import cuchaz.enigma.utils.I18n; -import cuchaz.enigma.utils.validation.Message; import cuchaz.enigma.utils.validation.ValidationContext; public class JavadocDialog { private final JDialog ui; private final GuiController controller; - private final EntryReference, Entry> entry; + private final Entry entry; private final ValidatableTextArea text; private final ValidationContext vc = new ValidationContext(); - private JavadocDialog(JFrame parent, GuiController controller, EntryReference, Entry> entry, String preset) { + private JavadocDialog(JFrame parent, GuiController controller, Entry entry, String preset) { this.ui = new JDialog(parent, I18n.translate("javadocs.edit")); this.controller = controller; this.entry = entry; @@ -161,28 +160,21 @@ public class JavadocDialog { public void validate() { vc.setActiveElement(text); - if (text.getText().contains("*/")) { - vc.raise(Message.ILLEGAL_DOC_COMMENT_END); - } - - controller.changeDocs(vc, entry, text.getText(), true); + controller.validateChange(vc, EntryChange.modify(entry).withJavadoc(text.getText())); } public void save() { vc.setActiveElement(text); - controller.changeDocs(vc, entry, text.getText().trim().isEmpty() ? null : text.getText()); - - if (!vc.canProceed()) return; - controller.sendPacket(new ChangeDocsC2SPacket(entry.getNameableEntry(), text.getText())); + controller.applyChange(vc, EntryChange.modify(entry).withJavadoc(text.getText())); } public static void show(JFrame parent, GuiController controller, EntryReference, Entry> entry) { EntryReference, Entry> translatedReference = controller.project.getMapper().deobfuscate(entry); String text = Strings.nullToEmpty(translatedReference.entry.getJavadocs()); - JavadocDialog dialog = new JavadocDialog(parent, controller, entry, text); - dialog.ui.doLayout(); + JavadocDialog dialog = new JavadocDialog(parent, controller, entry.getNameableEntry(), text); + //dialog.ui.doLayout(); dialog.ui.setVisible(true); dialog.text.grabFocus(); } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/newabstraction/EntryValidation.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/newabstraction/EntryValidation.java new file mode 100644 index 00000000..898529a4 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/newabstraction/EntryValidation.java @@ -0,0 +1,22 @@ +package cuchaz.enigma.gui.newabstraction; + +import cuchaz.enigma.EnigmaProject; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.utils.validation.Message; +import cuchaz.enigma.utils.validation.ValidationContext; + +public class EntryValidation { + + public static boolean validateJavadoc(ValidationContext vc, String javadoc) { + if (javadoc.contains("*/")) { + vc.raise(Message.ILLEGAL_DOC_COMMENT_END); + return false; + } + return true; + } + + public static boolean validateRename(ValidationContext vc, EnigmaProject p, Entry entry, String newName) { + return p.getMapper().getValidator().validateRename(vc, entry, newName); + } + +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java index 5b7882b0..cd09c1a1 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java @@ -22,7 +22,7 @@ public class DeobfPanel extends JPanel { this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true); this.deobfClasses.setSelectionListener(gui.getController()::navigateTo); - this.deobfClasses.setRenameSelectionListener(gui::onPanelRename); + this.deobfClasses.setRenameSelectionListener(gui::onRenameFromClassTree); this.setLayout(new BorderLayout()); this.add(this.title, BorderLayout.NORTH); diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java index 3bae94c3..4ae0b7be 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java @@ -15,7 +15,6 @@ import javax.swing.JLabel; import javax.swing.JPanel; import cuchaz.enigma.EnigmaProject; -import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.gui.EditableType; import cuchaz.enigma.gui.Gui; import cuchaz.enigma.gui.elements.ConvertingTextField; @@ -23,8 +22,8 @@ import cuchaz.enigma.gui.events.ConvertingTextFieldListener; import cuchaz.enigma.gui.util.GridBagConstraintsBuilder; import cuchaz.enigma.gui.util.GuiUtil; import cuchaz.enigma.gui.util.ScaleUtil; -import cuchaz.enigma.network.packet.RenameC2SPacket; import cuchaz.enigma.translation.mapping.AccessModifier; +import cuchaz.enigma.translation.mapping.EntryChange; import cuchaz.enigma.translation.mapping.EntryMapping; import cuchaz.enigma.translation.representation.entry.*; import cuchaz.enigma.utils.I18n; @@ -76,7 +75,7 @@ public class IdentifierPanel { } private void onModifierChanged(AccessModifier modifier) { - gui.validateImmediateAction(vc -> this.gui.getController().onModifierChanged(vc, entry, modifier)); + gui.validateImmediateAction(vc -> this.gui.getController().applyChange(vc, EntryChange.modify(entry).withAccess(modifier))); } public void refreshReference() { @@ -176,13 +175,12 @@ public class IdentifierPanel { } private void validateRename(String newName) { - gui.getController().rename(vc, new EntryReference<>(entry, deobfEntry.getName()), newName, true, true); + gui.getController().validateChange(vc, EntryChange.modify(entry).withDeobfName(newName)); } private void doRename(String newName) { - gui.getController().rename(vc, new EntryReference<>(entry, deobfEntry.getName()), newName, true); - if (!vc.canProceed()) return; - gui.getController().sendPacket(new RenameC2SPacket(entry, newName, true)); + EntryChange> change = EntryChange.modify(entry).withDeobfName(newName); + gui.getController().applyChange(vc, change); } public void retranslateUi() { @@ -277,12 +275,7 @@ public class IdentifierPanel { JComboBox combo = new JComboBox<>(AccessModifier.values()); EntryMapping mapping = project.getMapper().getDeobfMapping(e); - - if (mapping != null) { - combo.setSelectedIndex(mapping.getAccessModifier().ordinal()); - } else { - combo.setSelectedIndex(AccessModifier.UNCHANGED.ordinal()); - } + combo.setSelectedIndex(mapping.accessModifier().ordinal()); if (this.gui.isEditable(type)) { combo.addItemListener(event -> { diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ObfPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ObfPanel.java index b384968d..7783843d 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ObfPanel.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ObfPanel.java @@ -33,7 +33,7 @@ public class ObfPanel extends JPanel { this.obfClasses = new ClassSelector(gui, obfClassComparator, false); this.obfClasses.setSelectionListener(gui.getController()::navigateTo); - this.obfClasses.setRenameSelectionListener(gui::onPanelRename); + this.obfClasses.setRenameSelectionListener(gui::onRenameFromClassTree); this.setLayout(new BorderLayout()); this.add(this.title, BorderLayout.NORTH); diff --git a/enigma/src/main/java/cuchaz/enigma/source/DecompiledClassSource.java b/enigma/src/main/java/cuchaz/enigma/source/DecompiledClassSource.java index 9ac611f5..5f371a58 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/DecompiledClassSource.java +++ b/enigma/src/main/java/cuchaz/enigma/source/DecompiledClassSource.java @@ -79,7 +79,7 @@ public class DecompiledClassSource { return null; } - private Optional proposeName(EnigmaProject project, Entry entry) { + public static Optional proposeName(EnigmaProject project, Entry entry) { EnigmaServices services = project.getEnigma().getServices(); return services.get(NameProposalService.TYPE).stream().flatMap(nameProposalService -> { diff --git a/enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java b/enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java index b85851fa..4e8940ad 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java +++ b/enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java @@ -148,7 +148,7 @@ public class EnigmaDumper extends StringStreamDumper { continue; } - String javaDoc = mapping.getJavadoc(); + String javaDoc = mapping.javadoc(); if (javaDoc != null) { recordComponentDocs.add(String.format("@param %s %s", field.getFieldName(), javaDoc)); } @@ -159,7 +159,7 @@ public class EnigmaDumper extends StringStreamDumper { String javadoc = null; if (mapping != null) { - javadoc = mapping.getJavadoc(); + javadoc = mapping.javadoc(); } if (javadoc != null || !recordComponentDocs.isEmpty()) { @@ -191,7 +191,7 @@ public class EnigmaDumper extends StringStreamDumper { MethodEntry methodEntry = getMethodEntry(method); EntryMapping mapping = mapper.getDeobfMapping(methodEntry); if (mapping != null) { - String javadoc = mapping.getJavadoc(); + String javadoc = mapping.javadoc(); if (javadoc != null) { lines.addAll(Arrays.asList(javadoc.split("\\R"))); } @@ -204,9 +204,9 @@ public class EnigmaDumper extends StringStreamDumper { if (each instanceof LocalVariableEntry) { EntryMapping paramMapping = mapper.getDeobfMapping(each); if (paramMapping != null) { - String javadoc = paramMapping.getJavadoc(); + String javadoc = paramMapping.javadoc(); if (javadoc != null) { - lines.addAll(Arrays.asList(("@param " + paramMapping.getTargetName() + " " + javadoc).split("\\R"))); + lines.addAll(Arrays.asList(("@param " + paramMapping.targetName() + " " + javadoc).split("\\R"))); } } } @@ -230,7 +230,7 @@ public class EnigmaDumper extends StringStreamDumper { if (mapper != null && !recordComponent) { EntryMapping mapping = mapper.getDeobfMapping(getFieldEntry(owner, field.getFieldName(), field.getDescriptor())); if (mapping != null) { - String javadoc = mapping.getJavadoc(); + String javadoc = mapping.javadoc(); if (javadoc != null) { print("/**").newln(); for (String line : javadoc.split("\\R")) { diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java index 70fc8c6b..1e5beb1e 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java @@ -47,16 +47,17 @@ public final class AddJavadocsAstTransform implements IAstTransform { private Comment[] getComments(T node, Function> retriever) { final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node)); - final String docs = mapping == null ? null : Strings.emptyToNull(mapping.getJavadoc()); + final String docs = Strings.emptyToNull(mapping.javadoc()); return docs == null ? null : Stream.of(docs.split("\\R")).map(st -> new Comment(st, CommentType.Documentation)).toArray(Comment[]::new); } private Comment[] getParameterComments(ParameterDeclaration node, Function> retriever) { - final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node)); + Entry entry = retriever.apply(node); + final EntryMapping mapping = remapper.getDeobfMapping(entry); final Comment[] ret = getComments(node, retriever); if (ret != null) { - final String paramPrefix = "@param " + mapping.getTargetName() + " "; + final String paramPrefix = "@param " + (mapping.targetName() != null ? mapping.targetName() : entry.getName()) + " "; final String indent = Strings.repeat(" ", paramPrefix.length()); ret[0].setContent(paramPrefix + ret[0].getContent()); for (int i = 1; i < ret.length; i++) { diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryChange.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryChange.java new file mode 100644 index 00000000..b5ec8552 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryChange.java @@ -0,0 +1,97 @@ +package cuchaz.enigma.translation.mapping; + +import java.util.Objects; +import java.util.Optional; + +import javax.annotation.Nullable; + +import cuchaz.enigma.EnigmaProject; +import cuchaz.enigma.source.DecompiledClassSource; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.utils.TristateChange; + +public final class EntryChange> { + + private final E target; + private final TristateChange deobfName; + private final TristateChange javadoc; + private final TristateChange access; + + private EntryChange(E target, TristateChange deobfName, TristateChange javadoc, TristateChange access) { + this.target = target; + this.deobfName = deobfName; + this.javadoc = javadoc; + this.access = access; + } + + public static > EntryChange modify(E target) { + return new EntryChange<>(target, TristateChange.unchanged(), TristateChange.unchanged(), TristateChange.unchanged()); + } + + public EntryChange withDeobfName(String name) { + return new EntryChange<>(this.target, TristateChange.set(name), this.javadoc, this.access); + } + + public EntryChange withDefaultDeobfName(@Nullable EnigmaProject project) { + Optional proposed = project != null ? DecompiledClassSource.proposeName(project, this.target) : Optional.empty(); + return this.withDeobfName(proposed.orElse(this.target.getName())); + } + + public EntryChange clearDeobfName() { + return new EntryChange<>(this.target, TristateChange.reset(), this.javadoc, this.access); + } + + public EntryChange withJavadoc(String javadoc) { + return new EntryChange<>(this.target, this.deobfName, TristateChange.set(javadoc), this.access); + } + + public EntryChange clearJavadoc() { + return new EntryChange<>(this.target, this.deobfName, TristateChange.reset(), this.access); + } + + public EntryChange withAccess(AccessModifier access) { + return new EntryChange<>(this.target, this.deobfName, this.javadoc, TristateChange.set(access)); + } + + public EntryChange clearAccess() { + return new EntryChange<>(this.target, this.deobfName, this.javadoc, TristateChange.reset()); + } + + public TristateChange getDeobfName() { + return this.deobfName; + } + + public TristateChange getJavadoc() { + return this.javadoc; + } + + public TristateChange getAccess() { + return this.access; + } + + public E getTarget() { + return this.target; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof EntryChange)) return false; + EntryChange that = (EntryChange) o; + return Objects.equals(this.target, that.target) && + Objects.equals(this.deobfName, that.deobfName) && + Objects.equals(this.javadoc, that.javadoc) && + Objects.equals(this.access, that.access); + } + + @Override + public int hashCode() { + return Objects.hash(this.target, this.deobfName, this.javadoc, this.access); + } + + @Override + public String toString() { + return String.format("EntryChange { target: %s, deobfName: %s, javadoc: %s, access: %s }", this.target, this.deobfName, this.javadoc, this.access); + } + +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java index c607817c..e916bf35 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java @@ -1,49 +1,37 @@ package cuchaz.enigma.translation.mapping; +import java.util.Arrays; + import javax.annotation.Nonnull; import javax.annotation.Nullable; -public class EntryMapping { - private final String targetName; - private final AccessModifier accessModifier; - private final @Nullable String javadoc; +public record EntryMapping( + @Nullable String targetName, + @Nonnull AccessModifier accessModifier, + @Nullable String javadoc +) { + public static final EntryMapping DEFAULT = new EntryMapping(null, AccessModifier.UNCHANGED, null); + + public EntryMapping { + if (accessModifier == null) { + accessModifier = AccessModifier.UNCHANGED; + System.err.println("EntryMapping initialized with 'null' accessModifier, assuming UNCHANGED. Please fix."); + Arrays.stream(new Exception().getStackTrace()).skip(1).map("\tat %s"::formatted).forEach(System.err::println); + } + } - public EntryMapping(@Nonnull String targetName) { + public EntryMapping(@Nullable String targetName) { this(targetName, AccessModifier.UNCHANGED); } - public EntryMapping(@Nonnull String targetName, @Nullable String javadoc) { + public EntryMapping(@Nullable String targetName, @Nullable String javadoc) { this(targetName, AccessModifier.UNCHANGED, javadoc); } - public EntryMapping(@Nonnull String targetName, AccessModifier accessModifier) { + public EntryMapping(@Nullable String targetName, AccessModifier accessModifier) { this(targetName, accessModifier, null); } - public EntryMapping(@Nonnull String targetName, AccessModifier accessModifier, @Nullable String javadoc) { - this.targetName = targetName; - this.accessModifier = accessModifier; - this.javadoc = javadoc; - } - - @Nonnull - public String getTargetName() { - return targetName; - } - - @Nonnull - public AccessModifier getAccessModifier() { - if (accessModifier == null) { - return AccessModifier.UNCHANGED; - } - return accessModifier; - } - - @Nullable - public String getJavadoc() { - return javadoc; - } - public EntryMapping withName(String newName) { return new EntryMapping(newName, accessModifier, javadoc); } @@ -55,21 +43,4 @@ public class EntryMapping { public EntryMapping withDocs(String newDocs) { return new EntryMapping(targetName, accessModifier, newDocs); } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - - if (obj instanceof EntryMapping) { - EntryMapping mapping = (EntryMapping) obj; - return mapping.targetName.equals(targetName) && mapping.accessModifier.equals(accessModifier); - } - - return false; - } - - @Override - public int hashCode() { - return targetName.hashCode() + accessModifier.hashCode() * 31; - } } diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java index 8b5490e1..0977b742 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java @@ -1,10 +1,11 @@ package cuchaz.enigma.translation.mapping; import java.util.Collection; +import java.util.Objects; import java.util.List; import java.util.stream.Stream; -import javax.annotation.Nullable; +import javax.annotation.Nonnull; import cuchaz.enigma.analysis.index.JarIndex; import cuchaz.enigma.translation.MappingTranslator; @@ -50,15 +51,15 @@ public class EntryRemapper { return new EntryRemapper(index, new HashEntryTree<>()); } - public > void mapFromObf(ValidationContext vc, E obfuscatedEntry, @Nullable EntryMapping deobfMapping) { - mapFromObf(vc, obfuscatedEntry, deobfMapping, true); + public void validatePutMapping(ValidationContext vc, Entry obfuscatedEntry, @Nonnull EntryMapping deobfMapping) { + doPutMapping(vc, obfuscatedEntry, deobfMapping, true); } - public > void mapFromObf(ValidationContext vc, E obfuscatedEntry, @Nullable EntryMapping deobfMapping, boolean renaming) { - mapFromObf(vc, obfuscatedEntry, deobfMapping, renaming, false); + public void putMapping(ValidationContext vc, Entry obfuscatedEntry, @Nonnull EntryMapping deobfMapping) { + doPutMapping(vc, obfuscatedEntry, deobfMapping, false); } - public > void mapFromObf(ValidationContext vc, E obfuscatedEntry, @Nullable EntryMapping deobfMapping, boolean renaming, boolean validateOnly) { + private void doPutMapping(ValidationContext vc, Entry obfuscatedEntry, @Nonnull EntryMapping deobfMapping, boolean validateOnly) { if (obfuscatedEntry instanceof FieldEntry) { FieldEntry fieldEntry = (FieldEntry) obfuscatedEntry; ClassEntry classEntry = fieldEntry.getParent(); @@ -66,25 +67,27 @@ public class EntryRemapper { mapRecordComponentGetter(vc, classEntry, fieldEntry, deobfMapping); } - Collection resolvedEntries = obfResolver.resolveEntry(obfuscatedEntry, renaming ? ResolutionStrategy.RESOLVE_ROOT : ResolutionStrategy.RESOLVE_CLOSEST); + boolean renaming = !Objects.equals(getDeobfMapping(obfuscatedEntry).targetName(), deobfMapping.targetName()); - if (renaming && deobfMapping != null) { - for (E resolvedEntry : resolvedEntries) { - validator.validateRename(vc, resolvedEntry, deobfMapping.getTargetName()); + Collection> resolvedEntries = obfResolver.resolveEntry(obfuscatedEntry, renaming ? ResolutionStrategy.RESOLVE_ROOT : ResolutionStrategy.RESOLVE_CLOSEST); + + if (renaming && deobfMapping.targetName() != null) { + for (Entry resolvedEntry : resolvedEntries) { + validator.validateRename(vc, resolvedEntry, deobfMapping.targetName()); } } if (validateOnly || !vc.canProceed()) return; - for (E resolvedEntry : resolvedEntries) { - obfToDeobf.insert(resolvedEntry, deobfMapping); + for (Entry resolvedEntry : resolvedEntries) { + if (deobfMapping.equals(EntryMapping.DEFAULT)) { + obfToDeobf.insert(resolvedEntry, null); + } else { + obfToDeobf.insert(resolvedEntry, deobfMapping); + } } } - public void removeByObf(ValidationContext vc, Entry obfuscatedEntry) { - mapFromObf(vc, obfuscatedEntry, null); - } - // A little bit of a hack to also map the getter method for record fields/components. private void mapRecordComponentGetter(ValidationContext vc, ClassEntry classEntry, FieldEntry fieldEntry, EntryMapping fieldMapping) { if (!jarIndex.getEntryIndex().getClassAccess(classEntry).isRecord() || jarIndex.getEntryIndex().getFieldAccess(fieldEntry).isStatic()) { @@ -107,20 +110,17 @@ public class EntryRemapper { } if (methodEntry == null && fieldMapping != null) { - vc.raise(Message.UNKNOWN_RECORD_GETTER, fieldMapping.getTargetName()); + vc.raise(Message.UNKNOWN_RECORD_GETTER, fieldMapping.targetName()); return; } - mapFromObf(vc, methodEntry, fieldMapping != null ? new EntryMapping(fieldMapping.getTargetName()) : null); + putMapping(vc, methodEntry, fieldMapping != null ? new EntryMapping(fieldMapping.targetName()) : null); } - @Nullable + @Nonnull public EntryMapping getDeobfMapping(Entry entry) { - return obfToDeobf.get(entry); - } - - public boolean hasDeobfMapping(Entry obfEntry) { - return obfToDeobf.contains(obfEntry); + EntryMapping entryMapping = obfToDeobf.get(entry); + return entryMapping == null ? EntryMapping.DEFAULT : entryMapping; } public TranslateResult extendedDeobfuscate(T translatable) { @@ -158,4 +158,9 @@ public class EntryRemapper { public EntryResolver getObfResolver() { return obfResolver; } + + public MappingValidator getValidator() { + return validator; + } + } diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryUtil.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryUtil.java new file mode 100644 index 00000000..582076c1 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryUtil.java @@ -0,0 +1,42 @@ +package cuchaz.enigma.translation.mapping; + +import javax.annotation.Nonnull; + +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.utils.validation.ValidationContext; + +public class EntryUtil { + + public static EntryMapping applyChange(ValidationContext vc, EntryRemapper remapper, EntryChange change) { + Entry target = change.getTarget(); + EntryMapping prev = remapper.getDeobfMapping(target); + EntryMapping mapping = EntryUtil.applyChange(prev, change); + + remapper.putMapping(vc, target, mapping); + + return mapping; + } + + public static EntryMapping applyChange(@Nonnull EntryMapping self, EntryChange change) { + if (change.getDeobfName().isSet()) { + self = self.withName(change.getDeobfName().getNewValue()); + } else if (change.getDeobfName().isReset()) { + self = self.withName(null); + } + + if (change.getJavadoc().isSet()) { + self = self.withDocs(change.getJavadoc().getNewValue()); + } else if (change.getJavadoc().isReset()) { + self = self.withDocs(null); + } + + if (change.getAccess().isSet()) { + self = self.withModifier(change.getAccess().getNewValue()); + } else if (change.getAccess().isReset()) { + self = self.withModifier(AccessModifier.UNCHANGED); + } + + return self; + } + +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java index 1615912b..065e5c3e 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java @@ -25,18 +25,22 @@ public class MappingValidator { this.index = index; } - public void validateRename(ValidationContext vc, Entry entry, String name) { + public boolean validateRename(ValidationContext vc, Entry entry, String name) { Collection> equivalentEntries = index.getEntryResolver().resolveEquivalentEntries(entry); + boolean error = false; for (Entry equivalentEntry : equivalentEntries) { equivalentEntry.validateName(vc, name); - validateUnique(vc, equivalentEntry, name); + error |= validateUnique(vc, equivalentEntry, name); } + return error; } - private void validateUnique(ValidationContext vc, Entry entry, String name) { + private boolean validateUnique(ValidationContext vc, Entry entry, String name) { ClassEntry containingClass = entry.getContainingClass(); Collection relatedClasses = getRelatedClasses(containingClass); + boolean error = false; + for (ClassEntry relatedClass : relatedClasses) { Entry relatedEntry = entry.replaceAncestor(containingClass, relatedClass); Entry translatedEntry = deobfuscator.translate(relatedEntry); @@ -52,8 +56,11 @@ public class MappingValidator { } else { vc.raise(Message.NONUNIQUE_NAME, name); } + error = true; } } + + return error; } private Collection getRelatedClasses(ClassEntry classEntry) { diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java index 141a07cc..392b1a67 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java @@ -75,7 +75,7 @@ public class MappingsChecker { private final Map, String> droppedMappings = new HashMap<>(); public void drop(Entry entry, EntryMapping mapping) { - droppedMappings.put(entry, mapping.getTargetName()); + droppedMappings.put(entry, mapping.targetName() != null ? mapping.targetName() : entry.getName()); } void apply(EntryTree mappings) { diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java index 79587a0b..6465008b 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java @@ -1,23 +1,23 @@ package cuchaz.enigma.translation.mapping.serde; -import cuchaz.enigma.translation.mapping.AccessModifier; -import cuchaz.enigma.translation.mapping.EntryMapping; - import java.util.ArrayList; import java.util.List; +import cuchaz.enigma.translation.mapping.AccessModifier; +import cuchaz.enigma.translation.mapping.EntryMapping; + public final class RawEntryMapping { private final String targetName; private final AccessModifier access; - private List javadocs = new ArrayList<>(); + private final List javadocs = new ArrayList<>(); public RawEntryMapping(String targetName) { - this(targetName, null); + this(targetName, AccessModifier.UNCHANGED); } public RawEntryMapping(String targetName, AccessModifier access) { this.access = access; - this.targetName = targetName; + this.targetName = targetName != null && !targetName.equals("-") ? targetName : null; } public void addJavadocLine(String line) { diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsReader.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsReader.java index 27545c03..ccc25dff 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsReader.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsReader.java @@ -1,15 +1,21 @@ package cuchaz.enigma.translation.mapping.serde.enigma; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; + +import javax.annotation.Nullable; + import com.google.common.base.Charsets; + import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.translation.mapping.serde.MappingParseException; import cuchaz.enigma.translation.mapping.AccessModifier; import cuchaz.enigma.translation.mapping.EntryMapping; import cuchaz.enigma.translation.mapping.MappingPair; -import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; -import cuchaz.enigma.translation.mapping.serde.MappingHelper; -import cuchaz.enigma.translation.mapping.serde.MappingsReader; -import cuchaz.enigma.translation.mapping.serde.RawEntryMapping; +import cuchaz.enigma.translation.mapping.serde.*; import cuchaz.enigma.translation.mapping.tree.EntryTree; import cuchaz.enigma.translation.mapping.tree.HashEntryTree; import cuchaz.enigma.translation.representation.MethodDescriptor; @@ -17,18 +23,6 @@ import cuchaz.enigma.translation.representation.TypeDescriptor; import cuchaz.enigma.translation.representation.entry.*; import cuchaz.enigma.utils.I18n; -import javax.annotation.Nullable; -import java.io.IOException; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayDeque; -import java.util.Arrays; -import java.util.Deque; -import java.util.List; -import java.util.Locale; - public enum EnigmaMappingsReader implements MappingsReader { FILE { @Override @@ -200,12 +194,12 @@ public enum EnigmaMappingsReader implements MappingsReader { throw new RuntimeException("Unknown token '" + keyToken + "'"); } } - + private static void readJavadoc(MappingPair parent, String[] tokens) { if (parent == null) throw new IllegalStateException("Javadoc has no parent!"); // Empty string to concat - String jdLine = tokens.length > 1 ? String.join(" ", Arrays.copyOfRange(tokens,1,tokens.length)) : ""; + String jdLine = tokens.length > 1 ? String.join(" ", Arrays.copyOfRange(tokens, 1, tokens.length)) : ""; if (parent.getMapping() == null) { parent.setMapping(new RawEntryMapping(parent.getEntry().getName(), AccessModifier.UNCHANGED)); } @@ -237,11 +231,7 @@ public enum EnigmaMappingsReader implements MappingsReader { modifier = parseModifier(tokens[3]); } - if (mapping != null) { - return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier)); - } else { - return new MappingPair<>(obfuscatedEntry); - } + return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier)); } private static MappingPair parseField(@Nullable Entry parent, String[] tokens) { @@ -252,7 +242,7 @@ public enum EnigmaMappingsReader implements MappingsReader { ClassEntry ownerEntry = (ClassEntry) parent; String obfuscatedName = tokens[1]; - String mapping = obfuscatedName; + String mapping = null; AccessModifier modifier = AccessModifier.UNCHANGED; TypeDescriptor descriptor; @@ -269,19 +259,15 @@ public enum EnigmaMappingsReader implements MappingsReader { descriptor = new TypeDescriptor(tokens[3]); } } else if (tokens.length == 5) { - descriptor = new TypeDescriptor(tokens[3]); mapping = tokens[2]; - modifier = parseModifier(tokens[4]); + modifier = parseModifier(tokens[3]); + descriptor = new TypeDescriptor(tokens[4]); } else { throw new RuntimeException("Invalid field declaration"); } FieldEntry obfuscatedEntry = new FieldEntry(ownerEntry, obfuscatedName, descriptor); - if (mapping != null) { - return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier)); - } else { - return new MappingPair<>(obfuscatedEntry); - } + return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier)); } private static MappingPair parseMethod(@Nullable Entry parent, String[] tokens) { @@ -317,11 +303,7 @@ public enum EnigmaMappingsReader implements MappingsReader { } MethodEntry obfuscatedEntry = new MethodEntry(ownerEntry, obfuscatedName, descriptor); - if (mapping != null) { - return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier)); - } else { - return new MappingPair<>(obfuscatedEntry); - } + return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier)); } private static MappingPair parseArgument(@Nullable Entry parent, String[] tokens) { diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsWriter.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsWriter.java index d9f1470d..5d7a7893 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsWriter.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsWriter.java @@ -15,12 +15,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.net.URI; import java.net.URISyntaxException; -import java.nio.file.DirectoryStream; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.nio.file.*; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -28,25 +23,19 @@ import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; +import javax.annotation.Nonnull; + import cuchaz.enigma.ProgressListener; import cuchaz.enigma.translation.MappingTranslator; import cuchaz.enigma.translation.Translator; import cuchaz.enigma.translation.mapping.AccessModifier; import cuchaz.enigma.translation.mapping.EntryMapping; import cuchaz.enigma.translation.mapping.MappingDelta; -import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat; -import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; import cuchaz.enigma.translation.mapping.VoidEntryResolver; -import cuchaz.enigma.translation.mapping.serde.LfPrintWriter; -import cuchaz.enigma.translation.mapping.serde.MappingHelper; -import cuchaz.enigma.translation.mapping.serde.MappingsWriter; +import cuchaz.enigma.translation.mapping.serde.*; import cuchaz.enigma.translation.mapping.tree.EntryTree; import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.translation.representation.entry.FieldEntry; -import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; +import cuchaz.enigma.translation.representation.entry.*; import cuchaz.enigma.utils.I18n; public enum EnigmaMappingsWriter implements MappingsWriter { @@ -184,19 +173,22 @@ public enum EnigmaMappingsWriter implements MappingsWriter { EntryMapping classEntryMapping = mappings.get(classEntry); + if (classEntryMapping == null) { + classEntryMapping = EntryMapping.DEFAULT; + } + writer.println(writeClass(classEntry, classEntryMapping).trim()); - if (classEntryMapping != null && classEntryMapping.getJavadoc() != null) { + if (classEntryMapping.javadoc() != null) { writeDocs(writer, classEntryMapping, 0); } for (Entry child : children) { writeEntry(writer, mappings, child, 1); } - } private void writeDocs(PrintWriter writer, EntryMapping mapping, int depth) { - String jd = mapping.getJavadoc(); + String jd = mapping.javadoc(); if (jd != null) { for (String line : jd.split("\\R")) { writer.println(indent(EnigmaFormat.COMMENT + " " + MappingHelper.escape(line), depth + 1)); @@ -212,20 +204,26 @@ public enum EnigmaMappingsWriter implements MappingsWriter { EntryMapping mapping = node.getValue(); - if (entry instanceof ClassEntry) { - String line = writeClass((ClassEntry) entry, mapping); - writer.println(indent(line, depth)); - } else if (entry instanceof MethodEntry) { - String line = writeMethod((MethodEntry) entry, mapping); - writer.println(indent(line, depth)); - } else if (entry instanceof FieldEntry) { - String line = writeField((FieldEntry) entry, mapping); - writer.println(indent(line, depth)); - } else if (entry instanceof LocalVariableEntry && mapping != null) { - String line = writeArgument((LocalVariableEntry) entry, mapping); + if (mapping == null) { + mapping = EntryMapping.DEFAULT; + } + + String line = null; + if (entry instanceof ClassEntry classEntry) { + line = writeClass(classEntry, mapping); + } else if (entry instanceof MethodEntry methodEntry) { + line = writeMethod(methodEntry, mapping); + } else if (entry instanceof FieldEntry fieldEntry) { + line = writeField(fieldEntry, mapping); + } else if (entry instanceof LocalVariableEntry varEntry && mapping.targetName() != null) { + line = writeArgument(varEntry, mapping); + } + + if (line != null) { writer.println(indent(line, depth)); } - if (mapping != null && mapping.getJavadoc() != null) { + + if (mapping.javadoc() != null) { writeDocs(writer, mapping, depth); } @@ -254,55 +252,53 @@ public enum EnigmaMappingsWriter implements MappingsWriter { .forEach(result::add); children.stream().filter(e -> e instanceof ClassEntry) - .map(e -> (ClassEntry) e) - .sorted() - .forEach(result::add); + .map(e -> (ClassEntry) e) + .sorted() + .forEach(result::add); return result; } - protected String writeClass(ClassEntry entry, EntryMapping mapping) { - StringBuilder builder = new StringBuilder(EnigmaFormat.CLASS +" "); + protected String writeClass(ClassEntry entry, @Nonnull EntryMapping mapping) { + StringBuilder builder = new StringBuilder(EnigmaFormat.CLASS + " "); builder.append(entry.getName()).append(' '); writeMapping(builder, mapping); return builder.toString(); } - protected String writeMethod(MethodEntry entry, EntryMapping mapping) { + protected String writeMethod(MethodEntry entry, @Nonnull EntryMapping mapping) { StringBuilder builder = new StringBuilder(EnigmaFormat.METHOD + " "); builder.append(entry.getName()).append(' '); - if (mapping != null && !mapping.getTargetName().equals(entry.getName())) { - writeMapping(builder, mapping); - } + writeMapping(builder, mapping); builder.append(entry.getDesc().toString()); return builder.toString(); } - protected String writeField(FieldEntry entry, EntryMapping mapping) { + protected String writeField(FieldEntry entry, @Nonnull EntryMapping mapping) { StringBuilder builder = new StringBuilder(EnigmaFormat.FIELD + " "); builder.append(entry.getName()).append(' '); - if (mapping != null && !mapping.getTargetName().equals(entry.getName())) { - writeMapping(builder, mapping); - } + writeMapping(builder, mapping); builder.append(entry.getDesc().toString()); return builder.toString(); } - protected String writeArgument(LocalVariableEntry entry, EntryMapping mapping) { - return EnigmaFormat.PARAMETER + " " + entry.getIndex() + ' ' + mapping.getTargetName(); + protected String writeArgument(LocalVariableEntry entry, @Nonnull EntryMapping mapping) { + return EnigmaFormat.PARAMETER + " " + entry.getIndex() + ' ' + mapping.targetName(); } private void writeMapping(StringBuilder builder, EntryMapping mapping) { - if (mapping != null) { - builder.append(mapping.getTargetName()).append(' '); - if (mapping.getAccessModifier() != AccessModifier.UNCHANGED) { - builder.append(mapping.getAccessModifier().getFormattedName()).append(' '); + if (mapping.targetName() != null) { + builder.append(mapping.targetName()).append(' '); + if (mapping.accessModifier() != AccessModifier.UNCHANGED) { + builder.append(mapping.accessModifier().getFormattedName()).append(' '); } + } else if (mapping.accessModifier() != AccessModifier.UNCHANGED) { + builder.append("- ").append(mapping.accessModifier().getFormattedName()).append(' '); } } diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsWriter.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsWriter.java index 4f78e6f3..1f785e10 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsWriter.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsWriter.java @@ -73,13 +73,16 @@ public class TinyMappingsWriter implements MappingsWriter { Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE); EntryMapping mapping = mappings.get(entry); - if (mapping != null && !entry.getName().equals(mapping.getTargetName())) { + + // Do not write mappings without deobfuscated name since tiny v1 doesn't + // support comments anyway + if (mapping != null && mapping.targetName() != null) { if (entry instanceof ClassEntry) { writeClass(writer, (ClassEntry) entry, translator); } else if (entry instanceof FieldEntry) { - writeLine(writer, serializeEntry(entry, mapping.getTargetName())); + writeLine(writer, serializeEntry(entry, mapping.targetName())); } else if (entry instanceof MethodEntry) { - writeLine(writer, serializeEntry(entry, mapping.getTargetName())); + writeLine(writer, serializeEntry(entry, mapping.targetName())); } } diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Writer.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Writer.java index 5160eda3..c4005688 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Writer.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Writer.java @@ -62,8 +62,8 @@ public final class TinyV2Writer implements MappingsWriter { Deque parts = new LinkedList<>(); do { EntryMapping mapping = tree.get(classEntry); - if (mapping != null) { - parts.addFirst(mapping.getTargetName()); + if (mapping != null && mapping.targetName() != null) { + parts.addFirst(mapping.targetName()); } else { parts.addFirst(classEntry.getName()); } @@ -81,7 +81,7 @@ public final class TinyV2Writer implements MappingsWriter { writeComment(writer, node.getValue(), 1); for (EntryTreeNode child : node.getChildNodes()) { - Entry entry = child.getEntry(); + Entry entry = child.getEntry(); if (entry instanceof FieldEntry) { writeField(writer, child); } else if (entry instanceof MethodEntry) { @@ -98,16 +98,21 @@ public final class TinyV2Writer implements MappingsWriter { writer.print(node.getEntry().getName()); writer.print("\t"); EntryMapping mapping = node.getValue(); + if (mapping == null) { - writer.println(node.getEntry().getName()); // todo fix v2 name inference - } else { - writer.println(mapping.getTargetName()); + mapping = EntryMapping.DEFAULT; + } - writeComment(writer, mapping, 2); + if (mapping.targetName() != null) { + writer.println(mapping.targetName()); + } else { + writer.println(node.getEntry().getName()); // todo fix v2 name inference } + writeComment(writer, mapping, 2); + for (EntryTreeNode child : node.getChildNodes()) { - Entry entry = child.getEntry(); + Entry entry = child.getEntry(); if (entry instanceof LocalVariableEntry) { writeParameter(writer, child); } @@ -116,7 +121,7 @@ public final class TinyV2Writer implements MappingsWriter { } private void writeField(PrintWriter writer, EntryTreeNode node) { - if (node.getValue() == null) + if (node.getValue() == null || node.getValue().equals(EntryMapping.DEFAULT)) return; // Shortcut writer.print(indent(1)); @@ -126,17 +131,22 @@ public final class TinyV2Writer implements MappingsWriter { writer.print(node.getEntry().getName()); writer.print("\t"); EntryMapping mapping = node.getValue(); + if (mapping == null) { - writer.println(node.getEntry().getName()); // todo fix v2 name inference - } else { - writer.println(mapping.getTargetName()); + mapping = EntryMapping.DEFAULT; + } - writeComment(writer, mapping, 2); + if (mapping.targetName() != null) { + writer.println(mapping.targetName()); + } else { + writer.println(node.getEntry().getName()); // todo fix v2 name inference } + + writeComment(writer, mapping, 2); } private void writeParameter(PrintWriter writer, EntryTreeNode node) { - if (node.getValue() == null) + if (node.getValue() == null || node.getValue().equals(EntryMapping.DEFAULT)) return; // Shortcut writer.print(indent(2)); @@ -146,20 +156,20 @@ public final class TinyV2Writer implements MappingsWriter { writer.print(node.getEntry().getName()); writer.print("\t"); EntryMapping mapping = node.getValue(); - if (mapping == null) { + if (mapping == null || mapping.targetName() == null) { writer.println(); // todo ??? } else { - writer.println(mapping.getTargetName()); + writer.println(mapping.targetName()); writeComment(writer, mapping, 3); } } private void writeComment(PrintWriter writer, EntryMapping mapping, int indent) { - if (mapping != null && mapping.getJavadoc() != null) { + if (mapping != null && mapping.javadoc() != null) { writer.print(indent(indent)); writer.print("c\t"); - writer.print(MappingHelper.escape(mapping.getJavadoc())); + writer.print(MappingHelper.escape(mapping.javadoc())); writer.println(); } } diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java index 570941cd..0992d342 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java @@ -6,6 +6,7 @@ import cuchaz.enigma.translation.mapping.EntryMapping; import cuchaz.enigma.translation.mapping.EntryResolver; import cuchaz.enigma.translation.representation.entry.Entry; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.*; import java.util.function.Function; @@ -152,6 +153,7 @@ public class HashEntryTree implements EntryTree { } @Override + @Nonnull public Iterator> iterator() { Collection> nodes = new ArrayList<>(); for (EntryTreeNode node : root.values()) { diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/Lambda.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/Lambda.java index a6aed731..13c7cd48 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/representation/Lambda.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/Lambda.java @@ -35,9 +35,9 @@ public class Lambda implements Translatable { EntryMapping samMethodMapping = resolveMapping(resolver, mappings, samMethod); return TranslateResult.of( - samMethodMapping == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, + samMethodMapping.targetName() == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, new Lambda( - samMethodMapping != null ? samMethodMapping.getTargetName() : invokedName, + samMethodMapping.targetName() != null ? samMethodMapping.targetName() : invokedName, invokedType.extendedTranslate(translator, resolver, mappings).getValue(), samMethodType.extendedTranslate(translator, resolver, mappings).getValue(), implMethod.extendedTranslate(translator, resolver, mappings).getValue(), @@ -53,7 +53,7 @@ public class Lambda implements Translatable { return mapping; } } - return null; + return EntryMapping.DEFAULT; } public ClassEntry getInterface() { diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java index b0fd2865..237c93dc 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java @@ -13,6 +13,7 @@ package cuchaz.enigma.translation.representation.entry; import java.util.Arrays; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import com.google.common.base.Preconditions; @@ -75,15 +76,15 @@ public class ClassDefEntry extends ClassEntry implements DefEntry { } @Override - public TranslateResult extendedTranslate(Translator translator, @Nullable EntryMapping mapping) { + public TranslateResult extendedTranslate(Translator translator, @Nonnull EntryMapping mapping) { Signature translatedSignature = translator.translate(signature); - String translatedName = mapping != null ? mapping.getTargetName() : name; - AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access; + String translatedName = mapping.targetName() != null ? mapping.targetName() : name; + AccessFlags translatedAccess = mapping.accessModifier().transform(access); ClassEntry translatedSuper = translator.translate(superClass); ClassEntry[] translatedInterfaces = Arrays.stream(interfaces).map(translator::translate).toArray(ClassEntry[]::new); - String docs = mapping != null ? mapping.getJavadoc() : null; + String docs = mapping.javadoc(); return TranslateResult.of( - mapping == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, + mapping.targetName() == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, new ClassDefEntry(parent, translatedName, translatedSignature, translatedAccess, translatedSuper, translatedInterfaces, docs) ); } diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java index fe56611b..ec5294ed 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java @@ -82,16 +82,16 @@ public class ClassEntry extends ParentedEntry implements Comparable< } @Override - public TranslateResult extendedTranslate(Translator translator, @Nullable EntryMapping mapping) { + public TranslateResult extendedTranslate(Translator translator, @Nonnull EntryMapping mapping) { if (name.charAt(0) == '[') { TranslateResult translatedName = translator.extendedTranslate(new TypeDescriptor(name)); return translatedName.map(desc -> new ClassEntry(parent, desc.toString())); } - String translatedName = mapping != null ? mapping.getTargetName() : name; - String docs = mapping != null ? mapping.getJavadoc() : null; + String translatedName = mapping.targetName() != null ? mapping.targetName() : name; + String docs = mapping.javadoc(); return TranslateResult.of( - mapping == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, + mapping.targetName() == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, new ClassEntry(parent, translatedName, docs) ); } diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java index 8a81c54a..956f32ca 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java @@ -13,6 +13,7 @@ package cuchaz.enigma.translation.representation.entry; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import javax.annotation.Nullable; @@ -107,16 +108,29 @@ public interface Entry

> extends Translatable { boolean canConflictWith(Entry entry); - @Nullable default ClassEntry getContainingClass() { - P parent = getParent(); - if (parent == null) { - return null; + ClassEntry last = null; + Entry current = this; + while (current != null) { + if (current instanceof ClassEntry) { + last = (ClassEntry) current; + break; + } + current = current.getParent(); } - if (parent instanceof ClassEntry) { - return (ClassEntry) parent; + return Objects.requireNonNull(last, () -> String.format("%s has no containing class?", this)); + } + + default ClassEntry getTopLevelClass() { + ClassEntry last = null; + Entry current = this; + while (current != null) { + if (current instanceof ClassEntry) { + last = (ClassEntry) current; + } + current = current.getParent(); } - return parent.getContainingClass(); + return Objects.requireNonNull(last, () -> String.format("%s has no top level class?", this)); } default List> getAncestry() { diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java index b9da6cca..0efb6a99 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java @@ -11,8 +11,6 @@ package cuchaz.enigma.translation.representation.entry; -import javax.annotation.Nullable; - import com.google.common.base.Preconditions; import cuchaz.enigma.source.RenamableTokenType; @@ -23,6 +21,8 @@ import cuchaz.enigma.translation.representation.AccessFlags; import cuchaz.enigma.translation.representation.Signature; import cuchaz.enigma.translation.representation.TypeDescriptor; +import javax.annotation.Nonnull; + public class FieldDefEntry extends FieldEntry implements DefEntry { private final AccessFlags access; private final Signature signature; @@ -53,14 +53,14 @@ public class FieldDefEntry extends FieldEntry implements DefEntry { } @Override - protected TranslateResult extendedTranslate(Translator translator, @Nullable EntryMapping mapping) { + protected TranslateResult extendedTranslate(Translator translator, @Nonnull EntryMapping mapping) { TypeDescriptor translatedDesc = translator.translate(desc); Signature translatedSignature = translator.translate(signature); - String translatedName = mapping != null ? mapping.getTargetName() : name; - AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access; - String docs = mapping != null ? mapping.getJavadoc() : null; + String translatedName = mapping.targetName() != null ? mapping.targetName() : name; + AccessFlags translatedAccess = mapping.accessModifier().transform(access); + String docs = mapping.javadoc(); return TranslateResult.of( - mapping == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, + mapping.targetName() == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, new FieldDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess, docs) ); } diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java index 5ddd33de..db940118 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java @@ -13,7 +13,7 @@ package cuchaz.enigma.translation.representation.entry; import java.util.Objects; -import javax.annotation.Nullable; +import javax.annotation.Nonnull; import com.google.common.base.Preconditions; @@ -63,11 +63,11 @@ public class FieldEntry extends ParentedEntry implements Comparable< } @Override - protected TranslateResult extendedTranslate(Translator translator, @Nullable EntryMapping mapping) { - String translatedName = mapping != null ? mapping.getTargetName() : name; - String docs = mapping != null ? mapping.getJavadoc() : null; + protected TranslateResult extendedTranslate(Translator translator, @Nonnull EntryMapping mapping) { + String translatedName = mapping.targetName() != null ? mapping.targetName() : name; + String docs = mapping.javadoc(); return TranslateResult.of( - mapping == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, + mapping.targetName() == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, new FieldEntry(parent, translatedName, translator.translate(desc), docs) ); } diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java index 2712c65a..c151de4e 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java @@ -1,6 +1,6 @@ package cuchaz.enigma.translation.representation.entry; -import javax.annotation.Nullable; +import javax.annotation.Nonnull; import com.google.common.base.Preconditions; @@ -30,12 +30,12 @@ public class LocalVariableDefEntry extends LocalVariableEntry { } @Override - protected TranslateResult extendedTranslate(Translator translator, @Nullable EntryMapping mapping) { + protected TranslateResult extendedTranslate(Translator translator, @Nonnull EntryMapping mapping) { TypeDescriptor translatedDesc = translator.translate(desc); - String translatedName = mapping != null ? mapping.getTargetName() : name; - String javadoc = mapping != null ? mapping.getJavadoc() : javadocs; + String translatedName = mapping.targetName() != null ? mapping.targetName() : name; + String javadoc = mapping.javadoc(); return TranslateResult.of( - mapping == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, + mapping.targetName() == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, new LocalVariableDefEntry(parent, index, translatedName, parameter, translatedDesc, javadoc) ); } diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java index 154f11fd..1cf1a832 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java @@ -2,7 +2,7 @@ package cuchaz.enigma.translation.representation.entry; import java.util.Objects; -import javax.annotation.Nullable; +import javax.annotation.Nonnull; import com.google.common.base.Preconditions; @@ -50,11 +50,11 @@ public class LocalVariableEntry extends ParentedEntry implements Co } @Override - protected TranslateResult extendedTranslate(Translator translator, @Nullable EntryMapping mapping) { - String translatedName = mapping != null ? mapping.getTargetName() : name; - String javadoc = mapping != null ? mapping.getJavadoc() : null; + protected TranslateResult extendedTranslate(Translator translator, @Nonnull EntryMapping mapping) { + String translatedName = mapping.targetName() != null ? mapping.targetName() : name; + String javadoc = mapping.javadoc(); return TranslateResult.of( - mapping == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, + mapping.targetName() == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, new LocalVariableEntry(parent, index, translatedName, parameter, javadoc) ); } diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java index cc326d6a..30ef706e 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java @@ -11,7 +11,7 @@ package cuchaz.enigma.translation.representation.entry; -import javax.annotation.Nullable; +import javax.annotation.Nonnull; import com.google.common.base.Preconditions; @@ -53,14 +53,14 @@ public class MethodDefEntry extends MethodEntry implements DefEntry } @Override - protected TranslateResult extendedTranslate(Translator translator, @Nullable EntryMapping mapping) { + protected TranslateResult extendedTranslate(Translator translator, @Nonnull EntryMapping mapping) { MethodDescriptor translatedDesc = translator.translate(descriptor); Signature translatedSignature = translator.translate(signature); - String translatedName = mapping != null ? mapping.getTargetName() : name; - AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access; - String docs = mapping != null ? mapping.getJavadoc() : null; + String translatedName = mapping.targetName() != null ? mapping.targetName() : name; + AccessFlags translatedAccess = mapping.accessModifier().transform(access); + String docs = mapping.javadoc(); return TranslateResult.of( - mapping == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, + mapping.targetName() == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, new MethodDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess, docs) ); } diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java index 864a5802..ab9c2d17 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java @@ -13,7 +13,7 @@ package cuchaz.enigma.translation.representation.entry; import java.util.Objects; -import javax.annotation.Nullable; +import javax.annotation.Nonnull; import com.google.common.base.Preconditions; @@ -58,11 +58,11 @@ public class MethodEntry extends ParentedEntry implements Comparable } @Override - protected TranslateResult extendedTranslate(Translator translator, @Nullable EntryMapping mapping) { - String translatedName = mapping != null ? mapping.getTargetName() : name; - String docs = mapping != null ? mapping.getJavadoc() : null; + protected TranslateResult extendedTranslate(Translator translator, @Nonnull EntryMapping mapping) { + String translatedName = mapping.targetName() != null ? mapping.targetName() : name; + String docs = mapping.javadoc(); return TranslateResult.of( - mapping == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, + mapping.targetName() == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, new MethodEntry(parent, translatedName, translator.translate(descriptor), docs) ); } diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java index 56348915..267bc11a 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java @@ -11,6 +11,7 @@ package cuchaz.enigma.translation.representation.entry; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import com.google.common.base.Preconditions; @@ -41,7 +42,7 @@ public abstract class ParentedEntry

> implements Entry

{ @Override public abstract ParentedEntry

withName(String name); - protected abstract TranslateResult> extendedTranslate(Translator translator, @Nullable EntryMapping mapping); + protected abstract TranslateResult> extendedTranslate(Translator translator, @Nonnull EntryMapping mapping); @Override public String getName() { @@ -93,6 +94,6 @@ public abstract class ParentedEntry

> implements Entry

{ return mapping; } } - return null; + return EntryMapping.DEFAULT; } } diff --git a/enigma/src/main/java/cuchaz/enigma/utils/TristateChange.java b/enigma/src/main/java/cuchaz/enigma/utils/TristateChange.java new file mode 100644 index 00000000..864154cc --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/utils/TristateChange.java @@ -0,0 +1,78 @@ +package cuchaz.enigma.utils; + +import java.util.Objects; + +public final class TristateChange { + + private static final TristateChange UNCHANGED = new TristateChange<>(Type.UNCHANGED, null); + private static final TristateChange RESET = new TristateChange<>(Type.RESET, null); + + private final Type type; + private final T val; + + @SuppressWarnings("unchecked") + public static TristateChange unchanged() { + return (TristateChange) TristateChange.UNCHANGED; + } + + @SuppressWarnings("unchecked") + public static TristateChange reset() { + return (TristateChange) TristateChange.RESET; + } + + public static TristateChange set(T value) { + return new TristateChange<>(Type.SET, value); + } + + private TristateChange(Type type, T val) { + this.type = type; + this.val = val; + } + + public Type getType() { + return this.type; + } + + public boolean isUnchanged() { + return this.type == Type.UNCHANGED; + } + + public boolean isReset() { + return this.type == Type.RESET; + } + + public boolean isSet() { + return this.type == Type.SET; + } + + public T getNewValue() { + if (this.type != Type.SET) throw new IllegalStateException(String.format("No concrete value in %s", this)); + return this.val; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TristateChange that = (TristateChange) o; + return type == that.type && + Objects.equals(val, that.val); + } + + @Override + public int hashCode() { + return Objects.hash(type, val); + } + + @Override + public String toString() { + return String.format("TristateChange { type: %s, val: %s }", type, val); + } + + public enum Type { + UNCHANGED, + RESET, + SET, + } + +} diff --git a/enigma/src/main/java/cuchaz/enigma/utils/validation/PrintValidatable.java b/enigma/src/main/java/cuchaz/enigma/utils/validation/PrintValidatable.java index 81b6c43f..5067d7e3 100644 --- a/enigma/src/main/java/cuchaz/enigma/utils/validation/PrintValidatable.java +++ b/enigma/src/main/java/cuchaz/enigma/utils/validation/PrintValidatable.java @@ -1,5 +1,6 @@ package cuchaz.enigma.utils.validation; +import java.io.PrintStream; import java.util.Arrays; public class PrintValidatable implements Validatable { @@ -8,6 +9,10 @@ public class PrintValidatable implements Validatable { @Override public void addMessage(ParameterizedMessage message) { + formatMessage(System.out, message); + } + + public static void formatMessage(PrintStream w, ParameterizedMessage message) { String text = message.getText(); String longText = message.getLongText(); String type = switch (message.message.type) { @@ -15,9 +20,10 @@ public class PrintValidatable implements Validatable { case WARNING -> "warning"; case ERROR -> "error"; }; - System.out.printf("%s: %s\n", type, text); + w.printf("%s: %s\n", type, text); + if (!longText.isEmpty()) { - Arrays.stream(longText.split("\n")).forEach(s -> System.out.printf(" %s\n", s)); + Arrays.stream(longText.split("\n")).forEach(s -> w.printf(" %s\n", s)); } } diff --git a/enigma/src/main/java/cuchaz/enigma/utils/validation/ValidationContext.java b/enigma/src/main/java/cuchaz/enigma/utils/validation/ValidationContext.java index ee2b5956..0ecb9fb3 100644 --- a/enigma/src/main/java/cuchaz/enigma/utils/validation/ValidationContext.java +++ b/enigma/src/main/java/cuchaz/enigma/utils/validation/ValidationContext.java @@ -62,6 +62,20 @@ public class ValidationContext { return messages.stream().noneMatch(m -> m.message.type == Type.ERROR); } + /** + * If this validation context has at least one error, throw an exception. + * + * @throws IllegalStateException if errors are present + */ + public void throwOnError() { + if (!this.canProceed()) { + for (ParameterizedMessage message : this.messages) { + PrintValidatable.formatMessage(System.err, message); + } + throw new IllegalStateException("Errors encountered; cannot continue! Check error log for details."); + } + } + public List getMessages() { return Collections.unmodifiableList(messages); } diff --git a/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestReadWriteCycle.java b/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestReadWriteCycle.java index 4f6654a4..510dd3cd 100644 --- a/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestReadWriteCycle.java +++ b/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestReadWriteCycle.java @@ -85,18 +85,18 @@ public class TestReadWriteCycle { Assert.assertTrue("Loaded mappings don't contain testMethod1", loadedMappings.contains(testMethod1.a)); Assert.assertTrue("Loaded mappings don't contain testMethod2", loadedMappings.contains(testMethod2.a)); - Assert.assertEquals("Incorrect mapping: testClazz", testClazz.b.getTargetName(), loadedMappings.get(testClazz.a).getTargetName()); - Assert.assertEquals("Incorrect mapping: testField1", testField1.b.getTargetName(), loadedMappings.get(testField1.a).getTargetName()); - Assert.assertEquals("Incorrect mapping: testField2", testField2.b.getTargetName(), loadedMappings.get(testField2.a).getTargetName()); - Assert.assertEquals("Incorrect mapping: testMethod1", testMethod1.b.getTargetName(), loadedMappings.get(testMethod1.a).getTargetName()); - Assert.assertEquals("Incorrect mapping: testMethod2", testMethod2.b.getTargetName(), loadedMappings.get(testMethod2.a).getTargetName()); + Assert.assertEquals("Incorrect mapping: testClazz", testClazz.b.targetName(), loadedMappings.get(testClazz.a).targetName()); + Assert.assertEquals("Incorrect mapping: testField1", testField1.b.targetName(), loadedMappings.get(testField1.a).targetName()); + Assert.assertEquals("Incorrect mapping: testField2", testField2.b.targetName(), loadedMappings.get(testField2.a).targetName()); + Assert.assertEquals("Incorrect mapping: testMethod1", testMethod1.b.targetName(), loadedMappings.get(testMethod1.a).targetName()); + Assert.assertEquals("Incorrect mapping: testMethod2", testMethod2.b.targetName(), loadedMappings.get(testMethod2.a).targetName()); if (supportsJavadoc) { - Assert.assertEquals("Incorrect javadoc: testClazz", testClazz.b.getJavadoc(), loadedMappings.get(testClazz.a).getJavadoc()); - Assert.assertEquals("Incorrect javadoc: testField1", testField1.b.getJavadoc(), loadedMappings.get(testField1.a).getJavadoc()); - Assert.assertEquals("Incorrect javadoc: testField2", testField2.b.getJavadoc(), loadedMappings.get(testField2.a).getJavadoc()); - Assert.assertEquals("Incorrect javadoc: testMethod1", testMethod1.b.getJavadoc(), loadedMappings.get(testMethod1.a).getJavadoc()); - Assert.assertEquals("Incorrect javadoc: testMethod2", testMethod2.b.getJavadoc(), loadedMappings.get(testMethod2.a).getJavadoc()); + Assert.assertEquals("Incorrect javadoc: testClazz", testClazz.b.javadoc(), loadedMappings.get(testClazz.a).javadoc()); + Assert.assertEquals("Incorrect javadoc: testField1", testField1.b.javadoc(), loadedMappings.get(testField1.a).javadoc()); + Assert.assertEquals("Incorrect javadoc: testField2", testField2.b.javadoc(), loadedMappings.get(testField2.a).javadoc()); + Assert.assertEquals("Incorrect javadoc: testMethod1", testMethod1.b.javadoc(), loadedMappings.get(testMethod1.a).javadoc()); + Assert.assertEquals("Incorrect javadoc: testMethod2", testMethod2.b.javadoc(), loadedMappings.get(testMethod2.a).javadoc()); } tempFile.delete(); -- cgit v1.2.3