From 0f47403d0220757fed189b76e2071e25b1025cb8 Mon Sep 17 00:00:00 2001 From: Runemoro Date: Wed, 3 Jun 2020 13:39:42 -0400 Subject: Split GUI code to separate module (#242) * Split into modules * Post merge compile fixes Co-authored-by: modmuss50 --- .gitignore | 246 +---- README.md | 4 +- build.gradle | 174 +--- docs/protocol.md | 366 ------- enigma-cli/build.gradle | 5 + .../enigma/command/CheckMappingsCommand.java | 77 ++ .../main/java/cuchaz/enigma/command/Command.java | 154 +++ .../enigma/command/ComposeMappingsCommand.java | 42 + .../enigma/command/ConvertMappingsCommand.java | 39 + .../cuchaz/enigma/command/DecompileCommand.java | 54 + .../cuchaz/enigma/command/DeobfuscateCommand.java | 37 + .../enigma/command/InvertMappingsCommand.java | 41 + .../src/main/java/cuchaz/enigma/command/Main.java | 105 ++ .../command/MapSpecializedMethodsCommand.java | 71 ++ .../cuchaz/enigma/command/MappingCommandsUtil.java | 87 ++ .../enigma/command/CheckMappingsCommandTest.java | 21 + .../correctMappings/base/Base.mapping | 1 + .../packageAccess/correctMappings/base/One.mapping | 1 + .../packageAccess/correctMappings/two/Two.mapping | 1 + .../packageAccess/wrongMappings/base/Base.mapping | 1 + .../packageAccess/wrongMappings/one/One.mapping | 1 + .../packageAccess/wrongMappings/two/Two.mapping | 1 + enigma-server/build.gradle | 6 + enigma-server/docs/protocol.md | 366 +++++++ .../cuchaz/enigma/network/ClientPacketHandler.java | 29 + .../enigma/network/DedicatedEnigmaServer.java | 200 ++++ .../java/cuchaz/enigma/network/EnigmaClient.java | 78 ++ .../java/cuchaz/enigma/network/EnigmaServer.java | 290 ++++++ .../enigma/network/IntegratedEnigmaServer.java | 16 + .../main/java/cuchaz/enigma/network/Message.java | 393 ++++++++ .../cuchaz/enigma/network/ServerPacketHandler.java | 22 + .../enigma/network/packet/ChangeDocsC2SPacket.java | 59 ++ .../enigma/network/packet/ChangeDocsS2CPacket.java | 44 + .../network/packet/ConfirmChangeC2SPacket.java | 33 + .../enigma/network/packet/KickS2CPacket.java | 33 + .../enigma/network/packet/LoginC2SPacket.java | 75 ++ .../network/packet/MarkDeobfuscatedC2SPacket.java | 48 + .../network/packet/MarkDeobfuscatedS2CPacket.java | 40 + .../enigma/network/packet/MessageC2SPacket.java | 39 + .../enigma/network/packet/MessageS2CPacket.java | 36 + .../java/cuchaz/enigma/network/packet/Packet.java | 15 + .../cuchaz/enigma/network/packet/PacketHelper.java | 135 +++ .../enigma/network/packet/PacketRegistry.java | 64 ++ .../network/packet/RemoveMappingC2SPacket.java | 55 + .../network/packet/RemoveMappingS2CPacket.java | 40 + .../enigma/network/packet/RenameC2SPacket.java | 64 ++ .../enigma/network/packet/RenameS2CPacket.java | 48 + .../network/packet/SyncMappingsS2CPacket.java | 88 ++ .../enigma/network/packet/UserListS2CPacket.java | 44 + enigma-swing/build.gradle | 23 + .../main/java/cuchaz/enigma/gui/BrowserCaret.java | 28 + .../main/java/cuchaz/enigma/gui/ClassSelector.java | 532 ++++++++++ .../main/java/cuchaz/enigma/gui/CodeReader.java | 73 ++ .../java/cuchaz/enigma/gui/ConnectionState.java | 7 + .../cuchaz/enigma/gui/DecompiledClassSource.java | 160 +++ .../cuchaz/enigma/gui/EnigmaQuickFindDialog.java | 90 ++ .../java/cuchaz/enigma/gui/EnigmaSyntaxKit.java | 44 + .../java/cuchaz/enigma/gui/ExceptionIgnorer.java | 35 + .../src/main/java/cuchaz/enigma/gui/Gui.java | 1058 ++++++++++++++++++++ .../main/java/cuchaz/enigma/gui/GuiController.java | 719 +++++++++++++ .../src/main/java/cuchaz/enigma/gui/Main.java | 118 +++ .../cuchaz/enigma/gui/MessageListCellRenderer.java | 24 + .../cuchaz/enigma/gui/MethodTreeCellRenderer.java | 42 + .../java/cuchaz/enigma/gui/QuickFindAction.java | 45 + .../main/java/cuchaz/enigma/gui/ReadableToken.java | 30 + .../main/java/cuchaz/enigma/gui/RefreshMode.java | 7 + .../cuchaz/enigma/gui/TokenListCellRenderer.java | 35 + .../main/java/cuchaz/enigma/gui/config/Config.java | 261 +++++ .../main/java/cuchaz/enigma/gui/config/Themes.java | 45 + .../java/cuchaz/enigma/gui/dialog/AboutDialog.java | 70 ++ .../cuchaz/enigma/gui/dialog/ChangeDialog.java | 50 + .../enigma/gui/dialog/ConnectToServerDialog.java | 82 ++ .../java/cuchaz/enigma/gui/dialog/CrashDialog.java | 105 ++ .../enigma/gui/dialog/CreateServerDialog.java | 65 ++ .../cuchaz/enigma/gui/dialog/JavadocDialog.java | 159 +++ .../cuchaz/enigma/gui/dialog/ProgressDialog.java | 109 ++ .../cuchaz/enigma/gui/dialog/SearchDialog.java | 261 +++++ .../java/cuchaz/enigma/gui/dialog/StatsDialog.java | 82 ++ .../enigma/gui/elements/CollapsibleTabbedPane.java | 40 + .../java/cuchaz/enigma/gui/elements/MenuBar.java | 386 +++++++ .../cuchaz/enigma/gui/elements/PopupMenuBar.java | 125 +++ .../enigma/gui/filechooser/FileChooserAny.java | 10 + .../enigma/gui/filechooser/FileChooserFile.java | 8 + .../enigma/gui/filechooser/FileChooserFolder.java | 11 + .../enigma/gui/highlight/BoxHighlightPainter.java | 69 ++ .../gui/highlight/SelectionHighlightPainter.java | 31 + .../enigma/gui/highlight/TokenHighlightType.java | 7 + .../enigma/gui/node/ClassSelectorClassNode.java | 72 ++ .../enigma/gui/node/ClassSelectorPackageNode.java | 58 ++ .../java/cuchaz/enigma/gui/panels/PanelDeobf.java | 26 + .../java/cuchaz/enigma/gui/panels/PanelEditor.java | 171 ++++ .../cuchaz/enigma/gui/panels/PanelIdentifier.java | 32 + .../java/cuchaz/enigma/gui/panels/PanelObf.java | 37 + .../java/cuchaz/enigma/gui/search/SearchEntry.java | 17 + .../java/cuchaz/enigma/gui/search/SearchUtil.java | 268 +++++ .../cuchaz/enigma/gui/stats/StatsGenerator.java | 197 ++++ .../java/cuchaz/enigma/gui/stats/StatsMember.java | 8 + .../enigma/gui/util/AbstractListCellRenderer.java | 77 ++ .../main/java/cuchaz/enigma/gui/util/GuiUtil.java | 56 ++ .../main/java/cuchaz/enigma/gui/util/History.java | 49 + .../enigma/gui/util/ScaleChangeListener.java | 8 + .../java/cuchaz/enigma/gui/util/ScaleUtil.java | 110 ++ enigma-swing/src/main/resources/about.html | 6 + enigma-swing/src/main/resources/stats.html | 34 + enigma/build.gradle | 63 ++ .../src/main/java/cuchaz/enigma/ClassProvider.java | 10 + enigma/src/main/java/cuchaz/enigma/Enigma.java | 139 +++ .../src/main/java/cuchaz/enigma/EnigmaProfile.java | 131 +++ .../src/main/java/cuchaz/enigma/EnigmaProject.java | 288 ++++++ .../main/java/cuchaz/enigma/EnigmaServices.java | 20 + .../main/java/cuchaz/enigma/ProgressListener.java | 19 + .../main/java/cuchaz/enigma/analysis/Access.java | 43 + .../java/cuchaz/enigma/analysis/BuiltinPlugin.java | 156 +++ .../java/cuchaz/enigma/analysis/ClassCache.java | 126 +++ .../analysis/ClassImplementationsTreeNode.java | 72 ++ .../enigma/analysis/ClassInheritanceTreeNode.java | 72 ++ .../enigma/analysis/ClassReferenceTreeNode.java | 94 ++ .../cuchaz/enigma/analysis/EntryReference.java | 140 +++ .../enigma/analysis/FieldReferenceTreeNode.java | 83 ++ .../enigma/analysis/IndexSimpleVerifier.java | 154 +++ .../cuchaz/enigma/analysis/IndexTreeBuilder.java | 74 ++ .../cuchaz/enigma/analysis/InterpreterPair.java | 130 +++ .../analysis/MethodImplementationsTreeNode.java | 85 ++ .../enigma/analysis/MethodInheritanceTreeNode.java | 95 ++ .../enigma/analysis/MethodNodeWithAction.java | 19 + .../enigma/analysis/MethodReferenceTreeNode.java | 113 +++ .../enigma/analysis/ReferenceTargetType.java | 74 ++ .../cuchaz/enigma/analysis/ReferenceTreeNode.java | 20 + .../enigma/analysis/index/BridgeMethodIndex.java | 156 +++ .../cuchaz/enigma/analysis/index/EntryIndex.java | 102 ++ .../enigma/analysis/index/IndexClassVisitor.java | 40 + .../analysis/index/IndexReferenceVisitor.java | 180 ++++ .../enigma/analysis/index/InheritanceIndex.java | 127 +++ .../cuchaz/enigma/analysis/index/JarIndex.java | 170 ++++ .../cuchaz/enigma/analysis/index/JarIndexer.java | 28 + .../analysis/index/PackageVisibilityIndex.java | 147 +++ .../enigma/analysis/index/ReferenceIndex.java | 148 +++ .../main/java/cuchaz/enigma/api/EnigmaPlugin.java | 5 + .../cuchaz/enigma/api/EnigmaPluginContext.java | 9 + .../cuchaz/enigma/api/service/EnigmaService.java | 4 + .../enigma/api/service/EnigmaServiceContext.java | 11 + .../enigma/api/service/EnigmaServiceFactory.java | 5 + .../enigma/api/service/EnigmaServiceType.java | 29 + .../enigma/api/service/JarIndexerService.java | 10 + .../enigma/api/service/NameProposalService.java | 12 + .../enigma/api/service/ObfuscationTestService.java | 9 + .../bytecode/translators/AsmObjectTranslator.java | 46 + .../translators/LocalVariableFixVisitor.java | 126 +++ .../bytecode/translators/SourceFixVisitor.java | 39 + .../translators/TranslationAnnotationVisitor.java | 51 + .../translators/TranslationClassVisitor.java | 102 ++ .../translators/TranslationFieldVisitor.java | 33 + .../translators/TranslationMethodVisitor.java | 145 +++ .../translators/TranslationSignatureVisitor.java | 129 +++ .../main/java/cuchaz/enigma/source/Decompiler.java | 5 + .../cuchaz/enigma/source/DecompilerService.java | 11 + .../java/cuchaz/enigma/source/Decompilers.java | 9 + .../src/main/java/cuchaz/enigma/source/Source.java | 11 + .../java/cuchaz/enigma/source/SourceIndex.java | 172 ++++ .../java/cuchaz/enigma/source/SourceRemapper.java | 62 ++ .../java/cuchaz/enigma/source/SourceSettings.java | 11 + .../src/main/java/cuchaz/enigma/source/Token.java | 72 ++ .../cuchaz/enigma/source/cfr/CfrDecompiler.java | 108 ++ .../java/cuchaz/enigma/source/cfr/CfrSource.java | 38 + .../cuchaz/enigma/source/cfr/EnigmaDumper.java | 433 ++++++++ .../cuchaz/enigma/source/procyon/EntryParser.java | 49 + .../enigma/source/procyon/ProcyonDecompiler.java | 84 ++ .../enigma/source/procyon/ProcyonSource.java | 49 + .../procyon/index/SourceIndexClassVisitor.java | 95 ++ .../procyon/index/SourceIndexMethodVisitor.java | 218 ++++ .../source/procyon/index/SourceIndexVisitor.java | 40 + .../enigma/source/procyon/index/TokenFactory.java | 46 + .../transformers/AddJavadocsAstTransform.java | 134 +++ .../transformers/DropImportAstTransform.java | 33 + .../transformers/DropVarModifiersAstTransform.java | 37 + .../procyon/transformers/InvalidIdentifierFix.java | 29 + .../source/procyon/transformers/Java8Generics.java | 107 ++ .../ObfuscatedEnumSwitchRewriterTransform.java | 414 ++++++++ .../procyon/transformers/RemoveObjectCasts.java | 39 + .../source/procyon/transformers/VarargsFixer.java | 197 ++++ .../typeloader/CachingClasspathTypeLoader.java | 33 + .../procyon/typeloader/CachingTypeLoader.java | 38 + .../typeloader/CompiledSourceTypeLoader.java | 140 +++ .../procyon/typeloader/NoRetryMetadataSystem.java | 38 + .../procyon/typeloader/SynchronizedTypeLoader.java | 20 + .../enigma/translation/LocalNameGenerator.java | 44 + .../enigma/translation/MappingTranslator.java | 24 + .../enigma/translation/ProposingTranslator.java | 51 + .../enigma/translation/SignatureUpdater.java | 92 ++ .../cuchaz/enigma/translation/Translatable.java | 9 + .../enigma/translation/TranslationDirection.java | 36 + .../java/cuchaz/enigma/translation/Translator.java | 61 ++ .../cuchaz/enigma/translation/VoidTranslator.java | 10 + .../enigma/translation/mapping/AccessModifier.java | 25 + .../enigma/translation/mapping/EntryMap.java | 24 + .../enigma/translation/mapping/EntryMapping.java | 75 ++ .../enigma/translation/mapping/EntryRemapper.java | 104 ++ .../enigma/translation/mapping/EntryResolver.java | 41 + .../translation/mapping/IllegalNameException.java | 39 + .../translation/mapping/IndexEntryResolver.java | 227 +++++ .../enigma/translation/mapping/MappingDelta.java | 54 + .../translation/mapping/MappingOperations.java | 71 ++ .../enigma/translation/mapping/MappingPair.java | 32 + .../translation/mapping/MappingValidator.java | 75 ++ .../translation/mapping/MappingsChecker.java | 99 ++ .../enigma/translation/mapping/NameValidator.java | 50 + .../translation/mapping/ResolutionStrategy.java | 6 + .../translation/mapping/VoidEntryResolver.java | 27 + .../translation/mapping/serde/LfPrintWriter.java | 16 + .../mapping/serde/MappingFileNameFormat.java | 10 + .../translation/mapping/serde/MappingFormat.java | 65 ++ .../translation/mapping/serde/MappingHelper.java | 51 + .../mapping/serde/MappingParseException.java | 39 + .../mapping/serde/MappingSaveParameters.java | 16 + .../translation/mapping/serde/MappingsReader.java | 12 + .../translation/mapping/serde/MappingsWriter.java | 16 + .../translation/mapping/serde/RawEntryMapping.java | 30 + .../mapping/serde/enigma/EnigmaFormat.java | 9 + .../mapping/serde/enigma/EnigmaMappingsReader.java | 322 ++++++ .../mapping/serde/enigma/EnigmaMappingsWriter.java | 318 ++++++ .../serde/proguard/ProguardMappingsReader.java | 135 +++ .../mapping/serde/srg/SrgMappingsWriter.java | 119 +++ .../mapping/serde/tiny/TinyMappingsReader.java | 116 +++ .../mapping/serde/tiny/TinyMappingsWriter.java | 149 +++ .../mapping/serde/tinyv2/TinyV2Reader.java | 296 ++++++ .../mapping/serde/tinyv2/TinyV2Writer.java | 172 ++++ .../mapping/tree/DeltaTrackingTree.java | 110 ++ .../enigma/translation/mapping/tree/EntryTree.java | 26 + .../translation/mapping/tree/EntryTreeNode.java | 40 + .../translation/mapping/tree/HashEntryTree.java | 188 ++++ .../translation/mapping/tree/HashTreeNode.java | 75 ++ .../translation/representation/AccessFlags.java | 116 +++ .../enigma/translation/representation/Lambda.java | 105 ++ .../representation/MethodDescriptor.java | 132 +++ .../translation/representation/Signature.java | 98 ++ .../translation/representation/TypeDescriptor.java | 268 +++++ .../representation/entry/ClassDefEntry.java | 93 ++ .../representation/entry/ClassEntry.java | 214 ++++ .../translation/representation/entry/DefEntry.java | 7 + .../translation/representation/entry/Entry.java | 107 ++ .../representation/entry/FieldDefEntry.java | 71 ++ .../representation/entry/FieldEntry.java | 96 ++ .../entry/LocalVariableDefEntry.java | 51 + .../representation/entry/LocalVariableEntry.java | 93 ++ .../representation/entry/MethodDefEntry.java | 71 ++ .../representation/entry/MethodEntry.java | 105 ++ .../representation/entry/ParentedEntry.java | 81 ++ enigma/src/main/java/cuchaz/enigma/utils/I18n.java | 95 ++ enigma/src/main/java/cuchaz/enigma/utils/Pair.java | 26 + .../src/main/java/cuchaz/enigma/utils/Utils.java | 92 ++ .../services/cuchaz.enigma.api.EnigmaPlugin | 1 + enigma/src/main/resources/lang/en_us.json | 164 +++ enigma/src/main/resources/lang/fr_fr.json | 164 +++ enigma/src/main/resources/lang/zh_cn.json | 118 +++ enigma/src/main/resources/profile.json | 20 + .../cuchaz/enigma/PackageVisibilityIndexTest.java | 53 + .../src/test/java/cuchaz/enigma/TestDeobfed.java | 106 ++ .../test/java/cuchaz/enigma/TestDeobfuscator.java | 41 + .../test/java/cuchaz/enigma/TestEntryFactory.java | 49 + .../test/java/cuchaz/enigma/TestInnerClasses.java | 85 ++ .../enigma/TestJarIndexConstructorReferences.java | 124 +++ .../cuchaz/enigma/TestJarIndexInheritanceTree.java | 227 +++++ .../java/cuchaz/enigma/TestJarIndexLoneClass.java | 157 +++ .../java/cuchaz/enigma/TestMethodDescriptor.java | 247 +++++ .../java/cuchaz/enigma/TestTokensConstructors.java | 137 +++ .../test/java/cuchaz/enigma/TestTranslator.java | 155 +++ .../java/cuchaz/enigma/TestTypeDescriptor.java | 243 +++++ .../src/test/java/cuchaz/enigma/TokenChecker.java | 65 ++ .../src/test/java/cuchaz/enigma/inputs/Keep.java | 18 + .../enigma/inputs/constructors/BaseClass.java | 26 + .../cuchaz/enigma/inputs/constructors/Caller.java | 58 ++ .../inputs/constructors/DefaultConstructable.java | 16 + .../enigma/inputs/constructors/SubClass.java | 39 + .../enigma/inputs/constructors/SubSubClass.java | 22 + .../enigma/inputs/inheritanceTree/BaseClass.java | 32 + .../enigma/inputs/inheritanceTree/SubclassA.java | 22 + .../enigma/inputs/inheritanceTree/SubclassB.java | 41 + .../inputs/inheritanceTree/SubsubclassAA.java | 35 + .../enigma/inputs/innerClasses/A_Anonymous.java | 25 + .../innerClasses/B_AnonymousWithScopeArgs.java | 24 + .../inputs/innerClasses/C_ConstructorArgs.java | 31 + .../enigma/inputs/innerClasses/D_Simple.java | 19 + .../innerClasses/E_AnonymousWithOuterAccess.java | 32 + .../enigma/inputs/innerClasses/F_ClassTree.java | 30 + .../cuchaz/enigma/inputs/loneClass/LoneClass.java | 25 + .../cuchaz/enigma/inputs/packageAccess/Base.java | 7 + .../inputs/packageAccess/SamePackageChild.java | 12 + .../packageAccess/sub/OtherPackageChild.java | 14 + .../cuchaz/enigma/inputs/translation/A_Basic.java | 33 + .../enigma/inputs/translation/B_BaseClass.java | 26 + .../enigma/inputs/translation/C_SubClass.java | 28 + .../inputs/translation/D_AnonymousTesting.java | 29 + .../enigma/inputs/translation/E_Bridges.java | 32 + .../enigma/inputs/translation/F_ObjectMethods.java | 31 + .../enigma/inputs/translation/G_OuterClass.java | 36 + .../enigma/inputs/translation/H_NamelessClass.java | 40 + .../enigma/inputs/translation/I_Generics.java | 35 + .../enigma/translation/mapping/TestComments.java | 39 + .../mapping/TestTinyV2InnerClasses.java | 37 + .../enigma/translation/mapping/TestV2Main.java | 23 + enigma/src/test/resources/comments/test.mapping | 18 + enigma/src/test/resources/proguard-build.conf | 6 + enigma/src/test/resources/proguard-test.conf | 8 + .../test/resources/tinyV2InnerClasses/c.mapping | 2 + .../tinyV2InnerClasses/cuchaz/enigma/Dad.mapping | 5 + enigma/src/test/resources/translation.mappings | 41 + settings.gradle | 5 +- src/main/java/cuchaz/enigma/ClassProvider.java | 10 - src/main/java/cuchaz/enigma/CommandMain.java | 105 -- src/main/java/cuchaz/enigma/Constants.java | 20 - src/main/java/cuchaz/enigma/Enigma.java | 121 --- src/main/java/cuchaz/enigma/EnigmaProfile.java | 131 --- src/main/java/cuchaz/enigma/EnigmaProject.java | 276 ----- src/main/java/cuchaz/enigma/EnigmaServices.java | 20 - src/main/java/cuchaz/enigma/ExceptionIgnorer.java | 35 - src/main/java/cuchaz/enigma/Main.java | 116 --- src/main/java/cuchaz/enigma/ProgressListener.java | 19 - .../java/cuchaz/enigma/ProposingTranslator.java | 53 - src/main/java/cuchaz/enigma/analysis/Access.java | 43 - .../java/cuchaz/enigma/analysis/BuiltinPlugin.java | 157 --- .../java/cuchaz/enigma/analysis/ClassCache.java | 127 --- .../analysis/ClassImplementationsTreeNode.java | 72 -- .../enigma/analysis/ClassInheritanceTreeNode.java | 72 -- .../enigma/analysis/ClassReferenceTreeNode.java | 94 -- .../cuchaz/enigma/analysis/EntryReference.java | 140 --- .../enigma/analysis/FieldReferenceTreeNode.java | 83 -- .../enigma/analysis/IndexSimpleVerifier.java | 154 --- .../cuchaz/enigma/analysis/IndexTreeBuilder.java | 74 -- .../cuchaz/enigma/analysis/InterpreterPair.java | 131 --- .../analysis/MethodImplementationsTreeNode.java | 85 -- .../enigma/analysis/MethodInheritanceTreeNode.java | 95 -- .../enigma/analysis/MethodNodeWithAction.java | 19 - .../enigma/analysis/MethodReferenceTreeNode.java | 113 --- .../enigma/analysis/ReferenceTargetType.java | 74 -- .../cuchaz/enigma/analysis/ReferenceTreeNode.java | 20 - src/main/java/cuchaz/enigma/analysis/Token.java | 72 -- .../enigma/analysis/index/BridgeMethodIndex.java | 156 --- .../cuchaz/enigma/analysis/index/EntryIndex.java | 102 -- .../enigma/analysis/index/IndexClassVisitor.java | 40 - .../analysis/index/IndexReferenceVisitor.java | 180 ---- .../enigma/analysis/index/InheritanceIndex.java | 127 --- .../cuchaz/enigma/analysis/index/JarIndex.java | 171 ---- .../cuchaz/enigma/analysis/index/JarIndexer.java | 28 - .../analysis/index/PackageVisibilityIndex.java | 147 --- .../enigma/analysis/index/ReferenceIndex.java | 148 --- src/main/java/cuchaz/enigma/api/EnigmaPlugin.java | 5 - .../cuchaz/enigma/api/EnigmaPluginContext.java | 9 - .../cuchaz/enigma/api/service/EnigmaService.java | 4 - .../enigma/api/service/EnigmaServiceContext.java | 11 - .../enigma/api/service/EnigmaServiceFactory.java | 5 - .../enigma/api/service/EnigmaServiceType.java | 29 - .../enigma/api/service/JarIndexerService.java | 10 - .../enigma/api/service/NameProposalService.java | 12 - .../enigma/api/service/ObfuscationTestService.java | 9 - .../bytecode/translators/AsmObjectTranslator.java | 46 - .../translators/LocalVariableFixVisitor.java | 126 --- .../bytecode/translators/SourceFixVisitor.java | 39 - .../translators/TranslationAnnotationVisitor.java | 51 - .../translators/TranslationClassVisitor.java | 102 -- .../translators/TranslationFieldVisitor.java | 33 - .../translators/TranslationMethodVisitor.java | 145 --- .../translators/TranslationSignatureVisitor.java | 129 --- .../enigma/command/CheckMappingsCommand.java | 77 -- src/main/java/cuchaz/enigma/command/Command.java | 154 --- .../enigma/command/ComposeMappingsCommand.java | 41 - .../enigma/command/ConvertMappingsCommand.java | 39 - .../cuchaz/enigma/command/DecompileCommand.java | 54 - .../cuchaz/enigma/command/DeobfuscateCommand.java | 37 - .../enigma/command/InvertMappingsCommand.java | 40 - .../command/MapSpecializedMethodsCommand.java | 69 -- .../cuchaz/enigma/command/MappingCommandsUtil.java | 148 --- src/main/java/cuchaz/enigma/config/Config.java | 261 ----- src/main/java/cuchaz/enigma/config/Themes.java | 48 - src/main/java/cuchaz/enigma/gui/BrowserCaret.java | 28 - src/main/java/cuchaz/enigma/gui/ClassSelector.java | 532 ---------- src/main/java/cuchaz/enigma/gui/CodeReader.java | 73 -- .../java/cuchaz/enigma/gui/ConnectionState.java | 7 - .../cuchaz/enigma/gui/DecompiledClassSource.java | 159 --- .../cuchaz/enigma/gui/EnigmaQuickFindDialog.java | 90 -- .../java/cuchaz/enigma/gui/EnigmaSyntaxKit.java | 44 - src/main/java/cuchaz/enigma/gui/Gui.java | 1058 -------------------- src/main/java/cuchaz/enigma/gui/GuiController.java | 729 -------------- .../cuchaz/enigma/gui/MessageListCellRenderer.java | 24 - .../cuchaz/enigma/gui/MethodTreeCellRenderer.java | 42 - .../java/cuchaz/enigma/gui/QuickFindAction.java | 45 - src/main/java/cuchaz/enigma/gui/RefreshMode.java | 7 - .../java/cuchaz/enigma/gui/SourceRemapper.java | 64 -- .../cuchaz/enigma/gui/TokenListCellRenderer.java | 35 - .../java/cuchaz/enigma/gui/dialog/AboutDialog.java | 69 -- .../cuchaz/enigma/gui/dialog/ChangeDialog.java | 50 - .../enigma/gui/dialog/ConnectToServerDialog.java | 82 -- .../java/cuchaz/enigma/gui/dialog/CrashDialog.java | 105 -- .../enigma/gui/dialog/CreateServerDialog.java | 65 -- .../cuchaz/enigma/gui/dialog/JavadocDialog.java | 159 --- .../cuchaz/enigma/gui/dialog/ProgressDialog.java | 109 -- .../cuchaz/enigma/gui/dialog/SearchDialog.java | 261 ----- .../java/cuchaz/enigma/gui/dialog/StatsDialog.java | 82 -- .../enigma/gui/elements/CollapsibleTabbedPane.java | 40 - .../java/cuchaz/enigma/gui/elements/MenuBar.java | 386 ------- .../cuchaz/enigma/gui/elements/PopupMenuBar.java | 125 --- .../enigma/gui/filechooser/FileChooserAny.java | 10 - .../enigma/gui/filechooser/FileChooserFile.java | 8 - .../enigma/gui/filechooser/FileChooserFolder.java | 11 - .../enigma/gui/highlight/BoxHighlightPainter.java | 69 -- .../gui/highlight/SelectionHighlightPainter.java | 31 - .../enigma/gui/highlight/TokenHighlightType.java | 7 - .../enigma/gui/node/ClassSelectorClassNode.java | 72 -- .../enigma/gui/node/ClassSelectorPackageNode.java | 58 -- .../java/cuchaz/enigma/gui/panels/PanelDeobf.java | 26 - .../java/cuchaz/enigma/gui/panels/PanelEditor.java | 171 ---- .../cuchaz/enigma/gui/panels/PanelIdentifier.java | 32 - .../java/cuchaz/enigma/gui/panels/PanelObf.java | 37 - .../cuchaz/enigma/gui/stats/StatsGenerator.java | 197 ---- .../java/cuchaz/enigma/gui/stats/StatsMember.java | 8 - .../enigma/gui/util/AbstractListCellRenderer.java | 77 -- src/main/java/cuchaz/enigma/gui/util/History.java | 49 - .../enigma/gui/util/ScaleChangeListener.java | 8 - .../java/cuchaz/enigma/gui/util/ScaleUtil.java | 110 -- .../enigma/network/DedicatedEnigmaServer.java | 164 --- .../java/cuchaz/enigma/network/EnigmaClient.java | 85 -- .../java/cuchaz/enigma/network/EnigmaServer.java | 292 ------ .../enigma/network/IntegratedEnigmaServer.java | 16 - .../cuchaz/enigma/network/ServerPacketHandler.java | 22 - .../enigma/network/packet/ChangeDocsC2SPacket.java | 59 -- .../enigma/network/packet/ChangeDocsS2CPacket.java | 44 - .../network/packet/ConfirmChangeC2SPacket.java | 33 - .../enigma/network/packet/KickS2CPacket.java | 33 - .../enigma/network/packet/LoginC2SPacket.java | 75 -- .../network/packet/MarkDeobfuscatedC2SPacket.java | 48 - .../network/packet/MarkDeobfuscatedS2CPacket.java | 40 - .../enigma/network/packet/MessageC2SPacket.java | 39 - .../enigma/network/packet/MessageS2CPacket.java | 36 - .../java/cuchaz/enigma/network/packet/Packet.java | 15 - .../cuchaz/enigma/network/packet/PacketHelper.java | 135 --- .../enigma/network/packet/PacketRegistry.java | 64 -- .../network/packet/RemoveMappingC2SPacket.java | 55 - .../network/packet/RemoveMappingS2CPacket.java | 40 - .../enigma/network/packet/RenameC2SPacket.java | 64 -- .../enigma/network/packet/RenameS2CPacket.java | 48 - .../network/packet/SyncMappingsS2CPacket.java | 88 -- .../enigma/network/packet/UserListS2CPacket.java | 44 - src/main/java/cuchaz/enigma/source/Decompiler.java | 5 - .../cuchaz/enigma/source/DecompilerService.java | 11 - .../java/cuchaz/enigma/source/Decompilers.java | 9 - src/main/java/cuchaz/enigma/source/Source.java | 11 - .../java/cuchaz/enigma/source/SourceIndex.java | 174 ---- .../java/cuchaz/enigma/source/SourceSettings.java | 11 - .../cuchaz/enigma/source/cfr/CfrDecompiler.java | 108 -- .../java/cuchaz/enigma/source/cfr/CfrSource.java | 38 - .../cuchaz/enigma/source/cfr/EnigmaDumper.java | 433 -------- .../cuchaz/enigma/source/procyon/EntryParser.java | 49 - .../enigma/source/procyon/ProcyonDecompiler.java | 81 -- .../enigma/source/procyon/ProcyonSource.java | 49 - .../procyon/index/SourceIndexClassVisitor.java | 95 -- .../procyon/index/SourceIndexMethodVisitor.java | 218 ---- .../source/procyon/index/SourceIndexVisitor.java | 40 - .../enigma/source/procyon/index/TokenFactory.java | 46 - .../transformers/AddJavadocsAstTransform.java | 134 --- .../transformers/DropImportAstTransform.java | 33 - .../transformers/DropVarModifiersAstTransform.java | 37 - .../procyon/transformers/InvalidIdentifierFix.java | 29 - .../source/procyon/transformers/Java8Generics.java | 107 -- .../ObfuscatedEnumSwitchRewriterTransform.java | 414 -------- .../procyon/transformers/RemoveObjectCasts.java | 39 - .../source/procyon/transformers/VarargsFixer.java | 197 ---- .../typeloader/CachingClasspathTypeLoader.java | 33 - .../procyon/typeloader/CachingTypeLoader.java | 38 - .../typeloader/CompiledSourceTypeLoader.java | 140 --- .../procyon/typeloader/NoRetryMetadataSystem.java | 38 - .../procyon/typeloader/SynchronizedTypeLoader.java | 20 - .../enigma/throwables/IllegalNameException.java | 39 - .../cuchaz/enigma/throwables/MappingConflict.java | 7 - .../enigma/throwables/MappingParseException.java | 39 - .../enigma/translation/LocalNameGenerator.java | 44 - .../enigma/translation/MappingTranslator.java | 24 - .../enigma/translation/SignatureUpdater.java | 92 -- .../cuchaz/enigma/translation/Translatable.java | 9 - .../enigma/translation/TranslationDirection.java | 36 - .../java/cuchaz/enigma/translation/Translator.java | 61 -- .../cuchaz/enigma/translation/VoidTranslator.java | 10 - .../enigma/translation/mapping/AccessModifier.java | 25 - .../enigma/translation/mapping/EntryMap.java | 24 - .../enigma/translation/mapping/EntryMapping.java | 75 -- .../enigma/translation/mapping/EntryRemapper.java | 105 -- .../enigma/translation/mapping/EntryResolver.java | 41 - .../translation/mapping/IndexEntryResolver.java | 227 ----- .../enigma/translation/mapping/MappingDelta.java | 54 - .../translation/mapping/MappingFileNameFormat.java | 10 - .../enigma/translation/mapping/MappingPair.java | 32 - .../translation/mapping/MappingSaveParameters.java | 16 - .../translation/mapping/MappingValidator.java | 76 -- .../translation/mapping/MappingsChecker.java | 99 -- .../enigma/translation/mapping/NameValidator.java | 53 - .../translation/mapping/ResolutionStrategy.java | 6 - .../translation/mapping/VoidEntryResolver.java | 27 - .../translation/mapping/serde/EnigmaFormat.java | 9 - .../mapping/serde/EnigmaMappingsReader.java | 319 ------ .../mapping/serde/EnigmaMappingsWriter.java | 316 ------ .../translation/mapping/serde/MappingFormat.java | 59 -- .../translation/mapping/serde/MappingHelper.java | 51 - .../translation/mapping/serde/MappingsReader.java | 14 - .../translation/mapping/serde/MappingsWriter.java | 17 - .../mapping/serde/ProguardMappingsReader.java | 134 --- .../translation/mapping/serde/RawEntryMapping.java | 30 - .../mapping/serde/SrgMappingsWriter.java | 118 --- .../mapping/serde/TinyMappingsReader.java | 115 --- .../mapping/serde/TinyMappingsWriter.java | 148 --- .../translation/mapping/serde/TinyV2Reader.java | 295 ------ .../translation/mapping/serde/TinyV2Writer.java | 169 ---- .../mapping/tree/DeltaTrackingTree.java | 110 -- .../enigma/translation/mapping/tree/EntryTree.java | 26 - .../translation/mapping/tree/EntryTreeNode.java | 40 - .../translation/mapping/tree/HashEntryTree.java | 188 ---- .../translation/mapping/tree/HashTreeNode.java | 75 -- .../translation/representation/AccessFlags.java | 116 --- .../enigma/translation/representation/Lambda.java | 105 -- .../representation/MethodDescriptor.java | 132 --- .../translation/representation/Signature.java | 98 -- .../translation/representation/TypeDescriptor.java | 268 ----- .../representation/entry/ClassDefEntry.java | 93 -- .../representation/entry/ClassEntry.java | 214 ---- .../translation/representation/entry/DefEntry.java | 7 - .../translation/representation/entry/Entry.java | 107 -- .../representation/entry/FieldDefEntry.java | 71 -- .../representation/entry/FieldEntry.java | 96 -- .../entry/LocalVariableDefEntry.java | 51 - .../representation/entry/LocalVariableEntry.java | 93 -- .../representation/entry/MethodDefEntry.java | 71 -- .../representation/entry/MethodEntry.java | 105 -- .../representation/entry/ParentedEntry.java | 82 -- src/main/java/cuchaz/enigma/utils/I18n.java | 102 -- .../java/cuchaz/enigma/utils/LFPrintWriter.java | 16 - src/main/java/cuchaz/enigma/utils/Message.java | 392 -------- src/main/java/cuchaz/enigma/utils/Pair.java | 26 - .../java/cuchaz/enigma/utils/ReadableToken.java | 30 - src/main/java/cuchaz/enigma/utils/Utils.java | 179 ---- .../cuchaz/enigma/utils/search/SearchEntry.java | 17 - .../cuchaz/enigma/utils/search/SearchUtil.java | 268 ----- .../services/cuchaz.enigma.api.EnigmaPlugin | 1 - src/main/resources/about.html | 6 - src/main/resources/lang/en_us.json | 164 --- src/main/resources/lang/fr_fr.json | 164 --- src/main/resources/lang/zh_cn.json | 118 --- src/main/resources/profile.json | 20 - src/main/resources/stats.html | 34 - .../cuchaz/enigma/PackageVisibilityIndexTest.java | 53 - src/test/java/cuchaz/enigma/TestDeobfed.java | 100 -- src/test/java/cuchaz/enigma/TestDeobfuscator.java | 41 - src/test/java/cuchaz/enigma/TestEntryFactory.java | 49 - src/test/java/cuchaz/enigma/TestInnerClasses.java | 85 -- .../enigma/TestJarIndexConstructorReferences.java | 124 --- .../cuchaz/enigma/TestJarIndexInheritanceTree.java | 227 ----- .../java/cuchaz/enigma/TestJarIndexLoneClass.java | 157 --- .../java/cuchaz/enigma/TestMethodDescriptor.java | 247 ----- src/test/java/cuchaz/enigma/TestSourceIndex.java | 74 -- .../java/cuchaz/enigma/TestTokensConstructors.java | 137 --- src/test/java/cuchaz/enigma/TestTranslator.java | 155 --- .../java/cuchaz/enigma/TestTypeDescriptor.java | 243 ----- src/test/java/cuchaz/enigma/TokenChecker.java | 65 -- .../enigma/command/CheckMappingsCommandTest.java | 20 - src/test/java/cuchaz/enigma/inputs/Keep.java | 18 - .../enigma/inputs/constructors/BaseClass.java | 26 - .../cuchaz/enigma/inputs/constructors/Caller.java | 58 -- .../inputs/constructors/DefaultConstructable.java | 16 - .../enigma/inputs/constructors/SubClass.java | 39 - .../enigma/inputs/constructors/SubSubClass.java | 22 - .../enigma/inputs/inheritanceTree/BaseClass.java | 32 - .../enigma/inputs/inheritanceTree/SubclassA.java | 22 - .../enigma/inputs/inheritanceTree/SubclassB.java | 41 - .../inputs/inheritanceTree/SubsubclassAA.java | 35 - .../enigma/inputs/innerClasses/A_Anonymous.java | 25 - .../innerClasses/B_AnonymousWithScopeArgs.java | 24 - .../inputs/innerClasses/C_ConstructorArgs.java | 31 - .../enigma/inputs/innerClasses/D_Simple.java | 19 - .../innerClasses/E_AnonymousWithOuterAccess.java | 32 - .../enigma/inputs/innerClasses/F_ClassTree.java | 30 - .../cuchaz/enigma/inputs/loneClass/LoneClass.java | 25 - .../cuchaz/enigma/inputs/packageAccess/Base.java | 7 - .../inputs/packageAccess/SamePackageChild.java | 12 - .../packageAccess/sub/OtherPackageChild.java | 14 - .../cuchaz/enigma/inputs/translation/A_Basic.java | 33 - .../enigma/inputs/translation/B_BaseClass.java | 26 - .../enigma/inputs/translation/C_SubClass.java | 28 - .../inputs/translation/D_AnonymousTesting.java | 29 - .../enigma/inputs/translation/E_Bridges.java | 32 - .../enigma/inputs/translation/F_ObjectMethods.java | 31 - .../enigma/inputs/translation/G_OuterClass.java | 36 - .../enigma/inputs/translation/H_NamelessClass.java | 40 - .../enigma/inputs/translation/I_Generics.java | 35 - .../java/cuchaz/enigma/mapping/TestComments.java | 40 - .../enigma/mapping/TestTinyV2InnerClasses.java | 41 - .../java/cuchaz/enigma/mapping/TestV2Main.java | 24 - .../cuchaz/enigma/resources/translation.mappings | 41 - src/test/resources/comments/test.mapping | 18 - .../correctMappings/base/Base.mapping | 1 - .../packageAccess/correctMappings/base/One.mapping | 1 - .../packageAccess/correctMappings/two/Two.mapping | 1 - .../packageAccess/wrongMappings/base/Base.mapping | 1 - .../packageAccess/wrongMappings/one/One.mapping | 1 - .../packageAccess/wrongMappings/two/Two.mapping | 1 - src/test/resources/proguard-build.conf | 6 - src/test/resources/proguard-test.conf | 8 - src/test/resources/tinyV2InnerClasses/c.mapping | 2 - .../tinyV2InnerClasses/cuchaz/enigma/Dad.mapping | 5 - 604 files changed, 24971 insertions(+), 25216 deletions(-) delete mode 100644 docs/protocol.md create mode 100644 enigma-cli/build.gradle create mode 100644 enigma-cli/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java create mode 100644 enigma-cli/src/main/java/cuchaz/enigma/command/Command.java create mode 100644 enigma-cli/src/main/java/cuchaz/enigma/command/ComposeMappingsCommand.java create mode 100644 enigma-cli/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java create mode 100644 enigma-cli/src/main/java/cuchaz/enigma/command/DecompileCommand.java create mode 100644 enigma-cli/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java create mode 100644 enigma-cli/src/main/java/cuchaz/enigma/command/InvertMappingsCommand.java create mode 100644 enigma-cli/src/main/java/cuchaz/enigma/command/Main.java create mode 100644 enigma-cli/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java create mode 100644 enigma-cli/src/main/java/cuchaz/enigma/command/MappingCommandsUtil.java create mode 100644 enigma-cli/src/test/java/cuchaz/enigma/command/CheckMappingsCommandTest.java create mode 100644 enigma-cli/src/test/resources/packageAccess/correctMappings/base/Base.mapping create mode 100644 enigma-cli/src/test/resources/packageAccess/correctMappings/base/One.mapping create mode 100644 enigma-cli/src/test/resources/packageAccess/correctMappings/two/Two.mapping create mode 100644 enigma-cli/src/test/resources/packageAccess/wrongMappings/base/Base.mapping create mode 100644 enigma-cli/src/test/resources/packageAccess/wrongMappings/one/One.mapping create mode 100644 enigma-cli/src/test/resources/packageAccess/wrongMappings/two/Two.mapping create mode 100644 enigma-server/build.gradle create mode 100644 enigma-server/docs/protocol.md create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/EnigmaClient.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/Message.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java create 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/ConfirmChangeC2SPacket.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/Packet.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java create mode 100644 enigma-server/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java create mode 100644 enigma-swing/build.gradle create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/BrowserCaret.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/CodeReader.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/ConnectionState.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/EnigmaQuickFindDialog.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/ExceptionIgnorer.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/QuickFindAction.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/ReadableToken.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/RefreshMode.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/config/Config.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/config/Themes.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/search/SearchEntry.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/search/SearchUtil.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/stats/StatsMember.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/util/AbstractListCellRenderer.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/util/History.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/util/ScaleChangeListener.java create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java create mode 100644 enigma-swing/src/main/resources/about.html create mode 100644 enigma-swing/src/main/resources/stats.html create mode 100644 enigma/build.gradle create mode 100644 enigma/src/main/java/cuchaz/enigma/ClassProvider.java create mode 100644 enigma/src/main/java/cuchaz/enigma/Enigma.java create mode 100644 enigma/src/main/java/cuchaz/enigma/EnigmaProfile.java create mode 100644 enigma/src/main/java/cuchaz/enigma/EnigmaProject.java create mode 100644 enigma/src/main/java/cuchaz/enigma/EnigmaServices.java create mode 100644 enigma/src/main/java/cuchaz/enigma/ProgressListener.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/Access.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/ClassCache.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/EntryReference.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/ReferenceTargetType.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java create mode 100644 enigma/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java create mode 100644 enigma/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java create mode 100644 enigma/src/main/java/cuchaz/enigma/api/service/EnigmaService.java create mode 100644 enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java create mode 100644 enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java create mode 100644 enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java create mode 100644 enigma/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java create mode 100644 enigma/src/main/java/cuchaz/enigma/api/service/NameProposalService.java create mode 100644 enigma/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java create mode 100644 enigma/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java create mode 100644 enigma/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java create mode 100644 enigma/src/main/java/cuchaz/enigma/bytecode/translators/SourceFixVisitor.java create mode 100644 enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java create mode 100644 enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java create mode 100644 enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java create mode 100644 enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java create mode 100644 enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/Decompiler.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/DecompilerService.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/Decompilers.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/Source.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/SourceIndex.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/SourceRemapper.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/SourceSettings.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/Token.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java create mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/MappingTranslator.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/ProposingTranslator.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/Translatable.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/TranslationDirection.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/Translator.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/VoidTranslator.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/IllegalNameException.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingOperations.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/LfPrintWriter.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFileNameFormat.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingParseException.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingSaveParameters.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaFormat.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsReader.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsWriter.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/proguard/ProguardMappingsReader.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/srg/SrgMappingsWriter.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsReader.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsWriter.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Reader.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Writer.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/representation/Lambda.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/representation/Signature.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java create mode 100644 enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java create mode 100644 enigma/src/main/java/cuchaz/enigma/utils/I18n.java create mode 100644 enigma/src/main/java/cuchaz/enigma/utils/Pair.java create mode 100644 enigma/src/main/java/cuchaz/enigma/utils/Utils.java create mode 100644 enigma/src/main/resources/META-INF/services/cuchaz.enigma.api.EnigmaPlugin create mode 100644 enigma/src/main/resources/lang/en_us.json create mode 100644 enigma/src/main/resources/lang/fr_fr.json create mode 100644 enigma/src/main/resources/lang/zh_cn.json create mode 100644 enigma/src/main/resources/profile.json create mode 100644 enigma/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java create mode 100644 enigma/src/test/java/cuchaz/enigma/TestDeobfed.java create mode 100644 enigma/src/test/java/cuchaz/enigma/TestDeobfuscator.java create mode 100644 enigma/src/test/java/cuchaz/enigma/TestEntryFactory.java create mode 100644 enigma/src/test/java/cuchaz/enigma/TestInnerClasses.java create mode 100644 enigma/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java create mode 100644 enigma/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java create mode 100644 enigma/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java create mode 100644 enigma/src/test/java/cuchaz/enigma/TestMethodDescriptor.java create mode 100644 enigma/src/test/java/cuchaz/enigma/TestTokensConstructors.java create mode 100644 enigma/src/test/java/cuchaz/enigma/TestTranslator.java create mode 100644 enigma/src/test/java/cuchaz/enigma/TestTypeDescriptor.java create mode 100644 enigma/src/test/java/cuchaz/enigma/TokenChecker.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/Keep.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/constructors/Caller.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/loneClass/LoneClass.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/Base.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/SamePackageChild.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/sub/OtherPackageChild.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/translation/B_BaseClass.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/translation/G_OuterClass.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java create mode 100644 enigma/src/test/java/cuchaz/enigma/inputs/translation/I_Generics.java create mode 100644 enigma/src/test/java/cuchaz/enigma/translation/mapping/TestComments.java create mode 100644 enigma/src/test/java/cuchaz/enigma/translation/mapping/TestTinyV2InnerClasses.java create mode 100644 enigma/src/test/java/cuchaz/enigma/translation/mapping/TestV2Main.java create mode 100644 enigma/src/test/resources/comments/test.mapping create mode 100644 enigma/src/test/resources/proguard-build.conf create mode 100644 enigma/src/test/resources/proguard-test.conf create mode 100644 enigma/src/test/resources/tinyV2InnerClasses/c.mapping create mode 100644 enigma/src/test/resources/tinyV2InnerClasses/cuchaz/enigma/Dad.mapping create mode 100644 enigma/src/test/resources/translation.mappings delete mode 100644 src/main/java/cuchaz/enigma/ClassProvider.java delete mode 100644 src/main/java/cuchaz/enigma/CommandMain.java delete mode 100644 src/main/java/cuchaz/enigma/Constants.java delete mode 100644 src/main/java/cuchaz/enigma/Enigma.java delete mode 100644 src/main/java/cuchaz/enigma/EnigmaProfile.java delete mode 100644 src/main/java/cuchaz/enigma/EnigmaProject.java delete mode 100644 src/main/java/cuchaz/enigma/EnigmaServices.java delete mode 100644 src/main/java/cuchaz/enigma/ExceptionIgnorer.java delete mode 100644 src/main/java/cuchaz/enigma/Main.java delete mode 100644 src/main/java/cuchaz/enigma/ProgressListener.java delete mode 100644 src/main/java/cuchaz/enigma/ProposingTranslator.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/Access.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/ClassCache.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/EntryReference.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/InterpreterPair.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/ReferenceTargetType.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/Token.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/index/JarIndex.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java delete mode 100644 src/main/java/cuchaz/enigma/api/EnigmaPlugin.java delete mode 100644 src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java delete mode 100644 src/main/java/cuchaz/enigma/api/service/EnigmaService.java delete mode 100644 src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java delete mode 100644 src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java delete mode 100644 src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java delete mode 100644 src/main/java/cuchaz/enigma/api/service/JarIndexerService.java delete mode 100644 src/main/java/cuchaz/enigma/api/service/NameProposalService.java delete mode 100644 src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/SourceFixVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java delete mode 100644 src/main/java/cuchaz/enigma/command/Command.java delete mode 100644 src/main/java/cuchaz/enigma/command/ComposeMappingsCommand.java delete mode 100644 src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java delete mode 100644 src/main/java/cuchaz/enigma/command/DecompileCommand.java delete mode 100644 src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java delete mode 100644 src/main/java/cuchaz/enigma/command/InvertMappingsCommand.java delete mode 100644 src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java delete mode 100644 src/main/java/cuchaz/enigma/command/MappingCommandsUtil.java delete mode 100644 src/main/java/cuchaz/enigma/config/Config.java delete mode 100644 src/main/java/cuchaz/enigma/config/Themes.java delete mode 100644 src/main/java/cuchaz/enigma/gui/BrowserCaret.java delete mode 100644 src/main/java/cuchaz/enigma/gui/ClassSelector.java delete mode 100644 src/main/java/cuchaz/enigma/gui/CodeReader.java delete mode 100644 src/main/java/cuchaz/enigma/gui/ConnectionState.java delete mode 100644 src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java delete mode 100644 src/main/java/cuchaz/enigma/gui/EnigmaQuickFindDialog.java delete mode 100644 src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java delete mode 100644 src/main/java/cuchaz/enigma/gui/Gui.java delete mode 100644 src/main/java/cuchaz/enigma/gui/GuiController.java delete mode 100644 src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java delete mode 100644 src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java delete mode 100644 src/main/java/cuchaz/enigma/gui/QuickFindAction.java delete mode 100644 src/main/java/cuchaz/enigma/gui/RefreshMode.java delete mode 100644 src/main/java/cuchaz/enigma/gui/SourceRemapper.java delete mode 100644 src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java delete mode 100644 src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java delete mode 100644 src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java delete mode 100644 src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java delete mode 100644 src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java delete mode 100644 src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java delete mode 100644 src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java delete mode 100644 src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java delete mode 100644 src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java delete mode 100644 src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java delete mode 100644 src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java delete mode 100644 src/main/java/cuchaz/enigma/gui/elements/MenuBar.java delete mode 100644 src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java delete mode 100644 src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java delete mode 100644 src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java delete mode 100644 src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java delete mode 100644 src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java delete mode 100644 src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java delete mode 100644 src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java delete mode 100644 src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java delete mode 100644 src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java delete mode 100644 src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java delete mode 100644 src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java delete mode 100644 src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java delete mode 100644 src/main/java/cuchaz/enigma/gui/panels/PanelObf.java delete mode 100644 src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java delete mode 100644 src/main/java/cuchaz/enigma/gui/stats/StatsMember.java delete mode 100644 src/main/java/cuchaz/enigma/gui/util/AbstractListCellRenderer.java delete mode 100644 src/main/java/cuchaz/enigma/gui/util/History.java delete mode 100644 src/main/java/cuchaz/enigma/gui/util/ScaleChangeListener.java delete mode 100644 src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java delete mode 100644 src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java delete mode 100644 src/main/java/cuchaz/enigma/network/EnigmaClient.java delete mode 100644 src/main/java/cuchaz/enigma/network/EnigmaServer.java delete mode 100644 src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java delete mode 100644 src/main/java/cuchaz/enigma/network/ServerPacketHandler.java delete mode 100644 src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java delete mode 100644 src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java delete mode 100644 src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java delete mode 100644 src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java delete mode 100644 src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java delete mode 100644 src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java delete mode 100644 src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java delete mode 100644 src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java delete mode 100644 src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java delete mode 100644 src/main/java/cuchaz/enigma/network/packet/Packet.java delete mode 100644 src/main/java/cuchaz/enigma/network/packet/PacketHelper.java delete mode 100644 src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java delete mode 100644 src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java delete mode 100644 src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java delete mode 100644 src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java delete mode 100644 src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java delete mode 100644 src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java delete mode 100644 src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java delete mode 100644 src/main/java/cuchaz/enigma/source/Decompiler.java delete mode 100644 src/main/java/cuchaz/enigma/source/DecompilerService.java delete mode 100644 src/main/java/cuchaz/enigma/source/Decompilers.java delete mode 100644 src/main/java/cuchaz/enigma/source/Source.java delete mode 100644 src/main/java/cuchaz/enigma/source/SourceIndex.java delete mode 100644 src/main/java/cuchaz/enigma/source/SourceSettings.java delete mode 100644 src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java delete mode 100644 src/main/java/cuchaz/enigma/source/cfr/CfrSource.java delete mode 100644 src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/EntryParser.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java delete mode 100644 src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java delete mode 100644 src/main/java/cuchaz/enigma/throwables/IllegalNameException.java delete mode 100644 src/main/java/cuchaz/enigma/throwables/MappingConflict.java delete mode 100644 src/main/java/cuchaz/enigma/throwables/MappingParseException.java delete mode 100644 src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java delete mode 100644 src/main/java/cuchaz/enigma/translation/MappingTranslator.java delete mode 100644 src/main/java/cuchaz/enigma/translation/SignatureUpdater.java delete mode 100644 src/main/java/cuchaz/enigma/translation/Translatable.java delete mode 100644 src/main/java/cuchaz/enigma/translation/TranslationDirection.java delete mode 100644 src/main/java/cuchaz/enigma/translation/Translator.java delete mode 100644 src/main/java/cuchaz/enigma/translation/VoidTranslator.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/MappingFileNameFormat.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/MappingSaveParameters.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaFormat.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/ProguardMappingsReader.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsWriter.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Reader.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Writer.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java delete mode 100644 src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java delete mode 100644 src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java delete mode 100644 src/main/java/cuchaz/enigma/translation/representation/Lambda.java delete mode 100644 src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java delete mode 100644 src/main/java/cuchaz/enigma/translation/representation/Signature.java delete mode 100644 src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java delete mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java delete mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java delete mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java delete mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java delete mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java delete mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java delete mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java delete mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java delete mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java delete mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java delete mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java delete mode 100644 src/main/java/cuchaz/enigma/utils/I18n.java delete mode 100644 src/main/java/cuchaz/enigma/utils/LFPrintWriter.java delete mode 100644 src/main/java/cuchaz/enigma/utils/Message.java delete mode 100644 src/main/java/cuchaz/enigma/utils/Pair.java delete mode 100644 src/main/java/cuchaz/enigma/utils/ReadableToken.java delete mode 100644 src/main/java/cuchaz/enigma/utils/Utils.java delete mode 100644 src/main/java/cuchaz/enigma/utils/search/SearchEntry.java delete mode 100644 src/main/java/cuchaz/enigma/utils/search/SearchUtil.java delete mode 100644 src/main/resources/META-INF/services/cuchaz.enigma.api.EnigmaPlugin delete mode 100644 src/main/resources/about.html delete mode 100644 src/main/resources/lang/en_us.json delete mode 100644 src/main/resources/lang/fr_fr.json delete mode 100644 src/main/resources/lang/zh_cn.json delete mode 100644 src/main/resources/profile.json delete mode 100644 src/main/resources/stats.html delete mode 100644 src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java delete mode 100644 src/test/java/cuchaz/enigma/TestDeobfed.java delete mode 100644 src/test/java/cuchaz/enigma/TestDeobfuscator.java delete mode 100644 src/test/java/cuchaz/enigma/TestEntryFactory.java delete mode 100644 src/test/java/cuchaz/enigma/TestInnerClasses.java delete mode 100644 src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java delete mode 100644 src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java delete mode 100644 src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java delete mode 100644 src/test/java/cuchaz/enigma/TestMethodDescriptor.java delete mode 100644 src/test/java/cuchaz/enigma/TestSourceIndex.java delete mode 100644 src/test/java/cuchaz/enigma/TestTokensConstructors.java delete mode 100644 src/test/java/cuchaz/enigma/TestTranslator.java delete mode 100644 src/test/java/cuchaz/enigma/TestTypeDescriptor.java delete mode 100644 src/test/java/cuchaz/enigma/TokenChecker.java delete mode 100644 src/test/java/cuchaz/enigma/command/CheckMappingsCommandTest.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/Keep.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/constructors/Caller.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/loneClass/LoneClass.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/packageAccess/Base.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/packageAccess/SamePackageChild.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/packageAccess/sub/OtherPackageChild.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/translation/B_BaseClass.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/translation/G_OuterClass.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java delete mode 100644 src/test/java/cuchaz/enigma/inputs/translation/I_Generics.java delete mode 100644 src/test/java/cuchaz/enigma/mapping/TestComments.java delete mode 100644 src/test/java/cuchaz/enigma/mapping/TestTinyV2InnerClasses.java delete mode 100644 src/test/java/cuchaz/enigma/mapping/TestV2Main.java delete mode 100644 src/test/java/cuchaz/enigma/resources/translation.mappings delete mode 100644 src/test/resources/comments/test.mapping delete mode 100644 src/test/resources/packageAccess/correctMappings/base/Base.mapping delete mode 100644 src/test/resources/packageAccess/correctMappings/base/One.mapping delete mode 100644 src/test/resources/packageAccess/correctMappings/two/Two.mapping delete mode 100644 src/test/resources/packageAccess/wrongMappings/base/Base.mapping delete mode 100644 src/test/resources/packageAccess/wrongMappings/one/One.mapping delete mode 100644 src/test/resources/packageAccess/wrongMappings/two/Two.mapping delete mode 100644 src/test/resources/proguard-build.conf delete mode 100644 src/test/resources/proguard-test.conf delete mode 100644 src/test/resources/tinyV2InnerClasses/c.mapping delete mode 100644 src/test/resources/tinyV2InnerClasses/cuchaz/enigma/Dad.mapping diff --git a/.gitignore b/.gitignore index 662e408b..89623a31 100644 --- a/.gitignore +++ b/.gitignore @@ -1,231 +1,35 @@ -### Windows ### -# Windows image file caches -Thumbs.db -ehthumbs.db - -# Folder config file -Desktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msm -*.msp - -# Windows shortcuts -*.lnk - - -### OSX ### -*.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - - -### Linux ### -*~ - -# temporary files which can be created if a process still has a handle open of a deleted file -.fuse_hidden* - -# KDE directory preferences -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - - -### Vim ### -# swap -[._]*.s[a-w][a-z] -[._]s[a-w][a-z] -# session -Session.vim -# temporary -.netrwhist -*~ -# auto-generated tag files -tags - - -### Emacs ### -# -*- mode: gitignore; -*- -*~ -\#*\# -/.emacs.desktop -/.emacs.desktop.lock -*.elc -auto-save-list -tramp -.\#* - -# Org-mode -.org-id-locations -*_archive - -# flymake-mode -*_flymake.* - -# eshell files -/eshell/history -/eshell/lastdir - -# elpa packages -/elpa/ - -# reftex files -*.rel - -# AUCTeX auto folder -/auto/ - -# cask packages -.cask/ -dist/ - -# Flycheck -flycheck_*.el - -# server auth directory -/server/ - -# projectiles files -.projectile - -### Intellij ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff: -.idea/workspace.xml -.idea/tasks.xml -.idea/dictionaries -.idea/vcs.xml -.idea/jsLibraryMappings.xml - -# Sensitive or high-churn files: -.idea/dataSources.ids -.idea/dataSources.xml -.idea/dataSources.local.xml -.idea/sqlDataSources.xml -.idea/dynamic.xml -.idea/uiDesigner.xml - -# Gradle: -.idea/gradle.xml -.idea/libraries - -## File-based project format: -*.iws -*.iml -*.ipr - -## Plugin-specific files: - -# IntelliJ -/out/ -.idea - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -### Eclipse ### +# Gradle +.gradle +# Eclipse +.checkstyle +.classpath .metadata -bin/ -tmp/ -*.tmp -*.bak -*.swp -*~.nib -local.properties -.settings/ -.loadpath -.recommenders - -# Eclipse Core +.settings .project - -# External tool builders -.externalToolBuilders/ - -# Locally stored "Eclipse launch configurations" *.launch -# CDT-specific (C/C++ Development Tooling) -.cproject - -# JDT-specific (Eclipse Java Development Tools) -.classpath - -# Java annotation processor (APT) +# Intellij/Idea .factorypath +.idea +*.iml +*.ipr +*.iws -# sbteclipse plugin -.target - -# Tern plugin -.tern-project - -# TeXlipse plugin -.texlipse - -# STS (Spring Tool Suite) -.springBeans - -# Code Recommenders -.recommenders/ - - -### Java ### -*.class - -# Package Files # -*.jar -*.war -*.ear - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - -### Gradle ### -.gradle -/build/ +# Build artifacts +bin/ +build/ +jars/ +out/ +classes/ -# Ignore Gradle GUI config -gradle-app.setting +# Debug artifacts +run +*.log -# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) -!gradle-wrapper.jar +# Windows +*.db +$RECYCLE.BIN/ -# Cache of project -.gradletasknamecache \ No newline at end of file +# Mac +.DS_Store diff --git a/README.md b/README.md index 7d01dacf..fac036f4 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Enigma includes the following open-source libraries: ## Usage -Pre-compiled jars can be found on the [fabric maven](https://maven.fabricmc.net/cuchaz/enigma/). +Pre-compiled jars can be found on the [fabric maven](https://maven.fabricmc.net/cuchaz/enigma-swing/). ### Launching the GUI @@ -25,4 +25,4 @@ Pre-compiled jars can be found on the [fabric maven](https://maven.fabricmc.net/ ### On the command line -`java -cp enigma.jar cuchaz.enigma.CommandMain` +`java -cp enigma.jar cuchaz.enigma.command.Main` diff --git a/build.gradle b/build.gradle index ba2d86c4..bd2d99f4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,165 +1,59 @@ plugins { - id 'java' - id 'com.github.johnrengelman.shadow' version '5.2.0' id 'maven-publish' } -group = 'cuchaz' -version = '0.16.1' +subprojects { + apply plugin: 'java' + apply plugin: 'maven-publish' -def generatedSourcesDir = "$buildDir/generated-src" + sourceCompatibility = 1.8 + targetCompatibility = 1.8 -def buildNumber = System.getenv("BUILD_NUMBER") -version = version + "+" + (buildNumber ? "build.$buildNumber" : "local") - -sourceCompatibility = 1.8 -targetCompatibility = 1.8 - -task generateSources(type: Copy) { - from sourceSets.main.java - into generatedSourcesDir - - filter { String line -> - ("$line".replaceAll('@VERSION@', version)) + repositories { + mavenLocal() + mavenCentral() + maven { url 'https://maven.fabricmc.net/' } } -} -compileJava.source = generatedSourcesDir -compileJava.dependsOn generateSources + dependencies { + implementation 'com.google.guava:guava:28.0-jre' + implementation 'com.google.code.gson:gson:2.8.5' -repositories { - mavenLocal() - mavenCentral() - - maven { - name "Modmuss Repository" - url 'https://maven.modmuss50.me/' + testImplementation 'junit:junit:4.+' + testImplementation 'org.hamcrest:hamcrest-all:1.+' } -} - -configurations { - proGuard -} - -dependencies { - implementation 'com.google.guava:guava:28.0-jre' - implementation 'com.google.code.gson:gson:2.8.5' - implementation 'org.ow2.asm:asm:8.0' - implementation 'org.ow2.asm:asm-commons:8.0' - implementation 'org.ow2.asm:asm-tree:8.0' - implementation 'org.ow2.asm:asm-util:8.0' - implementation 'net.sf.jopt-simple:jopt-simple:6.0-alpha-3' - implementation 'net.fabricmc:procyon-fabric-compilertools:0.5.35.13' - implementation 'net.fabricmc:cfr:0.0.1' - implementation 'com.bulenkov:darcula:1.0.0-bobbylight' - implementation 'de.sciss:syntaxpane:1.2.0' - implementation 'com.github.lukeu:swing-dpi:0.6' - testImplementation 'junit:junit:4.+' - testImplementation 'org.hamcrest:hamcrest-all:1.+' + group = 'cuchaz' + version = '0.17' - proGuard 'net.sf.proguard:proguard-base:6.+' -} - -def libraryJarsArg = JavaVersion.current().java9Compatible ? "/jmods": "/lib/rt.jar" - -// For each set of test inputs, create an output jar and obfuscate it. -file('src/test/java/cuchaz/enigma/inputs').listFiles().each { theFile -> - if (theFile.directory) { - task("${theFile.name}TestJar", type: Jar) { - from(sourceSets.test.output) { - include "cuchaz/enigma/inputs/$theFile.name/**/*.class" - include 'cuchaz/enigma/inputs/Keep.class' - } - - archiveFileName = theFile.name + '.jar' - destinationDirectory = file('build/test-inputs') - } - - task("${theFile.name}TestObf", type: JavaExec, - dependsOn: "${theFile.name}TestJar") { - main 'proguard.ProGuard' - classpath configurations.proGuard - - args '@src/test/resources/proguard-test.conf', '-injars', file('build/test-inputs/' + - "${theFile.name}.jar"), '-libraryjars', libraryJarsArg, - '-outjars', file('build/test-obf/' + "${theFile.name}.jar") - } + def buildNumber = System.getenv("BUILD_NUMBER") + version = version + "+" + (buildNumber ? "build.$buildNumber" : "local") - test.dependsOn "${theFile.name}TestObf" + task sourcesJar(type: Jar, dependsOn: classes) { + classifier = 'sources' + from sourceSets.main.allSource } -} - -// We also semi-deobfuscate translation.jar to then test it... yeah... oh well. -task('deobfTranslationInput', type: JavaExec, dependsOn: 'translationTestObf') - { - classpath sourceSets.main.runtimeClasspath - main 'cuchaz.enigma.CommandMain' - args 'deobfuscate', file('build/test-obf/translation.jar'), - file('build/test-deobf/translation.jar') - } -test.dependsOn 'deobfTranslationInput' -test { - // Since the Minecraft test is really long (like 10 minutes D:) we turn it - // off by default. - if (!System.getProperty('enableExtremelySlowMinecraftTest', '') - .equalsIgnoreCase('true')) { - exclude 'cuchaz/enigma/TestSourceIndex.class' + java { + withSourcesJar() } - // Allow people to specify a custom path to their Minecraft directory. - // (Example: `gradle build -Denigma.test.minecraftdir=./`) - systemProperties = [ - 'enigma.test.minecraftdir': System.getProperty('test.minecraftdir') - ] -} - -// Set the main class. -jar.manifest.attributes 'Main-Class': 'cuchaz.enigma.Main' - -// Make the "fat" application jar. This is useful to just throw in a classpath -// for tests, though it includes some slightly useless stuff. -shadowJar { - append 'LICENSE' - append 'README.md' -} - -// Create a library jar, containing only the deobfuscation code, for use at -// runtime. This will be deployed to Maven Local with a POM, and can be uploaded -// to a remote server manually (for now anyway). -task libJar (type: Jar) { - classifier = 'lib' - - from(sourceSets.main.output) { - exclude 'cuchaz/enigma/gui/**' - exclude 'cuchaz/enigma/convert/**' - - // Main classes + inner classes (keep CommandMain) - exclude 'cuchaz/enigma/Main.class' - exclude 'cuchaz/enigma/Main.class' + publishing { + publications { + "$project.name"(MavenPublication) { + groupId project.group + artifactId project.name + version project.version + from components.java + } + } } } -task sourcesJar(type: Jar, dependsOn: generateSources) { - classifier = 'sources' - from generatedSourcesDir - from sourceSets.main.resources -} - publishing { - publications { - mavenJava(MavenPublication) { - from components.java - - artifact shadowJar - artifact libJar - artifact sourcesJar - } - } - - // select the repositories you want to publish to repositories { + mavenLocal() + if (project.hasProperty('mavenPass')) { maven { url = "http://mavenupload.modmuss50.me/" diff --git a/docs/protocol.md b/docs/protocol.md deleted file mode 100644 index c14ecb81..00000000 --- a/docs/protocol.md +++ /dev/null @@ -1,366 +0,0 @@ -# Enigma protocol -Enigma uses TCP sockets for communication. Data is sent in each direction as a continuous stream, with packets being -concatenated one after the other. - -In this document, data will be represented in C-like pseudocode. The primitive data types will be the same as those -defined by Java's [DataOutputStream](https://docs.oracle.com/javase/7/docs/api/java/io/DataOutputStream.html), i.e. in -big-endian order for multi-byte integers (`short`, `int` and `long`). The one exception is for Strings, which do *not* -use the same modified UTF format as in `DataOutputStream`, I repeat, the normal `writeUTF` method in `DataOutputStream` -(and the corresponding method in `DataInputStream`) should *not* be used. Instead, there is a custom `utf` struct for -Strings, see below. - -## Login protocol -``` -Client Server -| | -| Login | -| >>>>>>>>>>>>> | -| | -| SyncMappings | -| <<<<<<<<<<<<< | -| | -| ConfirmChange | -| >>>>>>>>>>>>> | -``` -1. On connect, the client sends a login packet to the server. This allows the server to test the validity of the client, - as well as allowing the client to declare metadata about itself, such as the username. -1. After validating the login packet, the server sends all its mappings to the client, and the client will apply them. -1. Upon receiving the mappings, the client sends a `ConfirmChange` packet with `sync_id` set to 0, to confirm that it - has received the mappings and is in sync with the server. Once the server receives this packet, the client will be - allowed to modify mappings. - -The server will not accept any other packets from the client until this entire exchange has been completed. - -## Kicking clients -When the server kicks a client, it may optionally send a `Kick` packet immediately before closing the connection, which -contains the reason why the client was kicked (so the client can display it to the user). This is not required though - -the server may simply terminate the connection. - -## Changing mappings -This section uses the example of renaming, but the same pattern applies to all mapping changes. -``` -Client A Server Client B -| | | -| RenameC2S | | -| >>>>>>>>> | | -| | | -| | RenameS2C | -| | >>>>>>>>>>>>> | -| | | -| | ConfirmChange | -| | <<<<<<<<<<<<< | -``` - -1. Client A validates the name and updates the mapping client-side to give the impression there is no latency >:) -1. Client A sends a rename packet to the server, notifying it of the rename. -1. The server assesses the validity of the rename. If it is invalid for whatever reason (e.g. the mapping was locked or - the name contains invalid characters), then the server sends an appropriate packet back to client A to revert the - change, with `sync_id` set to 0. The server will ignore any `ConfirmChange` packets it receives in response to this. -1. If the rename was valid, the server will lock all clients except client A from being able to modify this mapping, and - then send an appropriate packet to all clients except client A notifying them of this rename. The `sync_id` will be a - unique non-zero value identifying this change. -1. Each client responds to this packet by updating their mappings locally to reflect this change, then sending a - `ConfirmChange` packet with the same `sync_id` as the one in the packet they received, to confirm that they have - received the change. -1. When the server receives the `ConfirmChange` packet, and another change to that mapping hasn't occurred since, the - server will unlock that mapping for that client and allow them to make changes again. - -## Packets -```c -struct Packet { - unsigned short packet_id; - data[]; // depends on packet_id -} -``` -The IDs for client-to-server packets are as follows: -- 0: `Login` -- 1: `ConfirmChange` -- 2: `Rename` -- 3: `RemoveMapping` -- 4: `ChangeDocs` -- 5: `MarkDeobfuscated` -- 6: `Message` - -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` - -### The utf struct -```c -struct utf { - unsigned short length; - byte data[length]; -} -``` -- `length`: The number of bytes in the UTF-8 encoding of the string. Note, this may not be the same as the number of - Unicode characters in the string. -- `data`: A standard UTF-8 encoded byte array representing the string. - -### The Entry struct -```c -enum EntryType { - ENTRY_CLASS = 0, ENTRY_FIELD = 1, ENTRY_METHOD = 2, ENTRY_LOCAL_VAR = 3; -} -struct Entry { - unsigned byte type; - boolean has_parent; - if { - Entry parent; - } - utf name; - boolean has_javadoc; - if { - utf javadoc; - } - if { - utf descriptor; - } - if { - unsigned short index; - boolean parameter; - } -} -``` -- `type`: The type of entry this is. One of `ENTRY_CLASS`, `ENTRY_FIELD`, `ENTRY_METHOD` or `ENTRY_LOCAL_VAR`. -- `parent`: The parent entry. Only class entries may have no parent. fields, methods and inner classes must have their - containing class as their parent. Local variables have a method as a parent. -- `name`: The class/field/method/variable name. -- `javadoc`: The javadoc of an entry, if present. -- `descriptor`: The field/method descriptor. -- `index`: The index of the local variable in the local variable table. -- `parameter`: Whether the local variable is a parameter. - -### The Message struct -```c -enum MessageType { - MESSAGE_CHAT = 0, - MESSAGE_CONNECT = 1, - MESSAGE_DISCONNECT = 2, - MESSAGE_EDIT_DOCS = 3, - MESSAGE_MARK_DEOBF = 4, - MESSAGE_REMOVE_MAPPING = 5, - MESSAGE_RENAME = 6 -}; -typedef unsigned byte message_type_t; - -struct Message { - message_type_t type; - union { // Note that the size of this varies depending on type, it is not constant size - struct { - utf user; - utf message; - } chat; - struct { - utf user; - } connect; - struct { - utf user; - } disconnect; - struct { - utf user; - Entry entry; - } edit_docs; - struct { - utf user; - Entry entry; - } mark_deobf; - struct { - utf user; - Entry entry; - } remove_mapping; - struct { - utf user; - Entry entry; - utf new_name; - } rename; - } data; -}; -``` -- `type`: The type of message this is. One of `MESSAGE_CHAT`, `MESSAGE_CONNECT`, `MESSAGE_DISCONNECT`, - `MESSAGE_EDIT_DOCS`, `MESSAGE_MARK_DEOBF`, `MESSAGE_REMOVE_MAPPING`, `MESSAGE_RENAME`. -- `chat`: Chat message. Use in case `type` is `MESSAGE_CHAT` -- `connect`: Sent when a user connects. Use in case `type` is `MESSAGE_CONNECT` -- `disconnect`: Sent when a user disconnects. Use in case `type` is `MESSAGE_DISCONNECT` -- `edit_docs`: Sent when a user edits the documentation of an entry. Use in case `type` is `MESSAGE_EDIT_DOCS` -- `mark_deobf`: Sent when a user marks an entry as deobfuscated. Use in case `type` is `MESSAGE_MARK_DEOBF` -- `remove_mapping`: Sent when a user removes a mapping. Use in case `type` is `MESSAGE_REMOVE_MAPPING` -- `rename`: Sent when a user renames an entry. Use in case `type` is `MESSAGE_RENAME` -- `user`: The user that performed the action. -- `message`: The message the user sent. -- `entry`: The entry that was modified. -- `new_name`: The new name for the entry. - - -### Login (client-to-server) -```c -struct LoginC2SPacket { - unsigned short protocol_version; - byte checksum[20]; - unsigned byte password_length; - char password[password_length]; - utf username; -} -``` -- `protocol_version`: the version of the protocol. If the version does not match on the server, then the client will be - kicked immediately. Currently always equal to 0. -- `checksum`: the SHA-1 hash of the JAR file the client has open. If this does not match the SHA-1 hash of the JAR file - the server has open, the client will be kicked. -- `password`: the password needed to log into the server. Note that each `char` is 2 bytes, as per the Java data type. - If this password is incorrect, the client will be kicked. -- `username`: the username of the user logging in. If the username is not unique, the client will be kicked. - -### ConfirmChange (client-to-server) -```c -struct ConfirmChangeC2SPacket { - unsigned short sync_id; -} -``` -- `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 { - utf message; -} -``` -- `message`: The text message the user sent. - -### Kick (server-to-client) -```c -struct KickS2CPacket { - utf reason; -} -``` -- `reason`: the reason for the kick, may or may not be a translation key for the client to display to the user. - -### SyncMappings (server-to-client) -```c -struct SyncMappingsS2CPacket { - int num_roots; - MappingNode roots[num_roots]; -} -struct MappingNode { - NoParentEntry obf_entry; - boolean is_named; - if { - utf name; - boolean has_javadoc; - if { - utf javadoc; - } - } - unsigned short children_count; - MappingNode children[children_count]; -} -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. -- `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 { - Message message; -} -``` - -### UserList (server-to-client) -```c -struct UserListS2CPacket { - unsigned short len; - utf user[len]; -} -``` diff --git a/enigma-cli/build.gradle b/enigma-cli/build.gradle new file mode 100644 index 00000000..3ee12396 --- /dev/null +++ b/enigma-cli/build.gradle @@ -0,0 +1,5 @@ +dependencies { + implementation project(':enigma') +} + +jar.manifest.attributes 'Main-Class': 'cuchaz.enigma.command.Main' diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java b/enigma-cli/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java new file mode 100644 index 00000000..e4deef8b --- /dev/null +++ b/enigma-cli/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java @@ -0,0 +1,77 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.Enigma; +import cuchaz.enigma.EnigmaProject; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.representation.entry.ClassEntry; + +import java.nio.file.Path; +import java.util.Set; +import java.util.stream.Collectors; + +public class CheckMappingsCommand extends Command { + + public CheckMappingsCommand() { + super("checkmappings"); + } + + @Override + public String getUsage() { + return " "; + } + + @Override + public boolean isValidArgument(int length) { + return length == 2; + } + + @Override + public void run(String... args) throws Exception { + Path fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)).toPath(); + Path fileMappings = getReadablePath(getArg(args, 1, "mappings file", true)); + + Enigma enigma = Enigma.create(); + + System.out.println("Reading JAR..."); + + EnigmaProject project = enigma.openJar(fileJarIn, ProgressListener.none()); + + System.out.println("Reading mappings..."); + + MappingFormat format = chooseEnigmaFormat(fileMappings); + MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters(); + + EntryTree mappings = format.read(fileMappings, ProgressListener.none(), saveParameters); + project.setMappings(mappings); + + JarIndex idx = project.getJarIndex(); + + boolean error = false; + + for (Set partition : idx.getPackageVisibilityIndex().getPartitions()) { + long packages = partition.stream() + .map(project.getMapper()::deobfuscate) + .map(ClassEntry::getPackageName) + .distinct() + .count(); + if (packages > 1) { + error = true; + System.err.println("ERROR: Must be in one package:\n" + partition.stream() + .map(project.getMapper()::deobfuscate) + .map(ClassEntry::toString) + .sorted() + .collect(Collectors.joining("\n")) + ); + } + } + + if (error) { + throw new IllegalStateException("Errors in package visibility detected, see SysErr above"); + } + } +} diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/Command.java b/enigma-cli/src/main/java/cuchaz/enigma/command/Command.java new file mode 100644 index 00000000..0640e3e7 --- /dev/null +++ b/enigma-cli/src/main/java/cuchaz/enigma/command/Command.java @@ -0,0 +1,154 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.Enigma; +import cuchaz.enigma.EnigmaProject; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; +import cuchaz.enigma.translation.mapping.tree.EntryTree; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import com.google.common.io.MoreFiles; + +public abstract class Command { + public final String name; + + protected Command(String name) { + this.name = name; + } + + public abstract String getUsage(); + + public abstract boolean isValidArgument(int length); + + public abstract void run(String... args) throws Exception; + + protected static EnigmaProject openProject(Path fileJarIn, Path fileMappings) throws Exception { + ProgressListener progress = new ConsoleProgressListener(); + + Enigma enigma = Enigma.create(); + + System.out.println("Reading jar..."); + EnigmaProject project = enigma.openJar(fileJarIn, progress); + + if (fileMappings != null) { + System.out.println("Reading mappings..."); + + MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters(); + EntryTree mappings = chooseEnigmaFormat(fileMappings).read(fileMappings, progress, saveParameters); + + project.setMappings(mappings); + } + + return project; + } + + protected static MappingFormat chooseEnigmaFormat(Path path) { + if (Files.isDirectory(path)) { + return MappingFormat.ENIGMA_DIRECTORY; + } else if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(path))) { + return MappingFormat.ENIGMA_ZIP; + } else { + return MappingFormat.ENIGMA_FILE; + } + } + + protected static File getWritableFile(String path) { + if (path == null) { + return null; + } + File file = new File(path).getAbsoluteFile(); + File dir = file.getParentFile(); + if (dir == null) { + throw new IllegalArgumentException("Cannot write file: " + path); + } + // quick fix to avoid stupid stuff in Gradle code + if (!dir.isDirectory()) { + dir.mkdirs(); + } + return file; + } + + protected static File getWritableFolder(String path) { + if (path == null) { + return null; + } + File dir = new File(path).getAbsoluteFile(); + if (!dir.exists()) { + throw new IllegalArgumentException("Cannot write to folder: " + dir); + } + return dir; + } + + protected static File getReadableFile(String path) { + if (path == null) { + return null; + } + File file = new File(path).getAbsoluteFile(); + if (!file.exists()) { + throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath()); + } + return file; + } + + protected static Path getReadablePath(String path) { + if (path == null) { + return null; + } + Path file = Paths.get(path).toAbsolutePath(); + if (!Files.exists(file)) { + throw new IllegalArgumentException("Cannot find file: " + file.toString()); + } + return file; + } + + protected static String getArg(String[] args, int i, String name, boolean required) { + if (i >= args.length) { + if (required) { + throw new IllegalArgumentException(name + " is required"); + } else { + return null; + } + } + return args[i]; + } + + public static class ConsoleProgressListener implements ProgressListener { + + private static final int ReportTime = 5000; // 5s + + private int totalWork; + private long startTime; + private long lastReportTime; + + @Override + public void init(int totalWork, String title) { + this.totalWork = totalWork; + this.startTime = System.currentTimeMillis(); + this.lastReportTime = this.startTime; + System.out.println(title); + } + + @Override + public void step(int numDone, String message) { + long now = System.currentTimeMillis(); + boolean isLastUpdate = numDone == this.totalWork; + boolean shouldReport = isLastUpdate || now - this.lastReportTime > ReportTime; + + if (shouldReport) { + int percent = numDone * 100 / this.totalWork; + System.out.println(String.format("\tProgress: %3d%%", percent)); + this.lastReportTime = now; + } + if (isLastUpdate) { + double elapsedSeconds = (now - this.startTime) / 1000.0; + System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds)); + } + } + } +} diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/ComposeMappingsCommand.java b/enigma-cli/src/main/java/cuchaz/enigma/command/ComposeMappingsCommand.java new file mode 100644 index 00000000..e10fd47e --- /dev/null +++ b/enigma-cli/src/main/java/cuchaz/enigma/command/ComposeMappingsCommand.java @@ -0,0 +1,42 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.translation.mapping.MappingOperations; +import cuchaz.enigma.translation.mapping.serde.MappingParseException; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat; +import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.utils.Utils; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class ComposeMappingsCommand extends Command { + public ComposeMappingsCommand() { + super("compose-mappings"); + } + + @Override + public String getUsage() { + return " "; + } + + @Override + public boolean isValidArgument(int length) { + return length == 7; + } + + @Override + public void run(String... args) throws IOException, MappingParseException { + MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); + + EntryTree left = MappingCommandsUtil.read(args[0], Paths.get(args[1]), saveParameters); + EntryTree right = MappingCommandsUtil.read(args[2], Paths.get(args[3]), saveParameters); + EntryTree result = MappingOperations.compose(left, right, args[6].equals("left") || args[6].equals("both"), args[6].equals("right") || args[6].equals("both")); + + Path output = Paths.get(args[5]); + Utils.delete(output); + MappingCommandsUtil.write(result, args[4], output, saveParameters); + } +} diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java b/enigma-cli/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java new file mode 100644 index 00000000..144d89c5 --- /dev/null +++ b/enigma-cli/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java @@ -0,0 +1,39 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.translation.mapping.serde.MappingParseException; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat; +import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.utils.Utils; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class ConvertMappingsCommand extends Command { + public ConvertMappingsCommand() { + super("convert-mappings"); + } + + @Override + public String getUsage() { + return " "; + } + + @Override + public boolean isValidArgument(int length) { + return length == 4; + } + + @Override + public void run(String... args) throws IOException, MappingParseException { + MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); + + EntryTree mappings = MappingCommandsUtil.read(args[0], Paths.get(args[1]), saveParameters); + + Path output = Paths.get(args[3]); + Utils.delete(output); + MappingCommandsUtil.write(mappings, args[2], output, saveParameters); + } +} diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/DecompileCommand.java b/enigma-cli/src/main/java/cuchaz/enigma/command/DecompileCommand.java new file mode 100644 index 00000000..3d15dac6 --- /dev/null +++ b/enigma-cli/src/main/java/cuchaz/enigma/command/DecompileCommand.java @@ -0,0 +1,54 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.EnigmaProject; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.source.DecompilerService; +import cuchaz.enigma.source.Decompilers; + +import java.lang.reflect.Field; +import java.nio.file.Path; +import java.util.Locale; + +public class DecompileCommand extends Command { + + public DecompileCommand() { + super("decompile"); + } + + @Override + public String getUsage() { + return " []"; + } + + @Override + public boolean isValidArgument(int length) { + return length == 2 || length == 3; + } + + @Override + public void run(String... args) throws Exception { + String decompilerName = getArg(args, 1, "decompiler", true); + Path fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)).toPath(); + Path fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true)).toPath(); + Path fileMappings = getReadablePath(getArg(args, 3, "mappings file", false)); + + DecompilerService decompilerService; + + try { + Field decompilerField = Decompilers.class.getField(decompilerName.toUpperCase(Locale.ROOT)); + decompilerService = (DecompilerService) decompilerField.get(null); + } catch (NoSuchFieldException e) { + System.err.println("Decompiler not found."); + return; + } + + EnigmaProject project = openProject(fileJarIn, fileMappings); + + ProgressListener progress = new ConsoleProgressListener(); + + EnigmaProject.JarExport jar = project.exportRemappedJar(progress); + EnigmaProject.SourceExport source = jar.decompile(progress, decompilerService); + + source.write(fileJarOut, progress); + } +} diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java b/enigma-cli/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java new file mode 100644 index 00000000..b0d2a7d0 --- /dev/null +++ b/enigma-cli/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java @@ -0,0 +1,37 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.EnigmaProject; +import cuchaz.enigma.ProgressListener; + +import java.nio.file.Path; + +public class DeobfuscateCommand extends Command { + + public DeobfuscateCommand() { + super("deobfuscate"); + } + + @Override + public String getUsage() { + return " []"; + } + + @Override + public boolean isValidArgument(int length) { + return length == 2 || length == 3; + } + + @Override + public void run(String... args) throws Exception { + Path fileJarIn = getReadablePath(getArg(args, 0, "in jar", true)); + Path fileJarOut = getWritableFile(getArg(args, 1, "out jar", true)).toPath(); + Path fileMappings = getReadablePath(getArg(args, 2, "mappings file", false)); + + EnigmaProject project = openProject(fileJarIn, fileMappings); + + ProgressListener progress = new ConsoleProgressListener(); + + EnigmaProject.JarExport jar = project.exportRemappedJar(progress); + jar.write(fileJarOut, progress); + } +} diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/InvertMappingsCommand.java b/enigma-cli/src/main/java/cuchaz/enigma/command/InvertMappingsCommand.java new file mode 100644 index 00000000..0780a965 --- /dev/null +++ b/enigma-cli/src/main/java/cuchaz/enigma/command/InvertMappingsCommand.java @@ -0,0 +1,41 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.translation.mapping.MappingOperations; +import cuchaz.enigma.translation.mapping.serde.MappingParseException; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat; +import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.utils.Utils; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class InvertMappingsCommand extends Command { + public InvertMappingsCommand() { + super("invert-mappings"); + } + + @Override + public String getUsage() { + return " "; + } + + @Override + public boolean isValidArgument(int length) { + return length == 4; + } + + @Override + public void run(String... args) throws IOException, MappingParseException { + MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); + + EntryTree source = MappingCommandsUtil.read(args[0], Paths.get(args[1]), saveParameters); + EntryTree result = MappingOperations.invert(source); + + Path output = Paths.get(args[3]); + Utils.delete(output); + MappingCommandsUtil.write(result, args[2], output, saveParameters); + } +} diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/Main.java b/enigma-cli/src/main/java/cuchaz/enigma/command/Main.java new file mode 100644 index 00000000..0a4c1b9b --- /dev/null +++ b/enigma-cli/src/main/java/cuchaz/enigma/command/Main.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.command; + +import cuchaz.enigma.Enigma; + +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; + +public class Main { + + private static final Map COMMANDS = new LinkedHashMap<>(); + + public static void main(String... args) throws Exception { + try { + // process the command + if (args.length < 1) + throw new IllegalArgumentException("Requires a command"); + String command = args[0].toLowerCase(Locale.ROOT); + + Command cmd = COMMANDS.get(command); + if (cmd == null) + throw new IllegalArgumentException("Command not recognized: " + command); + + if (!cmd.isValidArgument(args.length - 1)) { + throw new CommandHelpException(cmd); + } + + String[] cmdArgs = new String[args.length - 1]; + System.arraycopy(args, 1, cmdArgs, 0, args.length - 1); + + try { + cmd.run(cmdArgs); + } catch (Exception ex) { + throw new CommandHelpException(cmd, ex); + } + } catch (CommandHelpException ex) { + System.err.println(ex.getMessage()); + System.out.println(String.format("%s - %s", Enigma.NAME, Enigma.VERSION)); + System.out.println("Command " + ex.command.name + " has encountered an error! Usage:"); + printHelp(ex.command); + System.exit(1); + } catch (IllegalArgumentException ex) { + System.err.println(ex.getMessage()); + printHelp(); + System.exit(1); + } + } + + private static void printHelp() { + System.out.println(String.format("%s - %s", Enigma.NAME, Enigma.VERSION)); + System.out.println("Usage:"); + System.out.println("\tjava -cp enigma.jar cuchaz.enigma.command.CommandMain "); + System.out.println("\twhere is one of:"); + + for (Command command : COMMANDS.values()) { + printHelp(command); + } + } + + private static void printHelp(Command command) { + System.out.println("\t\t" + command.name + " " + command.getUsage()); + } + + private static void register(Command command) { + Command old = COMMANDS.put(command.name, command); + if (old != null) { + System.err.println("Command " + old + " with name " + command.name + " has been substituted by " + command); + } + } + + static { + register(new DeobfuscateCommand()); + register(new DecompileCommand()); + register(new ConvertMappingsCommand()); + register(new ComposeMappingsCommand()); + register(new InvertMappingsCommand()); + register(new CheckMappingsCommand()); + register(new MapSpecializedMethodsCommand()); + } + + private static final class CommandHelpException extends IllegalArgumentException { + + final Command command; + + CommandHelpException(Command command) { + this.command = command; + } + + CommandHelpException(Command command, Throwable cause) { + super(cause); + this.command = command; + } + } +} diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java b/enigma-cli/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java new file mode 100644 index 00000000..292de192 --- /dev/null +++ b/enigma-cli/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java @@ -0,0 +1,71 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.analysis.ClassCache; +import cuchaz.enigma.analysis.index.BridgeMethodIndex; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat; +import cuchaz.enigma.translation.mapping.serde.MappingParseException; +import cuchaz.enigma.translation.MappingTranslator; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.*; +import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; +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.MethodEntry; +import cuchaz.enigma.utils.Utils; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; + +public class MapSpecializedMethodsCommand extends Command { + public MapSpecializedMethodsCommand() { + super("map-specialized-methods"); + } + + @Override + public String getUsage() { + return " "; + } + + @Override + public boolean isValidArgument(int length) { + return length == 5; + } + + @Override + public void run(String... args) throws IOException, MappingParseException { + run(Paths.get(args[0]), args[1], Paths.get(args[2]), args[3], Paths.get(args[4])); + } + + public static void run(Path jar, String sourceFormat, Path sourcePath, String resultFormat, Path output) throws IOException, MappingParseException { + MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); + EntryTree source = MappingCommandsUtil.read(sourceFormat, sourcePath, saveParameters); + EntryTree result = new HashEntryTree<>(); + ClassCache classCache = ClassCache.of(jar); + JarIndex jarIndex = classCache.index(ProgressListener.none()); + BridgeMethodIndex bridgeMethodIndex = jarIndex.getBridgeMethodIndex(); + Translator translator = new MappingTranslator(source, jarIndex.getEntryResolver()); + + // Copy all non-specialized methods + for (EntryTreeNode node : source) { + if (!(node.getEntry() instanceof MethodEntry) || !bridgeMethodIndex.isSpecializedMethod((MethodEntry) node.getEntry())) { + result.insert(node.getEntry(), node.getValue()); + } + } + + // Add correct mappings for specialized methods + for (Map.Entry entry : bridgeMethodIndex.getBridgeToSpecialized().entrySet()) { + MethodEntry bridge = entry.getKey(); + MethodEntry specialized = entry.getValue(); + String name = translator.translate(bridge).getName(); + result.insert(specialized, new EntryMapping(name)); + } + + Utils.delete(output); + MappingCommandsUtil.write(result, resultFormat, output, saveParameters); + } +} diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/MappingCommandsUtil.java b/enigma-cli/src/main/java/cuchaz/enigma/command/MappingCommandsUtil.java new file mode 100644 index 00000000..d365129b --- /dev/null +++ b/enigma-cli/src/main/java/cuchaz/enigma/command/MappingCommandsUtil.java @@ -0,0 +1,87 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.mapping.serde.MappingParseException; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; +import cuchaz.enigma.translation.mapping.serde.*; +import cuchaz.enigma.translation.mapping.serde.enigma.EnigmaMappingsReader; +import cuchaz.enigma.translation.mapping.serde.enigma.EnigmaMappingsWriter; +import cuchaz.enigma.translation.mapping.serde.tiny.TinyMappingsReader; +import cuchaz.enigma.translation.mapping.serde.tiny.TinyMappingsWriter; +import cuchaz.enigma.translation.mapping.serde.tinyv2.TinyV2Writer; +import cuchaz.enigma.translation.mapping.tree.EntryTree; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public final class MappingCommandsUtil { + private MappingCommandsUtil() {} + + public static EntryTree read(String type, Path path, MappingSaveParameters saveParameters) throws MappingParseException, IOException { + if (type.equals("enigma")) { + return (Files.isDirectory(path) ? EnigmaMappingsReader.DIRECTORY : EnigmaMappingsReader.ZIP).read(path, ProgressListener.none(), saveParameters); + } + + if (type.equals("tiny")) { + return TinyMappingsReader.INSTANCE.read(path, ProgressListener.none(), saveParameters); + } + + MappingFormat format = null; + try { + format = MappingFormat.valueOf(type.toUpperCase()); + } catch (IllegalArgumentException ignored) { + if (type.equals("tinyv2")) { + format = MappingFormat.TINY_V2; + } + } + + if (format != null) { + return format.getReader().read(path, ProgressListener.none(), saveParameters); + } + + throw new IllegalArgumentException("no reader for " + type); + } + + public static void write(EntryTree mappings, String type, Path path, MappingSaveParameters saveParameters) { + if (type.equals("enigma")) { + EnigmaMappingsWriter.DIRECTORY.write(mappings, path, ProgressListener.none(), saveParameters); + return; + } + + if (type.startsWith("tinyv2:") || type.startsWith("tiny_v2:")) { + String[] split = type.split(":"); + + if (split.length != 3) { + throw new IllegalArgumentException("specify column names as 'tinyv2:from_namespace:to_namespace'"); + } + + new TinyV2Writer(split[1], split[2]).write(mappings, path, ProgressListener.none(), saveParameters); + return; + } + + if (type.startsWith("tiny:")) { + String[] split = type.split(":"); + + if (split.length != 3) { + throw new IllegalArgumentException("specify column names as 'tiny:from_column:to_column'"); + } + + new TinyMappingsWriter(split[1], split[2]).write(mappings, path, ProgressListener.none(), saveParameters); + return; + } + + MappingFormat format = null; + try { + format = MappingFormat.valueOf(type.toUpperCase()); + } catch (IllegalArgumentException ignored) {} + + if (format != null) { + format.getWriter().write(mappings, path, ProgressListener.none(), saveParameters); + return; + } + + throw new IllegalArgumentException("no writer for " + type); + } +} diff --git a/enigma-cli/src/test/java/cuchaz/enigma/command/CheckMappingsCommandTest.java b/enigma-cli/src/test/java/cuchaz/enigma/command/CheckMappingsCommandTest.java new file mode 100644 index 00000000..a29bba40 --- /dev/null +++ b/enigma-cli/src/test/java/cuchaz/enigma/command/CheckMappingsCommandTest.java @@ -0,0 +1,21 @@ +package cuchaz.enigma.command; + +import org.junit.Test; + +import java.io.File; + +public class CheckMappingsCommandTest { + private static final String PACKAGE_ACCESS = "../enigma/build/test-obf/packageAccess.jar"; + + @Test(expected = IllegalStateException.class) + public void testWrong() throws Exception { + new CheckMappingsCommand().run(new File(PACKAGE_ACCESS).getAbsolutePath(), new File("src/test/resources" + + "/packageAccess/wrongMappings").getAbsolutePath()); + } + + @Test + public void testRight() throws Exception { + new CheckMappingsCommand().run(new File(PACKAGE_ACCESS).getAbsolutePath(), new File("src/test/resources" + + "/packageAccess/correctMappings").getAbsolutePath()); + } +} diff --git a/enigma-cli/src/test/resources/packageAccess/correctMappings/base/Base.mapping b/enigma-cli/src/test/resources/packageAccess/correctMappings/base/Base.mapping new file mode 100644 index 00000000..0a86def0 --- /dev/null +++ b/enigma-cli/src/test/resources/packageAccess/correctMappings/base/Base.mapping @@ -0,0 +1 @@ +CLASS a base/Base diff --git a/enigma-cli/src/test/resources/packageAccess/correctMappings/base/One.mapping b/enigma-cli/src/test/resources/packageAccess/correctMappings/base/One.mapping new file mode 100644 index 00000000..dd4c2083 --- /dev/null +++ b/enigma-cli/src/test/resources/packageAccess/correctMappings/base/One.mapping @@ -0,0 +1 @@ +CLASS b base/One diff --git a/enigma-cli/src/test/resources/packageAccess/correctMappings/two/Two.mapping b/enigma-cli/src/test/resources/packageAccess/correctMappings/two/Two.mapping new file mode 100644 index 00000000..a179349c --- /dev/null +++ b/enigma-cli/src/test/resources/packageAccess/correctMappings/two/Two.mapping @@ -0,0 +1 @@ +CLASS c two/Two diff --git a/enigma-cli/src/test/resources/packageAccess/wrongMappings/base/Base.mapping b/enigma-cli/src/test/resources/packageAccess/wrongMappings/base/Base.mapping new file mode 100644 index 00000000..0a86def0 --- /dev/null +++ b/enigma-cli/src/test/resources/packageAccess/wrongMappings/base/Base.mapping @@ -0,0 +1 @@ +CLASS a base/Base diff --git a/enigma-cli/src/test/resources/packageAccess/wrongMappings/one/One.mapping b/enigma-cli/src/test/resources/packageAccess/wrongMappings/one/One.mapping new file mode 100644 index 00000000..15b42cf5 --- /dev/null +++ b/enigma-cli/src/test/resources/packageAccess/wrongMappings/one/One.mapping @@ -0,0 +1 @@ +CLASS b one/One diff --git a/enigma-cli/src/test/resources/packageAccess/wrongMappings/two/Two.mapping b/enigma-cli/src/test/resources/packageAccess/wrongMappings/two/Two.mapping new file mode 100644 index 00000000..a179349c --- /dev/null +++ b/enigma-cli/src/test/resources/packageAccess/wrongMappings/two/Two.mapping @@ -0,0 +1 @@ +CLASS c two/Two diff --git a/enigma-server/build.gradle b/enigma-server/build.gradle new file mode 100644 index 00000000..bf72b184 --- /dev/null +++ b/enigma-server/build.gradle @@ -0,0 +1,6 @@ +dependencies { + implementation project(':enigma') + implementation 'net.sf.jopt-simple:jopt-simple:6.0-alpha-3' +} + +jar.manifest.attributes 'Main-Class': 'cuchaz.enigma.network.DedicatedEnigmaServer' diff --git a/enigma-server/docs/protocol.md b/enigma-server/docs/protocol.md new file mode 100644 index 00000000..c14ecb81 --- /dev/null +++ b/enigma-server/docs/protocol.md @@ -0,0 +1,366 @@ +# Enigma protocol +Enigma uses TCP sockets for communication. Data is sent in each direction as a continuous stream, with packets being +concatenated one after the other. + +In this document, data will be represented in C-like pseudocode. The primitive data types will be the same as those +defined by Java's [DataOutputStream](https://docs.oracle.com/javase/7/docs/api/java/io/DataOutputStream.html), i.e. in +big-endian order for multi-byte integers (`short`, `int` and `long`). The one exception is for Strings, which do *not* +use the same modified UTF format as in `DataOutputStream`, I repeat, the normal `writeUTF` method in `DataOutputStream` +(and the corresponding method in `DataInputStream`) should *not* be used. Instead, there is a custom `utf` struct for +Strings, see below. + +## Login protocol +``` +Client Server +| | +| Login | +| >>>>>>>>>>>>> | +| | +| SyncMappings | +| <<<<<<<<<<<<< | +| | +| ConfirmChange | +| >>>>>>>>>>>>> | +``` +1. On connect, the client sends a login packet to the server. This allows the server to test the validity of the client, + as well as allowing the client to declare metadata about itself, such as the username. +1. After validating the login packet, the server sends all its mappings to the client, and the client will apply them. +1. Upon receiving the mappings, the client sends a `ConfirmChange` packet with `sync_id` set to 0, to confirm that it + has received the mappings and is in sync with the server. Once the server receives this packet, the client will be + allowed to modify mappings. + +The server will not accept any other packets from the client until this entire exchange has been completed. + +## Kicking clients +When the server kicks a client, it may optionally send a `Kick` packet immediately before closing the connection, which +contains the reason why the client was kicked (so the client can display it to the user). This is not required though - +the server may simply terminate the connection. + +## Changing mappings +This section uses the example of renaming, but the same pattern applies to all mapping changes. +``` +Client A Server Client B +| | | +| RenameC2S | | +| >>>>>>>>> | | +| | | +| | RenameS2C | +| | >>>>>>>>>>>>> | +| | | +| | ConfirmChange | +| | <<<<<<<<<<<<< | +``` + +1. Client A validates the name and updates the mapping client-side to give the impression there is no latency >:) +1. Client A sends a rename packet to the server, notifying it of the rename. +1. The server assesses the validity of the rename. If it is invalid for whatever reason (e.g. the mapping was locked or + the name contains invalid characters), then the server sends an appropriate packet back to client A to revert the + change, with `sync_id` set to 0. The server will ignore any `ConfirmChange` packets it receives in response to this. +1. If the rename was valid, the server will lock all clients except client A from being able to modify this mapping, and + then send an appropriate packet to all clients except client A notifying them of this rename. The `sync_id` will be a + unique non-zero value identifying this change. +1. Each client responds to this packet by updating their mappings locally to reflect this change, then sending a + `ConfirmChange` packet with the same `sync_id` as the one in the packet they received, to confirm that they have + received the change. +1. When the server receives the `ConfirmChange` packet, and another change to that mapping hasn't occurred since, the + server will unlock that mapping for that client and allow them to make changes again. + +## Packets +```c +struct Packet { + unsigned short packet_id; + data[]; // depends on packet_id +} +``` +The IDs for client-to-server packets are as follows: +- 0: `Login` +- 1: `ConfirmChange` +- 2: `Rename` +- 3: `RemoveMapping` +- 4: `ChangeDocs` +- 5: `MarkDeobfuscated` +- 6: `Message` + +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` + +### The utf struct +```c +struct utf { + unsigned short length; + byte data[length]; +} +``` +- `length`: The number of bytes in the UTF-8 encoding of the string. Note, this may not be the same as the number of + Unicode characters in the string. +- `data`: A standard UTF-8 encoded byte array representing the string. + +### The Entry struct +```c +enum EntryType { + ENTRY_CLASS = 0, ENTRY_FIELD = 1, ENTRY_METHOD = 2, ENTRY_LOCAL_VAR = 3; +} +struct Entry { + unsigned byte type; + boolean has_parent; + if { + Entry parent; + } + utf name; + boolean has_javadoc; + if { + utf javadoc; + } + if { + utf descriptor; + } + if { + unsigned short index; + boolean parameter; + } +} +``` +- `type`: The type of entry this is. One of `ENTRY_CLASS`, `ENTRY_FIELD`, `ENTRY_METHOD` or `ENTRY_LOCAL_VAR`. +- `parent`: The parent entry. Only class entries may have no parent. fields, methods and inner classes must have their + containing class as their parent. Local variables have a method as a parent. +- `name`: The class/field/method/variable name. +- `javadoc`: The javadoc of an entry, if present. +- `descriptor`: The field/method descriptor. +- `index`: The index of the local variable in the local variable table. +- `parameter`: Whether the local variable is a parameter. + +### The Message struct +```c +enum MessageType { + MESSAGE_CHAT = 0, + MESSAGE_CONNECT = 1, + MESSAGE_DISCONNECT = 2, + MESSAGE_EDIT_DOCS = 3, + MESSAGE_MARK_DEOBF = 4, + MESSAGE_REMOVE_MAPPING = 5, + MESSAGE_RENAME = 6 +}; +typedef unsigned byte message_type_t; + +struct Message { + message_type_t type; + union { // Note that the size of this varies depending on type, it is not constant size + struct { + utf user; + utf message; + } chat; + struct { + utf user; + } connect; + struct { + utf user; + } disconnect; + struct { + utf user; + Entry entry; + } edit_docs; + struct { + utf user; + Entry entry; + } mark_deobf; + struct { + utf user; + Entry entry; + } remove_mapping; + struct { + utf user; + Entry entry; + utf new_name; + } rename; + } data; +}; +``` +- `type`: The type of message this is. One of `MESSAGE_CHAT`, `MESSAGE_CONNECT`, `MESSAGE_DISCONNECT`, + `MESSAGE_EDIT_DOCS`, `MESSAGE_MARK_DEOBF`, `MESSAGE_REMOVE_MAPPING`, `MESSAGE_RENAME`. +- `chat`: Chat message. Use in case `type` is `MESSAGE_CHAT` +- `connect`: Sent when a user connects. Use in case `type` is `MESSAGE_CONNECT` +- `disconnect`: Sent when a user disconnects. Use in case `type` is `MESSAGE_DISCONNECT` +- `edit_docs`: Sent when a user edits the documentation of an entry. Use in case `type` is `MESSAGE_EDIT_DOCS` +- `mark_deobf`: Sent when a user marks an entry as deobfuscated. Use in case `type` is `MESSAGE_MARK_DEOBF` +- `remove_mapping`: Sent when a user removes a mapping. Use in case `type` is `MESSAGE_REMOVE_MAPPING` +- `rename`: Sent when a user renames an entry. Use in case `type` is `MESSAGE_RENAME` +- `user`: The user that performed the action. +- `message`: The message the user sent. +- `entry`: The entry that was modified. +- `new_name`: The new name for the entry. + + +### Login (client-to-server) +```c +struct LoginC2SPacket { + unsigned short protocol_version; + byte checksum[20]; + unsigned byte password_length; + char password[password_length]; + utf username; +} +``` +- `protocol_version`: the version of the protocol. If the version does not match on the server, then the client will be + kicked immediately. Currently always equal to 0. +- `checksum`: the SHA-1 hash of the JAR file the client has open. If this does not match the SHA-1 hash of the JAR file + the server has open, the client will be kicked. +- `password`: the password needed to log into the server. Note that each `char` is 2 bytes, as per the Java data type. + If this password is incorrect, the client will be kicked. +- `username`: the username of the user logging in. If the username is not unique, the client will be kicked. + +### ConfirmChange (client-to-server) +```c +struct ConfirmChangeC2SPacket { + unsigned short sync_id; +} +``` +- `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 { + utf message; +} +``` +- `message`: The text message the user sent. + +### Kick (server-to-client) +```c +struct KickS2CPacket { + utf reason; +} +``` +- `reason`: the reason for the kick, may or may not be a translation key for the client to display to the user. + +### SyncMappings (server-to-client) +```c +struct SyncMappingsS2CPacket { + int num_roots; + MappingNode roots[num_roots]; +} +struct MappingNode { + NoParentEntry obf_entry; + boolean is_named; + if { + utf name; + boolean has_javadoc; + if { + utf javadoc; + } + } + unsigned short children_count; + MappingNode children[children_count]; +} +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. +- `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 { + Message message; +} +``` + +### UserList (server-to-client) +```c +struct UserListS2CPacket { + unsigned short len; + utf user[len]; +} +``` diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java b/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java new file mode 100644 index 00000000..720744bf --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java @@ -0,0 +1,29 @@ +package cuchaz.enigma.network; + +import cuchaz.enigma.analysis.EntryReference; +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 java.util.List; + +public interface ClientPacketHandler { + void openMappings(EntryTree mappings); + + void rename(EntryReference, Entry> reference, String newName, boolean refreshClassTree, boolean jumpToReference); + + void removeMapping(EntryReference, Entry> reference, boolean jumpToReference); + + void changeDocs(EntryReference, Entry> reference, String updatedDocs, boolean jumpToReference); + + void markAsDeobfuscated(EntryReference, Entry> reference, boolean jumpToReference); + + void disconnectIfConnected(String reason); + + void sendPacket(Packet packet); + + void addMessage(Message message); + + void updateUserList(List users); +} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java b/enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java new file mode 100644 index 00000000..924302f3 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java @@ -0,0 +1,200 @@ +package cuchaz.enigma.network; + +import com.google.common.io.MoreFiles; +import cuchaz.enigma.*; +import cuchaz.enigma.translation.mapping.serde.MappingParseException; +import cuchaz.enigma.translation.mapping.EntryRemapper; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; +import cuchaz.enigma.utils.Utils; +import joptsimple.OptionParser; +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import joptsimple.ValueConverter; + +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; + +public class DedicatedEnigmaServer extends EnigmaServer { + + private final EnigmaProfile profile; + private final MappingFormat mappingFormat; + private final Path mappingsFile; + private final PrintWriter log; + private BlockingQueue tasks = new LinkedBlockingDeque<>(); + + public DedicatedEnigmaServer( + byte[] jarChecksum, + char[] password, + EnigmaProfile profile, + MappingFormat mappingFormat, + Path mappingsFile, + PrintWriter log, + EntryRemapper mappings, + int port + ) { + super(jarChecksum, password, mappings, port); + this.profile = profile; + this.mappingFormat = mappingFormat; + this.mappingsFile = mappingsFile; + this.log = log; + } + + @Override + protected void runOnThread(Runnable task) { + tasks.add(task); + } + + @Override + public void log(String message) { + super.log(message); + log.println(message); + } + + public static void main(String[] args) { + OptionParser parser = new OptionParser(); + + OptionSpec jarOpt = parser.accepts("jar", "Jar file to open at startup") + .withRequiredArg() + .required() + .withValuesConvertedBy(PathConverter.INSTANCE); + + OptionSpec mappingsOpt = parser.accepts("mappings", "Mappings file to open at startup") + .withRequiredArg() + .required() + .withValuesConvertedBy(PathConverter.INSTANCE); + + OptionSpec profileOpt = parser.accepts("profile", "Profile json to apply at startup") + .withRequiredArg() + .withValuesConvertedBy(PathConverter.INSTANCE); + + OptionSpec portOpt = parser.accepts("port", "Port to run the server on") + .withOptionalArg() + .ofType(Integer.class) + .defaultsTo(EnigmaServer.DEFAULT_PORT); + + OptionSpec passwordOpt = parser.accepts("password", "The password to join the server") + .withRequiredArg() + .defaultsTo(""); + + OptionSpec logFileOpt = parser.accepts("log", "The log file to write to") + .withRequiredArg() + .withValuesConvertedBy(PathConverter.INSTANCE) + .defaultsTo(Paths.get("log.txt")); + + OptionSet parsedArgs = parser.parse(args); + Path jar = parsedArgs.valueOf(jarOpt); + Path mappingsFile = parsedArgs.valueOf(mappingsOpt); + Path profileFile = parsedArgs.valueOf(profileOpt); + int port = parsedArgs.valueOf(portOpt); + char[] password = parsedArgs.valueOf(passwordOpt).toCharArray(); + if (password.length > EnigmaServer.MAX_PASSWORD_LENGTH) { + System.err.println("Password too long, must be at most " + EnigmaServer.MAX_PASSWORD_LENGTH + " characters"); + System.exit(1); + } + Path logFile = parsedArgs.valueOf(logFileOpt); + + System.out.println("Starting Enigma server"); + DedicatedEnigmaServer server; + try { + byte[] checksum = Utils.zipSha1(parsedArgs.valueOf(jarOpt)); + + EnigmaProfile profile = EnigmaProfile.read(profileFile); + Enigma enigma = Enigma.builder().setProfile(profile).build(); + System.out.println("Indexing Jar..."); + EnigmaProject project = enigma.openJar(jar, ProgressListener.none()); + + MappingFormat mappingFormat = MappingFormat.ENIGMA_DIRECTORY; + EntryRemapper mappings; + if (!Files.exists(mappingsFile)) { + mappings = EntryRemapper.empty(project.getJarIndex()); + } else { + System.out.println("Reading mappings..."); + if (Files.isDirectory(mappingsFile)) { + mappingFormat = MappingFormat.ENIGMA_DIRECTORY; + } else if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(mappingsFile))) { + mappingFormat = MappingFormat.ENIGMA_ZIP; + } else { + mappingFormat = MappingFormat.ENIGMA_FILE; + } + mappings = EntryRemapper.mapped(project.getJarIndex(), mappingFormat.read(mappingsFile, ProgressListener.none(), profile.getMappingSaveParameters())); + } + + PrintWriter log = new PrintWriter(Files.newBufferedWriter(logFile)); + + server = new DedicatedEnigmaServer(checksum, password, profile, mappingFormat, mappingsFile, log, mappings, port); + server.start(); + System.out.println("Server started"); + } catch (IOException | MappingParseException e) { + System.err.println("Error starting server!"); + e.printStackTrace(); + System.exit(1); + return; + } + + // noinspection RedundantSuppression + // noinspection Convert2MethodRef - javac 8 bug + Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> server.runOnThread(() -> server.saveMappings()), 0, 1, TimeUnit.MINUTES); + Runtime.getRuntime().addShutdownHook(new Thread(server::saveMappings)); + + while (true) { + try { + server.tasks.take().run(); + } catch (InterruptedException e) { + break; + } + } + } + + @Override + public synchronized void stop() { + super.stop(); + System.exit(0); + } + + private void saveMappings() { + mappingFormat.write(getMappings().getObfToDeobf(), getMappings().takeMappingDelta(), mappingsFile, ProgressListener.none(), profile.getMappingSaveParameters()); + log.flush(); + } + + public static class PathConverter implements ValueConverter { + public static final ValueConverter INSTANCE = new PathConverter(); + + PathConverter() { + } + + @Override + public Path convert(String path) { + // expand ~ to the home dir + if (path.startsWith("~")) { + // get the home dir + Path dirHome = Paths.get(System.getProperty("user.home")); + + // is the path just ~/ or is it ~user/ ? + if (path.startsWith("~/")) { + return dirHome.resolve(path.substring(2)); + } else { + return dirHome.getParent().resolve(path.substring(1)); + } + } + + return Paths.get(path); + } + + @Override + public Class valueType() { + return Path.class; + } + + @Override + public String valuePattern() { + return "path"; + } + } +} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaClient.java b/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaClient.java new file mode 100644 index 00000000..71bd011c --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaClient.java @@ -0,0 +1,78 @@ +package cuchaz.enigma.network; + +import cuchaz.enigma.network.packet.Packet; +import cuchaz.enigma.network.packet.PacketRegistry; + +import javax.swing.*; +import java.io.*; +import java.net.Socket; +import java.net.SocketException; + +public class EnigmaClient { + + private final ClientPacketHandler controller; + + private final String ip; + private final int port; + private Socket socket; + private DataOutput output; + + public EnigmaClient(ClientPacketHandler controller, String ip, int port) { + this.controller = controller; + this.ip = ip; + this.port = port; + } + + public void connect() throws IOException { + socket = new Socket(ip, port); + output = new DataOutputStream(socket.getOutputStream()); + Thread thread = new Thread(() -> { + try { + DataInput input = new DataInputStream(socket.getInputStream()); + while (true) { + int packetId; + try { + packetId = input.readUnsignedByte(); + } catch (EOFException | SocketException e) { + break; + } + Packet packet = PacketRegistry.createS2CPacket(packetId); + if (packet == null) { + throw new IOException("Received invalid packet id " + packetId); + } + packet.read(input); + SwingUtilities.invokeLater(() -> packet.handle(controller)); + } + } catch (IOException e) { + controller.disconnectIfConnected(e.toString()); + return; + } + controller.disconnectIfConnected("Disconnected"); + }); + thread.setName("Client I/O thread"); + thread.setDaemon(true); + thread.start(); + } + + public synchronized void disconnect() { + if (socket != null && !socket.isClosed()) { + try { + socket.close(); + } catch (IOException e1) { + System.err.println("Failed to close socket"); + e1.printStackTrace(); + } + } + } + + + public void sendPacket(Packet packet) { + try { + output.writeByte(PacketRegistry.getC2SId(packet)); + packet.write(output); + } catch (IOException e) { + controller.disconnectIfConnected(e.toString()); + } + } + +} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java b/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java new file mode 100644 index 00000000..6027a6bd --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java @@ -0,0 +1,290 @@ +package cuchaz.enigma.network; + +import cuchaz.enigma.network.packet.KickS2CPacket; +import cuchaz.enigma.network.packet.MessageS2CPacket; +import cuchaz.enigma.network.packet.Packet; +import cuchaz.enigma.network.packet.PacketRegistry; +import cuchaz.enigma.network.packet.RemoveMappingS2CPacket; +import cuchaz.enigma.network.packet.RenameS2CPacket; +import cuchaz.enigma.network.packet.UserListS2CPacket; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryRemapper; +import cuchaz.enigma.translation.representation.entry.Entry; + +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; + +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 String OWNER_USERNAME = "Owner"; + 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 + + private final int port; + private ServerSocket socket; + private List clients = new CopyOnWriteArrayList<>(); + private Map usernames = new HashMap<>(); + private Set unapprovedClients = new HashSet<>(); + + private final byte[] jarChecksum; + private final char[] password; + + public static final int DUMMY_SYNC_ID = 0; + private final EntryRemapper mappings; + private Map, Integer> syncIds = new HashMap<>(); + private Map> inverseSyncIds = new HashMap<>(); + private Map> clientsNeedingConfirmation = new HashMap<>(); + private int nextSyncId = DUMMY_SYNC_ID + 1; + + private static int nextIoId = 0; + + public EnigmaServer(byte[] jarChecksum, char[] password, EntryRemapper mappings, int port) { + this.jarChecksum = jarChecksum; + this.password = password; + this.mappings = mappings; + this.port = port; + } + + public void start() throws IOException { + socket = new ServerSocket(port); + log("Server started on " + socket.getInetAddress() + ":" + port); + Thread thread = new Thread(() -> { + try { + while (!socket.isClosed()) { + acceptClient(); + } + } catch (SocketException e) { + System.out.println("Server closed"); + } catch (IOException e) { + e.printStackTrace(); + } + }); + thread.setName("Server client listener"); + thread.setDaemon(true); + thread.start(); + } + + private void acceptClient() throws IOException { + Socket client = socket.accept(); + clients.add(client); + Thread thread = new Thread(() -> { + try { + DataInput input = new DataInputStream(client.getInputStream()); + while (true) { + int packetId; + try { + packetId = input.readUnsignedByte(); + } catch (EOFException | SocketException e) { + break; + } + Packet packet = PacketRegistry.createC2SPacket(packetId); + if (packet == null) { + throw new IOException("Received invalid packet id " + packetId); + } + packet.read(input); + runOnThread(() -> packet.handle(new ServerPacketHandler(client, this))); + } + } catch (IOException e) { + kick(client, e.toString()); + e.printStackTrace(); + return; + } + kick(client, "disconnect.disconnected"); + }); + thread.setName("Server I/O thread #" + (nextIoId++)); + thread.setDaemon(true); + thread.start(); + } + + public void stop() { + runOnThread(() -> { + if (socket != null && !socket.isClosed()) { + for (Socket client : clients) { + kick(client, "disconnect.server_closed"); + } + try { + socket.close(); + } catch (IOException e) { + System.err.println("Failed to close server socket"); + e.printStackTrace(); + } + } + }); + } + + public void kick(Socket client, String reason) { + if (!clients.remove(client)) return; + + sendPacket(client, new KickS2CPacket(reason)); + + clientsNeedingConfirmation.values().removeIf(list -> { + list.remove(client); + return list.isEmpty(); + }); + String username = usernames.remove(client); + try { + client.close(); + } catch (IOException e) { + System.err.println("Failed to close server client socket"); + e.printStackTrace(); + } + + if (username != null) { + System.out.println("Kicked " + username + " because " + reason); + sendMessage(Message.disconnect(username)); + } + sendUsernamePacket(); + } + + public boolean isUsernameTaken(String username) { + return usernames.containsValue(username); + } + + public void setUsername(Socket client, String username) { + usernames.put(client, username); + sendUsernamePacket(); + } + + private void sendUsernamePacket() { + List usernames = new ArrayList<>(this.usernames.values()); + Collections.sort(usernames); + sendToAll(new UserListS2CPacket(usernames)); + } + + public String getUsername(Socket client) { + return usernames.get(client); + } + + public void sendPacket(Socket client, Packet packet) { + if (!client.isClosed()) { + int packetId = PacketRegistry.getS2CId(packet); + try { + DataOutput output = new DataOutputStream(client.getOutputStream()); + output.writeByte(packetId); + packet.write(output); + } catch (IOException e) { + if (!(packet instanceof KickS2CPacket)) { + kick(client, e.toString()); + e.printStackTrace(); + } + } + } + } + + public void sendToAll(Packet packet) { + for (Socket client : clients) { + sendPacket(client, packet); + } + } + + public void sendToAllExcept(Socket excluded, Packet packet) { + for (Socket client : clients) { + if (client != excluded) { + sendPacket(client, packet); + } + } + } + + public boolean canModifyEntry(Socket client, Entry entry) { + if (unapprovedClients.contains(client)) { + return false; + } + + Integer syncId = syncIds.get(entry); + if (syncId == null) { + return true; + } + Set clients = clientsNeedingConfirmation.get(syncId); + return clients == null || !clients.contains(client); + } + + public int lockEntry(Socket exception, Entry entry) { + int syncId = nextSyncId; + nextSyncId++; + // sync id is sent as an unsigned short, can't have more than 65536 + if (nextSyncId == 65536) { + nextSyncId = DUMMY_SYNC_ID + 1; + } + Integer oldSyncId = syncIds.get(entry); + if (oldSyncId != null) { + clientsNeedingConfirmation.remove(oldSyncId); + } + syncIds.put(entry, syncId); + inverseSyncIds.put(syncId, entry); + Set clients = new HashSet<>(this.clients); + clients.remove(exception); + clientsNeedingConfirmation.put(syncId, clients); + return syncId; + } + + public void confirmChange(Socket client, int syncId) { + if (usernames.containsKey(client)) { + unapprovedClients.remove(client); + } + + Set clients = clientsNeedingConfirmation.get(syncId); + if (clients != null) { + clients.remove(client); + if (clients.isEmpty()) { + clientsNeedingConfirmation.remove(syncId); + syncIds.remove(inverseSyncIds.remove(syncId)); + } + } + } + + public void sendCorrectMapping(Socket client, Entry entry, boolean refreshClassTree) { + EntryMapping oldMapping = mappings.getDeobfMapping(entry); + String oldName = oldMapping == null ? null : oldMapping.getTargetName(); + if (oldName == null) { + sendPacket(client, new RemoveMappingS2CPacket(DUMMY_SYNC_ID, entry)); + } else { + sendPacket(client, new RenameS2CPacket(0, entry, oldName, refreshClassTree)); + } + } + + protected abstract void runOnThread(Runnable task); + + public void log(String message) { + System.out.println(message); + } + + protected boolean isRunning() { + return !socket.isClosed(); + } + + public byte[] getJarChecksum() { + return jarChecksum; + } + + public char[] getPassword() { + return password; + } + + public EntryRemapper getMappings() { + return mappings; + } + + public void sendMessage(Message message) { + log(String.format("[MSG] %s", message.translate())); + sendToAll(new MessageS2CPacket(message)); + } + +} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java b/enigma-server/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java new file mode 100644 index 00000000..21c6825b --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java @@ -0,0 +1,16 @@ +package cuchaz.enigma.network; + +import cuchaz.enigma.translation.mapping.EntryRemapper; + +import javax.swing.*; + +public class IntegratedEnigmaServer extends EnigmaServer { + public IntegratedEnigmaServer(byte[] jarChecksum, char[] password, EntryRemapper mappings, int port) { + super(jarChecksum, password, mappings, port); + } + + @Override + protected void runOnThread(Runnable task) { + SwingUtilities.invokeLater(task); + } +} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/Message.java b/enigma-server/src/main/java/cuchaz/enigma/network/Message.java new file mode 100644 index 00000000..c1578387 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/Message.java @@ -0,0 +1,393 @@ +package cuchaz.enigma.network; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Objects; + +import cuchaz.enigma.network.packet.PacketHelper; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.utils.I18n; + +public abstract class Message { + + public final String user; + + public static Chat chat(String user, String message) { + return new Chat(user, message); + } + + public static Connect connect(String user) { + return new Connect(user); + } + + public static Disconnect disconnect(String user) { + return new Disconnect(user); + } + + public static EditDocs editDocs(String user, Entry entry) { + return new EditDocs(user, entry); + } + + public static MarkDeobf markDeobf(String user, Entry entry) { + return new MarkDeobf(user, entry); + } + + public static RemoveMapping removeMapping(String user, Entry entry) { + return new RemoveMapping(user, entry); + } + + public static Rename rename(String user, Entry entry, String newName) { + return new Rename(user, entry, newName); + } + + public abstract String translate(); + + public abstract Type getType(); + + public static Message read(DataInput input) throws IOException { + byte typeId = input.readByte(); + if (typeId < 0 || typeId >= Type.values().length) { + throw new IOException(String.format("Invalid message type ID %d", typeId)); + } + Type type = Type.values()[typeId]; + String user = input.readUTF(); + switch (type) { + case CHAT: + String message = input.readUTF(); + return chat(user, message); + case CONNECT: + return connect(user); + case DISCONNECT: + return disconnect(user); + case EDIT_DOCS: + Entry entry = PacketHelper.readEntry(input); + return editDocs(user, entry); + case MARK_DEOBF: + entry = PacketHelper.readEntry(input); + return markDeobf(user, entry); + case REMOVE_MAPPING: + entry = PacketHelper.readEntry(input); + return removeMapping(user, entry); + case RENAME: + entry = PacketHelper.readEntry(input); + String newName = input.readUTF(); + return rename(user, entry, newName); + default: + throw new IllegalStateException("unreachable"); + } + } + + public void write(DataOutput output) throws IOException { + output.writeByte(getType().ordinal()); + PacketHelper.writeString(output, user); + } + + private Message(String user) { + this.user = user; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Message message = (Message) o; + return Objects.equals(user, message.user); + } + + @Override + public int hashCode() { + return Objects.hash(user); + } + + public enum Type { + CHAT, + CONNECT, + DISCONNECT, + EDIT_DOCS, + MARK_DEOBF, + REMOVE_MAPPING, + RENAME, + } + + public static final class Chat extends Message { + + public final String message; + + private Chat(String user, String message) { + super(user); + this.message = message; + } + + @Override + public void write(DataOutput output) throws IOException { + super.write(output); + PacketHelper.writeString(output, message); + } + + @Override + public String translate() { + return String.format(I18n.translate("message.chat.text"), user, message); + } + + @Override + public Type getType() { + return Type.CHAT; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Chat chat = (Chat) o; + return Objects.equals(message, chat.message); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), message); + } + + @Override + public String toString() { + return String.format("Message.Chat { user: '%s', message: '%s' }", user, message); + } + + } + + public static final class Connect extends Message { + + private Connect(String user) { + super(user); + } + + @Override + public String translate() { + return String.format(I18n.translate("message.connect.text"), user); + } + + @Override + public Type getType() { + return Type.CONNECT; + } + + @Override + public String toString() { + return String.format("Message.Connect { user: '%s' }", user); + } + + } + + public static final class Disconnect extends Message { + + private Disconnect(String user) { + super(user); + } + + @Override + public String translate() { + return String.format(I18n.translate("message.disconnect.text"), user); + } + + @Override + public Type getType() { + return Type.DISCONNECT; + } + + @Override + public String toString() { + return String.format("Message.Disconnect { user: '%s' }", user); + } + + } + + public static final class EditDocs extends Message { + + public final Entry entry; + + private EditDocs(String user, Entry entry) { + super(user); + this.entry = entry; + } + + @Override + public void write(DataOutput output) throws IOException { + super.write(output); + PacketHelper.writeEntry(output, entry); + } + + @Override + public String translate() { + return String.format(I18n.translate("message.edit_docs.text"), user, entry); + } + + @Override + public Type getType() { + return Type.EDIT_DOCS; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + EditDocs editDocs = (EditDocs) o; + return Objects.equals(entry, editDocs.entry); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), entry); + } + + @Override + public String toString() { + return String.format("Message.EditDocs { user: '%s', entry: %s }", user, entry); + } + + } + + public static final class MarkDeobf extends Message { + + public final Entry entry; + + private MarkDeobf(String user, Entry entry) { + super(user); + this.entry = entry; + } + + @Override + public void write(DataOutput output) throws IOException { + super.write(output); + PacketHelper.writeEntry(output, entry); + } + + @Override + public String translate() { + return String.format(I18n.translate("message.mark_deobf.text"), user, entry); + } + + @Override + public Type getType() { + return Type.MARK_DEOBF; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + MarkDeobf markDeobf = (MarkDeobf) o; + return Objects.equals(entry, markDeobf.entry); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), entry); + } + + @Override + public String toString() { + return String.format("Message.MarkDeobf { user: '%s', entry: %s }", user, entry); + } + + } + + public static final class RemoveMapping extends Message { + + public final Entry entry; + + private RemoveMapping(String user, Entry entry) { + super(user); + this.entry = entry; + } + + @Override + public void write(DataOutput output) throws IOException { + super.write(output); + PacketHelper.writeEntry(output, entry); + } + + @Override + public String translate() { + return String.format(I18n.translate("message.remove_mapping.text"), user, entry); + } + + @Override + public Type getType() { + return Type.REMOVE_MAPPING; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + RemoveMapping that = (RemoveMapping) o; + return Objects.equals(entry, that.entry); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), entry); + } + + @Override + public String toString() { + return String.format("Message.RemoveMapping { user: '%s', entry: %s }", user, entry); + } + + } + + public static final class Rename extends Message { + + public final Entry entry; + public final String newName; + + private Rename(String user, Entry entry, String newName) { + super(user); + this.entry = entry; + this.newName = newName; + } + + @Override + public void write(DataOutput output) throws IOException { + super.write(output); + PacketHelper.writeEntry(output, entry); + PacketHelper.writeString(output, newName); + } + + @Override + public String translate() { + return String.format(I18n.translate("message.rename.text"), user, entry, newName); + } + + @Override + public Type getType() { + return Type.RENAME; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Rename rename = (Rename) o; + return Objects.equals(entry, rename.entry) && + Objects.equals(newName, rename.newName); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), entry, newName); + } + + @Override + public String toString() { + return String.format("Message.Rename { user: '%s', entry: %s, newName: '%s' }", user, entry, newName); + } + + } + +} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java b/enigma-server/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java new file mode 100644 index 00000000..86185536 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java @@ -0,0 +1,22 @@ +package cuchaz.enigma.network; + +import java.net.Socket; + +public class ServerPacketHandler { + + private final Socket client; + private final EnigmaServer server; + + public ServerPacketHandler(Socket client, EnigmaServer server) { + this.client = client; + this.server = server; + } + + public Socket getClient() { + return client; + } + + public EnigmaServer getServer() { + return server; + } +} 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 new file mode 100644 index 00000000..1b52cf14 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java @@ -0,0 +1,59 @@ +package cuchaz.enigma.network.packet; + +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.Utils; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +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) { + 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(entry, mapping.withDocs(Utils.isBlank(newDocs) ? null : newDocs)); + + 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 new file mode 100644 index 00000000..12a30253 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java @@ -0,0 +1,44 @@ +package cuchaz.enigma.network.packet; + +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.network.ClientPacketHandler; +import cuchaz.enigma.translation.representation.entry.Entry; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +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) { + controller.changeDocs(new EntryReference<>(entry, entry.getName()), newDocs, false); + controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); + } +} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java new file mode 100644 index 00000000..78ef9645 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java @@ -0,0 +1,33 @@ +package cuchaz.enigma.network.packet; + +import cuchaz.enigma.network.ServerPacketHandler; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public class ConfirmChangeC2SPacket implements Packet { + private int syncId; + + ConfirmChangeC2SPacket() { + } + + public ConfirmChangeC2SPacket(int syncId) { + this.syncId = syncId; + } + + @Override + public void read(DataInput input) throws IOException { + this.syncId = input.readUnsignedShort(); + } + + @Override + public void write(DataOutput output) throws IOException { + output.writeShort(syncId); + } + + @Override + public void handle(ServerPacketHandler handler) { + handler.getServer().confirmChange(handler.getClient(), syncId); + } +} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java new file mode 100644 index 00000000..9a112a80 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java @@ -0,0 +1,33 @@ +package cuchaz.enigma.network.packet; + +import cuchaz.enigma.network.ClientPacketHandler; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public class KickS2CPacket implements Packet { + private String reason; + + KickS2CPacket() { + } + + public KickS2CPacket(String reason) { + this.reason = reason; + } + + @Override + public void read(DataInput input) throws IOException { + this.reason = PacketHelper.readString(input); + } + + @Override + public void write(DataOutput output) throws IOException { + PacketHelper.writeString(output, reason); + } + + @Override + public void handle(ClientPacketHandler controller) { + controller.disconnectIfConnected(reason); + } +} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java new file mode 100644 index 00000000..da0f44a5 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java @@ -0,0 +1,75 @@ +package cuchaz.enigma.network.packet; + +import cuchaz.enigma.network.EnigmaServer; +import cuchaz.enigma.network.ServerPacketHandler; +import cuchaz.enigma.network.Message; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Arrays; + +public class LoginC2SPacket implements Packet { + private byte[] jarChecksum; + private char[] password; + private String username; + + LoginC2SPacket() { + } + + public LoginC2SPacket(byte[] jarChecksum, char[] password, String username) { + this.jarChecksum = jarChecksum; + this.password = password; + this.username = username; + } + + @Override + public void read(DataInput input) throws IOException { + if (input.readUnsignedShort() != EnigmaServer.PROTOCOL_VERSION) { + throw new IOException("Mismatching protocol"); + } + this.jarChecksum = new byte[EnigmaServer.CHECKSUM_SIZE]; + input.readFully(jarChecksum); + this.password = new char[input.readUnsignedByte()]; + for (int i = 0; i < password.length; i++) { + password[i] = input.readChar(); + } + this.username = PacketHelper.readString(input); + } + + @Override + public void write(DataOutput output) throws IOException { + output.writeShort(EnigmaServer.PROTOCOL_VERSION); + output.write(jarChecksum); + output.writeByte(password.length); + for (char c : password) { + output.writeChar(c); + } + PacketHelper.writeString(output, username); + } + + @Override + public void handle(ServerPacketHandler handler) { + boolean usernameTaken = handler.getServer().isUsernameTaken(username); + handler.getServer().setUsername(handler.getClient(), username); + handler.getServer().log(username + " logged in with IP " + handler.getClient().getInetAddress().toString() + ":" + handler.getClient().getPort()); + + if (!Arrays.equals(password, handler.getServer().getPassword())) { + handler.getServer().kick(handler.getClient(), "disconnect.wrong_password"); + return; + } + + if (usernameTaken) { + handler.getServer().kick(handler.getClient(), "disconnect.username_taken"); + return; + } + + if (!Arrays.equals(jarChecksum, handler.getServer().getJarChecksum())) { + handler.getServer().kick(handler.getClient(), "disconnect.wrong_jar"); + return; + } + + handler.getServer().sendPacket(handler.getClient(), new SyncMappingsS2CPacket(handler.getServer().getMappings().getObfToDeobf())); + handler.getServer().sendMessage(Message.connect(username)); + } +} 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 new file mode 100644 index 00000000..a41c620f --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java @@ -0,0 +1,48 @@ +package cuchaz.enigma.network.packet; + +import cuchaz.enigma.network.ServerPacketHandler; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.network.Message; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +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) { + boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); + if (!valid) { + handler.getServer().sendCorrectMapping(handler.getClient(), entry, true); + return; + } + + handler.getServer().getMappings().mapFromObf(entry, new EntryMapping(handler.getServer().getMappings().deobfuscate(entry).getName())); + 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 new file mode 100644 index 00000000..7504430d --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java @@ -0,0 +1,40 @@ +package cuchaz.enigma.network.packet; + +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.network.ClientPacketHandler; +import cuchaz.enigma.translation.representation.entry.Entry; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +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) { + controller.markAsDeobfuscated(new EntryReference<>(entry, entry.getName()), false); + controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); + } +} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java new file mode 100644 index 00000000..3bc09e79 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java @@ -0,0 +1,39 @@ +package cuchaz.enigma.network.packet; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import cuchaz.enigma.network.ServerPacketHandler; +import cuchaz.enigma.network.Message; + +public class MessageC2SPacket implements Packet { + + private String message; + + MessageC2SPacket() { + } + + public MessageC2SPacket(String message) { + this.message = message; + } + + @Override + public void read(DataInput input) throws IOException { + message = PacketHelper.readString(input); + } + + @Override + public void write(DataOutput output) throws IOException { + PacketHelper.writeString(output, message); + } + + @Override + public void handle(ServerPacketHandler handler) { + String message = this.message.trim(); + if (!message.isEmpty()) { + handler.getServer().sendMessage(Message.chat(handler.getServer().getUsername(handler.getClient()), message)); + } + } + +} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java new file mode 100644 index 00000000..2b07968d --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java @@ -0,0 +1,36 @@ +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.network.Message; + +public class MessageS2CPacket implements Packet { + + private Message message; + + MessageS2CPacket() { + } + + public MessageS2CPacket(Message message) { + this.message = message; + } + + @Override + public void read(DataInput input) throws IOException { + message = Message.read(input); + } + + @Override + public void write(DataOutput output) throws IOException { + message.write(output); + } + + @Override + public void handle(ClientPacketHandler handler) { + handler.addMessage(message); + } + +} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/Packet.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/Packet.java new file mode 100644 index 00000000..2f16dfb9 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/Packet.java @@ -0,0 +1,15 @@ +package cuchaz.enigma.network.packet; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +public interface Packet { + + void read(DataInput input) throws IOException; + + void write(DataOutput output) throws IOException; + + void handle(H handler); + +} 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 new file mode 100644 index 00000000..464606e0 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java @@ -0,0 +1,135 @@ +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; + +public class PacketHelper { + + private static final int ENTRY_CLASS = 0, ENTRY_FIELD = 1, ENTRY_METHOD = 2, ENTRY_LOCAL_VAR = 3; + private static final int MAX_STRING_LENGTH = 65535; + + public static Entry readEntry(DataInput input) throws IOException { + return readEntry(input, null, true); + } + + public static Entry readEntry(DataInput input, Entry parent, boolean includeParent) throws IOException { + int type = input.readUnsignedByte(); + + if (includeParent && input.readBoolean()) { + parent = readEntry(input, null, true); + } + + String name = readString(input); + + String javadocs = null; + if (input.readBoolean()) { + javadocs = readString(input); + } + + switch (type) { + case ENTRY_CLASS: { + if (parent != null && !(parent instanceof ClassEntry)) { + throw new IOException("Class requires class parent"); + } + return new ClassEntry((ClassEntry) parent, name, javadocs); + } + case ENTRY_FIELD: { + if (!(parent instanceof ClassEntry)) { + throw new IOException("Field requires class parent"); + } + 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"); + } + 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"); + } + 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); + } + } + + public static void writeEntry(DataOutput output, Entry entry) throws IOException { + writeEntry(output, entry, true); + } + + public static void writeEntry(DataOutput output, Entry entry, boolean includeParent) throws IOException { + // type + if (entry instanceof ClassEntry) { + output.writeByte(ENTRY_CLASS); + } else if (entry instanceof FieldEntry) { + output.writeByte(ENTRY_FIELD); + } else if (entry instanceof MethodEntry) { + output.writeByte(ENTRY_METHOD); + } else if (entry instanceof LocalVariableEntry) { + output.writeByte(ENTRY_LOCAL_VAR); + } else { + throw new IOException("Don't know how to serialize entry of type " + entry.getClass().getSimpleName()); + } + + // parent + if (includeParent) { + output.writeBoolean(entry.getParent() != null); + if (entry.getParent() != null) { + writeEntry(output, entry.getParent(), true); + } + } + + // name + writeString(output, entry.getName()); + + // javadocs + output.writeBoolean(entry.getJavadocs() != null); + if (entry.getJavadocs() != null) { + writeString(output, entry.getJavadocs()); + } + + // type-specific stuff + if (entry instanceof FieldEntry) { + writeString(output, ((FieldEntry) entry).getDesc().toString()); + } else if (entry instanceof MethodEntry) { + writeString(output, ((MethodEntry) entry).getDesc().toString()); + } else if (entry instanceof LocalVariableEntry) { + LocalVariableEntry localVar = (LocalVariableEntry) entry; + output.writeShort(localVar.getIndex()); + output.writeBoolean(localVar.isArgument()); + } + } + + public static String readString(DataInput input) throws IOException { + int length = input.readUnsignedShort(); + byte[] bytes = new byte[length]; + input.readFully(bytes); + return new String(bytes, StandardCharsets.UTF_8); + } + + public static void writeString(DataOutput output, String str) throws IOException { + byte[] bytes = str.getBytes(StandardCharsets.UTF_8); + if (bytes.length > MAX_STRING_LENGTH) { + throw new IOException("String too long, was " + bytes.length + " bytes, max " + MAX_STRING_LENGTH + " allowed"); + } + output.writeShort(bytes.length); + output.write(bytes); + } + +} 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 new file mode 100644 index 00000000..3b8af81c --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java @@ -0,0 +1,64 @@ +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; + +public class PacketRegistry { + + private static final Map>, Integer> c2sPacketIds = new HashMap<>(); + private static final Map>> c2sPacketCreators = new HashMap<>(); + private static final Map>, Integer> s2cPacketIds = new HashMap<>(); + private static final Map>> s2cPacketCreators = new HashMap<>(); + + private static > void registerC2S(int id, Class clazz, Supplier creator) { + c2sPacketIds.put(clazz, id); + c2sPacketCreators.put(id, creator); + } + + private static > void registerS2C(int id, Class clazz, Supplier creator) { + s2cPacketIds.put(clazz, id); + s2cPacketCreators.put(id, creator); + } + + 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); + + 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); + } + + public static int getC2SId(Packet packet) { + return c2sPacketIds.get(packet.getClass()); + } + + public static Packet createC2SPacket(int id) { + Supplier> creator = c2sPacketCreators.get(id); + return creator == null ? null : creator.get(); + } + + public static int getS2CId(Packet packet) { + return s2cPacketIds.get(packet.getClass()); + } + + public static Packet createS2CPacket(int id) { + Supplier> creator = s2cPacketCreators.get(id); + return creator == null ? null : creator.get(); + } + +} 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 new file mode 100644 index 00000000..3f852285 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java @@ -0,0 +1,55 @@ +package cuchaz.enigma.network.packet; + +import cuchaz.enigma.network.ServerPacketHandler; +import cuchaz.enigma.translation.mapping.IllegalNameException; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.network.Message; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +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) { + boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); + + if (valid) { + try { + handler.getServer().getMappings().removeByObf(entry); + } catch (IllegalNameException e) { + valid = false; + } + } + + 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 new file mode 100644 index 00000000..70d803c1 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java @@ -0,0 +1,40 @@ +package cuchaz.enigma.network.packet; + +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.network.ClientPacketHandler; +import cuchaz.enigma.translation.representation.entry.Entry; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +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) { + controller.removeMapping(new EntryReference<>(entry, entry.getName()), false); + 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 new file mode 100644 index 00000000..e3e7e379 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java @@ -0,0 +1,64 @@ +package cuchaz.enigma.network.packet; + +import cuchaz.enigma.network.ServerPacketHandler; +import cuchaz.enigma.translation.mapping.IllegalNameException; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.network.Message; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +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) { + boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); + + if (valid) { + try { + handler.getServer().getMappings().mapFromObf(entry, new EntryMapping(newName)); + } catch (IllegalNameException e) { + valid = false; + } + } + + 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 new file mode 100644 index 00000000..787e89e6 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java @@ -0,0 +1,48 @@ +package cuchaz.enigma.network.packet; + +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.network.ClientPacketHandler; +import cuchaz.enigma.translation.representation.entry.Entry; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +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) { + controller.rename(new EntryReference<>(entry, entry.getName()), newName, refreshClassTree, false); + 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 new file mode 100644 index 00000000..76ecbc7d --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java @@ -0,0 +1,88 @@ +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 java.util.stream.Collectors; + +public class SyncMappingsS2CPacket implements Packet { + private EntryTree mappings; + + SyncMappingsS2CPacket() { + } + + public SyncMappingsS2CPacket(EntryTree mappings) { + this.mappings = mappings; + } + + @Override + public void read(DataInput input) throws IOException { + mappings = new HashEntryTree<>(); + int size = input.readInt(); + for (int i = 0; i < size; i++) { + readEntryTreeNode(input, null); + } + } + + 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); + } + } + mappings.insert(entry, mapping); + int size = input.readUnsignedShort(); + for (int i = 0; i < size; i++) { + readEntryTreeNode(input, entry); + } + } + + @Override + public void write(DataOutput output) throws IOException { + List> roots = mappings.getRootNodes().collect(Collectors.toList()); + output.writeInt(roots.size()); + for (EntryTreeNode node : roots) { + writeEntryTreeNode(output, node); + } + } + + 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()); + } + } + Collection> children = node.getChildNodes(); + output.writeShort(children.size()); + for (EntryTreeNode child : children) { + writeEntryTreeNode(output, child); + } + } + + @Override + public void handle(ClientPacketHandler controller) { + controller.openMappings(mappings); + controller.sendPacket(new ConfirmChangeC2SPacket(EnigmaServer.DUMMY_SYNC_ID)); + } +} diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java new file mode 100644 index 00000000..b4a277a4 --- /dev/null +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java @@ -0,0 +1,44 @@ +package cuchaz.enigma.network.packet; + +import cuchaz.enigma.network.ClientPacketHandler; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class UserListS2CPacket implements Packet { + + private List users; + + UserListS2CPacket() { + } + + public UserListS2CPacket(List users) { + this.users = users; + } + + @Override + public void read(DataInput input) throws IOException { + int len = input.readUnsignedShort(); + users = new ArrayList<>(len); + for (int i = 0; i < len; i++) { + users.add(input.readUTF()); + } + } + + @Override + public void write(DataOutput output) throws IOException { + output.writeShort(users.size()); + for (String user : users) { + PacketHelper.writeString(output, user); + } + } + + @Override + public void handle(ClientPacketHandler handler) { + handler.updateUserList(users); + } + +} diff --git a/enigma-swing/build.gradle b/enigma-swing/build.gradle new file mode 100644 index 00000000..a1bcafc5 --- /dev/null +++ b/enigma-swing/build.gradle @@ -0,0 +1,23 @@ +plugins { + id 'com.github.johnrengelman.shadow' version '5.2.0' +} + +dependencies { + implementation project(':enigma') + implementation project(':enigma-server') + + implementation 'net.sf.jopt-simple:jopt-simple:6.0-alpha-3' + implementation 'com.bulenkov:darcula:1.0.0-bobbylight' + implementation 'de.sciss:syntaxpane:1.2.0' + implementation 'com.github.lukeu:swing-dpi:0.6' +} + +jar.manifest.attributes 'Main-Class': 'cuchaz.enigma.gui.Main' + +publishing { + publications { + shadow(MavenPublication) { publication -> + project.shadow.component(publication) + } + } +} \ No newline at end of file diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/BrowserCaret.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/BrowserCaret.java new file mode 100644 index 00000000..af105dbd --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/BrowserCaret.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.gui; + +import javax.swing.text.DefaultCaret; + +public class BrowserCaret extends DefaultCaret { + + @Override + public boolean isSelectionVisible() { + return true; + } + + @Override + public boolean isVisible() { + return true; + } + +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java new file mode 100644 index 00000000..3d0e04c9 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java @@ -0,0 +1,532 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.gui; + +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.*; + +import javax.annotation.Nullable; +import javax.swing.JOptionPane; +import javax.swing.JTree; +import javax.swing.event.CellEditorListener; +import javax.swing.event.ChangeEvent; +import javax.swing.tree.*; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import cuchaz.enigma.gui.node.ClassSelectorClassNode; +import cuchaz.enigma.gui.node.ClassSelectorPackageNode; +import cuchaz.enigma.translation.mapping.IllegalNameException; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.entry.ClassEntry; + +public class ClassSelector extends JTree { + + public static final Comparator DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getFullName); + + private final GuiController controller; + + private DefaultMutableTreeNode rootNodes; + private ClassSelectionListener selectionListener; + private RenameSelectionListener renameSelectionListener; + private Comparator comparator; + + private final Map displayedObfToDeobf = new HashMap<>(); + + public ClassSelector(Gui gui, Comparator comparator, boolean isRenamable) { + this.comparator = comparator; + this.controller = gui.getController(); + + // configure the tree control + setEditable(true); + setRootVisible(false); + setShowsRootHandles(false); + setModel(null); + + // hook events + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + if (selectionListener != null && event.getClickCount() == 2) { + // get the selected node + TreePath path = getSelectionPath(); + if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) { + ClassSelectorClassNode node = (ClassSelectorClassNode) path.getLastPathComponent(); + selectionListener.onSelectClass(node.getObfEntry()); + } + } + } + }); + + final JTree tree = this; + + final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree, + (DefaultTreeCellRenderer) tree.getCellRenderer()) { + @Override + public boolean isCellEditable(EventObject event) { + return isRenamable && !(event instanceof MouseEvent) && super.isCellEditable(event); + } + }; + this.setCellEditor(editor); + editor.addCellEditorListener(new CellEditorListener() { + @Override + public void editingStopped(ChangeEvent e) { + String data = editor.getCellEditorValue().toString(); + TreePath path = getSelectionPath(); + + Object realPath = path.getLastPathComponent(); + if (realPath != null && realPath instanceof DefaultMutableTreeNode && data != null) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) realPath; + TreeNode parentNode = node.getParent(); + if (parentNode == null) + return; + boolean allowEdit = true; + for (int i = 0; i < parentNode.getChildCount(); i++) { + TreeNode childNode = parentNode.getChildAt(i); + if (childNode != null && childNode.toString().equals(data) && childNode != node) { + allowEdit = false; + break; + } + } + if (allowEdit && renameSelectionListener != null) { + Object prevData = node.getUserObject(); + Object objectData = node.getUserObject() instanceof ClassEntry ? new ClassEntry(((ClassEntry) prevData).getPackageName() + "/" + data) : data; + try { + renameSelectionListener.onSelectionRename(node.getUserObject(), objectData, node); + node.setUserObject(objectData); // Make sure that it's modified + } catch (IllegalNameException ex) { + JOptionPane.showOptionDialog(gui.getFrame(), ex.getMessage(), "Enigma - Error", JOptionPane.OK_OPTION, + JOptionPane.ERROR_MESSAGE, null, new String[]{"Ok"}, "OK"); + editor.cancelCellEditing(); + } + } else + editor.cancelCellEditing(); + } + + } + + @Override + public void editingCanceled(ChangeEvent e) { + // NOP + } + }); + // init defaults + this.selectionListener = null; + this.renameSelectionListener = null; + } + + public boolean isDuplicate(Object[] nodes, String data) { + int count = 0; + + for (Object node : nodes) { + if (node.toString().equals(data)) { + count++; + if (count == 2) + return true; + } + } + return false; + } + + public void setSelectionListener(ClassSelectionListener val) { + this.selectionListener = val; + } + + public void setRenameSelectionListener(RenameSelectionListener renameSelectionListener) { + this.renameSelectionListener = renameSelectionListener; + } + + public void setClasses(Collection classEntries) { + displayedObfToDeobf.clear(); + + List state = getExpansionState(this); + if (classEntries == null) { + setModel(null); + return; + } + + Translator translator = controller.project.getMapper().getDeobfuscator(); + + // build the package names + Map packages = Maps.newHashMap(); + for (ClassEntry obfClass : classEntries) { + ClassEntry deobfClass = translator.translate(obfClass); + packages.put(deobfClass.getPackageName(), null); + } + + // sort the packages + List sortedPackageNames = Lists.newArrayList(packages.keySet()); + sortedPackageNames.sort((a, b) -> + { + // I can never keep this rule straight when writing these damn things... + // a < b => -1, a == b => 0, a > b => +1 + + if (b == null || a == null) { + return 0; + } + + String[] aparts = a.split("/"); + String[] bparts = b.split("/"); + for (int i = 0; true; i++) { + if (i >= aparts.length) { + return -1; + } else if (i >= bparts.length) { + return 1; + } + + int result = aparts[i].compareTo(bparts[i]); + if (result != 0) { + return result; + } + } + }); + + // create the rootNodes node and the package nodes + rootNodes = new DefaultMutableTreeNode(); + for (String packageName : sortedPackageNames) { + ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName); + packages.put(packageName, node); + rootNodes.add(node); + } + + // put the classes into packages + Multimap packagedClassEntries = ArrayListMultimap.create(); + for (ClassEntry obfClass : classEntries) { + ClassEntry deobfClass = translator.translate(obfClass); + packagedClassEntries.put(deobfClass.getPackageName(), obfClass); + } + + // build the class nodes + for (String packageName : packagedClassEntries.keySet()) { + // sort the class entries + List classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName)); + classEntriesInPackage.sort((o1, o2) -> comparator.compare(translator.translate(o1), translator.translate(o2))); + + // create the nodes in order + for (ClassEntry obfClass : classEntriesInPackage) { + ClassEntry deobfClass = translator.translate(obfClass); + ClassSelectorPackageNode node = packages.get(packageName); + ClassSelectorClassNode classNode = new ClassSelectorClassNode(obfClass, deobfClass); + displayedObfToDeobf.put(obfClass, deobfClass); + node.add(classNode); + } + } + + // finally, update the tree control + setModel(new DefaultTreeModel(rootNodes)); + + restoreExpansionState(this, state); + } + + public ClassEntry getSelectedClass() { + if (!isSelectionEmpty()) { + Object selectedNode = getSelectionPath().getLastPathComponent(); + if (selectedNode instanceof ClassSelectorClassNode) { + ClassSelectorClassNode classNode = (ClassSelectorClassNode) selectedNode; + return classNode.getClassEntry(); + } + } + return null; + } + + public String getSelectedPackage() { + if (!isSelectionEmpty()) { + Object selectedNode = getSelectionPath().getLastPathComponent(); + if (selectedNode instanceof ClassSelectorPackageNode) { + ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode) selectedNode; + return packageNode.getPackageName(); + } else if (selectedNode instanceof ClassSelectorClassNode) { + ClassSelectorClassNode classNode = (ClassSelectorClassNode) selectedNode; + return classNode.getClassEntry().getPackageName(); + } + } + return null; + } + + public boolean isDescendant(TreePath path1, TreePath path2) { + int count1 = path1.getPathCount(); + int count2 = path2.getPathCount(); + if (count1 <= count2) { + return false; + } + while (count1 != count2) { + path1 = path1.getParentPath(); + count1--; + } + return path1.equals(path2); + } + + public enum State { + EXPANDED, + SELECTED + } + + public static class StateEntry { + public final State state; + public final TreePath path; + + public StateEntry(State state, TreePath path) { + this.state = state; + this.path = path; + } + } + + public List getExpansionState(JTree tree) { + List state = new ArrayList<>(); + int rowCount = tree.getRowCount(); + for (int i = 0; i < rowCount; i++) { + TreePath path = tree.getPathForRow(i); + if (tree.isPathSelected(path)) { + state.add(new StateEntry(State.SELECTED, path)); + } + if (tree.isExpanded(path)) { + state.add(new StateEntry(State.EXPANDED, path)); + } + } + return state; + } + + public void restoreExpansionState(JTree tree, List expansionState) { + tree.clearSelection(); + + for (StateEntry entry : expansionState) { + switch (entry.state) { + case SELECTED: + tree.addSelectionPath(entry.path); + break; + case EXPANDED: + tree.expandPath(entry.path); + break; + } + } + } + + public List packageNodes() { + List nodes = Lists.newArrayList(); + DefaultMutableTreeNode root = (DefaultMutableTreeNode) getModel().getRoot(); + Enumeration children = root.children(); + while (children.hasMoreElements()) { + ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode) children.nextElement(); + nodes.add(packageNode); + } + return nodes; + } + + public List classNodes(ClassSelectorPackageNode packageNode) { + List nodes = Lists.newArrayList(); + Enumeration children = packageNode.children(); + while (children.hasMoreElements()) { + ClassSelectorClassNode classNode = (ClassSelectorClassNode) children.nextElement(); + nodes.add(classNode); + } + return nodes; + } + + public void expandPackage(String packageName) { + if (packageName == null) { + return; + } + for (ClassSelectorPackageNode packageNode : packageNodes()) { + if (packageNode.getPackageName().equals(packageName)) { + expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode})); + return; + } + } + } + + public void expandAll() { + for (ClassSelectorPackageNode packageNode : packageNodes()) { + expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode})); + } + } + + public ClassEntry getFirstClass() { + ClassSelectorPackageNode packageNode = packageNodes().get(0); + if (packageNode != null) { + ClassSelectorClassNode classNode = classNodes(packageNode).get(0); + if (classNode != null) { + return classNode.getClassEntry(); + } + } + return null; + } + + public ClassSelectorPackageNode getPackageNode(ClassEntry entry) { + String packageName = entry.getPackageName(); + if (packageName == null) { + packageName = "(none)"; + } + for (ClassSelectorPackageNode packageNode : packageNodes()) { + if (packageNode.getPackageName().equals(packageName)) { + return packageNode; + } + } + return null; + } + + @Nullable + public ClassEntry getDisplayedDeobf(ClassEntry obfEntry) { + return displayedObfToDeobf.get(obfEntry); + } + + public ClassSelectorPackageNode getPackageNode(ClassSelector selector, ClassEntry entry) { + ClassSelectorPackageNode packageNode = getPackageNode(entry); + + if (selector != null && packageNode == null && selector.getPackageNode(entry) != null) + return selector.getPackageNode(entry); + return packageNode; + } + + public ClassEntry getNextClass(ClassEntry entry) { + boolean foundIt = false; + for (ClassSelectorPackageNode packageNode : packageNodes()) { + if (!foundIt) { + // skip to the package with our target in it + if (packageNode.getPackageName().equals(entry.getPackageName())) { + for (ClassSelectorClassNode classNode : classNodes(packageNode)) { + if (!foundIt) { + if (classNode.getClassEntry().equals(entry)) { + foundIt = true; + } + } else { + // return the next class + return classNode.getClassEntry(); + } + } + } + } else { + // return the next class + ClassSelectorClassNode classNode = classNodes(packageNode).get(0); + if (classNode != null) { + return classNode.getClassEntry(); + } + } + } + return null; + } + + public void setSelectionClass(ClassEntry classEntry) { + expandPackage(classEntry.getPackageName()); + for (ClassSelectorPackageNode packageNode : packageNodes()) { + for (ClassSelectorClassNode classNode : classNodes(packageNode)) { + if (classNode.getClassEntry().equals(classEntry)) { + TreePath path = new TreePath(new Object[]{getModel().getRoot(), packageNode, classNode}); + setSelectionPath(path); + scrollPathToVisible(path); + } + } + } + } + + public void removeNode(ClassSelectorPackageNode packageNode, ClassEntry entry) { + DefaultTreeModel model = (DefaultTreeModel) getModel(); + + if (packageNode == null) + return; + + for (int i = 0; i < packageNode.getChildCount(); i++) { + DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) packageNode.getChildAt(i); + if (childNode.getUserObject() instanceof ClassEntry && childNode.getUserObject().equals(entry)) { + model.removeNodeFromParent(childNode); + if (childNode instanceof ClassSelectorClassNode) { + displayedObfToDeobf.remove(((ClassSelectorClassNode) childNode).getObfEntry()); + } + break; + } + } + } + + public void removeNodeIfEmpty(ClassSelectorPackageNode packageNode) { + if (packageNode != null && packageNode.getChildCount() == 0) + ((DefaultTreeModel) getModel()).removeNodeFromParent(packageNode); + } + + public void moveClassIn(ClassEntry classEntry) { + removeEntry(classEntry); + insertNode(classEntry); + } + + public void moveClassOut(ClassEntry classEntry) { + removeEntry(classEntry); + } + + private void removeEntry(ClassEntry classEntry) { + ClassEntry previousDeobf = displayedObfToDeobf.get(classEntry); + if (previousDeobf != null) { + ClassSelectorPackageNode packageNode = getPackageNode(previousDeobf); + removeNode(packageNode, previousDeobf); + removeNodeIfEmpty(packageNode); + } + } + + public ClassSelectorPackageNode getOrCreatePackage(ClassEntry entry) { + DefaultTreeModel model = (DefaultTreeModel) getModel(); + ClassSelectorPackageNode newPackageNode = getPackageNode(entry); + if (newPackageNode == null) { + newPackageNode = new ClassSelectorPackageNode(entry.getPackageName()); + model.insertNodeInto(newPackageNode, (MutableTreeNode) model.getRoot(), getPlacementIndex(newPackageNode)); + } + return newPackageNode; + } + + public void insertNode(ClassEntry obfEntry) { + ClassEntry deobfEntry = controller.project.getMapper().deobfuscate(obfEntry); + ClassSelectorPackageNode packageNode = getOrCreatePackage(deobfEntry); + + DefaultTreeModel model = (DefaultTreeModel) getModel(); + ClassSelectorClassNode classNode = new ClassSelectorClassNode(obfEntry, deobfEntry); + model.insertNodeInto(classNode, packageNode, getPlacementIndex(packageNode, classNode)); + + displayedObfToDeobf.put(obfEntry, deobfEntry); + } + + public void reload() { + DefaultTreeModel model = (DefaultTreeModel) getModel(); + model.reload(rootNodes); + } + + private int getPlacementIndex(ClassSelectorPackageNode newPackageNode, ClassSelectorClassNode classNode) { + List classNodes = classNodes(newPackageNode); + classNodes.add(classNode); + classNodes.sort((a, b) -> comparator.compare(a.getClassEntry(), b.getClassEntry())); + for (int i = 0; i < classNodes.size(); i++) + if (classNodes.get(i) == classNode) + return i; + + return 0; + } + + private int getPlacementIndex(ClassSelectorPackageNode newPackageNode) { + List packageNodes = packageNodes(); + if (!packageNodes.contains(newPackageNode)) { + packageNodes.add(newPackageNode); + packageNodes.sort(Comparator.comparing(ClassSelectorPackageNode::toString)); + } + + for (int i = 0; i < packageNodes.size(); i++) + if (packageNodes.get(i) == newPackageNode) + return i; + + return 0; + } + + public interface ClassSelectionListener { + void onSelectClass(ClassEntry classEntry); + } + + public interface RenameSelectionListener { + void onSelectionRename(Object prevData, Object data, DefaultMutableTreeNode node); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/CodeReader.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/CodeReader.java new file mode 100644 index 00000000..356656b9 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/CodeReader.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.gui; + +import cuchaz.enigma.source.Token; + +import javax.swing.*; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Highlighter.HighlightPainter; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class CodeReader extends JEditorPane { + private static final long serialVersionUID = 3673180950485748810L; + + // HACKHACK: someday we can update the main GUI to use this code reader + public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) { + + // set the caret position to the token + Document document = editor.getDocument(); + int clampedPosition = Math.min(Math.max(token.start, 0), document.getLength()); + + editor.setCaretPosition(clampedPosition); + editor.grabFocus(); + + try { + // make sure the token is visible in the scroll window + Rectangle start = editor.modelToView(token.start); + Rectangle end = editor.modelToView(token.end); + final Rectangle show = start.union(end); + show.grow(start.width * 10, start.height * 6); + SwingUtilities.invokeLater(() -> editor.scrollRectToVisible(show)); + } catch (BadLocationException ex) { + throw new Error(ex); + } + + // highlight the token momentarily + final Timer timer = new Timer(200, new ActionListener() { + private int counter = 0; + private Object highlight = null; + + @Override + public void actionPerformed(ActionEvent event) { + if (counter % 2 == 0) { + try { + highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter); + } catch (BadLocationException ex) { + // don't care + } + } else if (highlight != null) { + editor.getHighlighter().removeHighlight(highlight); + } + + if (counter++ > 6) { + Timer timer = (Timer) event.getSource(); + timer.stop(); + } + } + }); + timer.start(); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/ConnectionState.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/ConnectionState.java new file mode 100644 index 00000000..db6590de --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/ConnectionState.java @@ -0,0 +1,7 @@ +package cuchaz.enigma.gui; + +public enum ConnectionState { + NOT_CONNECTED, + HOSTING, + CONNECTED, +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java new file mode 100644 index 00000000..aca5d724 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java @@ -0,0 +1,160 @@ +package cuchaz.enigma.gui; + +import cuchaz.enigma.EnigmaProject; +import cuchaz.enigma.EnigmaServices; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.source.Token; +import cuchaz.enigma.api.service.NameProposalService; +import cuchaz.enigma.gui.highlight.TokenHighlightType; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.source.SourceRemapper; +import cuchaz.enigma.translation.LocalNameGenerator; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryRemapper; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; +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.LocalVariableDefEntry; + +import javax.annotation.Nullable; +import java.util.*; + +public class DecompiledClassSource { + private final ClassEntry classEntry; + + private final SourceIndex obfuscatedIndex; + private SourceIndex remappedIndex; + + private final Map> highlightedTokens = new EnumMap<>(TokenHighlightType.class); + + public DecompiledClassSource(ClassEntry classEntry, SourceIndex index) { + this.classEntry = classEntry; + this.obfuscatedIndex = index; + this.remappedIndex = index; + } + + public static DecompiledClassSource text(ClassEntry classEntry, String text) { + return new DecompiledClassSource(classEntry, new SourceIndex(text)); + } + + public void remapSource(EnigmaProject project, Translator translator) { + highlightedTokens.clear(); + + SourceRemapper remapper = new SourceRemapper(obfuscatedIndex.getSource(), obfuscatedIndex.referenceTokens()); + + SourceRemapper.Result remapResult = remapper.remap((token, movedToken) -> remapToken(project, token, movedToken, translator)); + remappedIndex = obfuscatedIndex.remapTo(remapResult); + } + + private String remapToken(EnigmaProject project, Token token, Token movedToken, Translator translator) { + EntryReference, Entry> reference = obfuscatedIndex.getReference(token); + + Entry entry = reference.getNameableEntry(); + Entry translatedEntry = translator.translate(entry); + + if (project.isRenamable(reference)) { + if (isDeobfuscated(entry, translatedEntry)) { + highlightToken(movedToken, TokenHighlightType.DEOBFUSCATED); + return translatedEntry.getSourceRemapName(); + } else { + Optional proposedName = proposeName(project, entry); + if (proposedName.isPresent()) { + highlightToken(movedToken, TokenHighlightType.PROPOSED); + return proposedName.get(); + } + + highlightToken(movedToken, TokenHighlightType.OBFUSCATED); + } + } + + String defaultName = generateDefaultName(translatedEntry); + if (defaultName != null) { + return defaultName; + } + + return null; + } + + private Optional proposeName(EnigmaProject project, Entry entry) { + EnigmaServices services = project.getEnigma().getServices(); + + return services.get(NameProposalService.TYPE).stream().flatMap(nameProposalService -> { + EntryRemapper mapper = project.getMapper(); + Collection> resolved = mapper.getObfResolver().resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT); + + return resolved.stream() + .map(e -> nameProposalService.proposeName(e, mapper)) + .filter(Optional::isPresent) + .map(Optional::get); + }).findFirst(); + } + + @Nullable + private String generateDefaultName(Entry entry) { + if (entry instanceof LocalVariableDefEntry) { + LocalVariableDefEntry localVariable = (LocalVariableDefEntry) entry; + + int index = localVariable.getIndex(); + if (localVariable.isArgument()) { + List arguments = localVariable.getParent().getDesc().getArgumentDescs(); + return LocalNameGenerator.generateArgumentName(index, localVariable.getDesc(), arguments); + } else { + return LocalNameGenerator.generateLocalVariableName(index, localVariable.getDesc()); + } + } + + return null; + } + + private boolean isDeobfuscated(Entry entry, Entry translatedEntry) { + return !entry.getName().equals(translatedEntry.getName()); + } + + public ClassEntry getEntry() { + return classEntry; + } + + public SourceIndex getIndex() { + return remappedIndex; + } + + public Map> getHighlightedTokens() { + return highlightedTokens; + } + + private void highlightToken(Token token, TokenHighlightType highlightType) { + highlightedTokens.computeIfAbsent(highlightType, t -> new ArrayList<>()).add(token); + } + + public int getObfuscatedOffset(int deobfOffset) { + return getOffset(remappedIndex, obfuscatedIndex, deobfOffset); + } + + public int getDeobfuscatedOffset(int obfOffset) { + return getOffset(obfuscatedIndex, remappedIndex, obfOffset); + } + + private static int getOffset(SourceIndex fromIndex, SourceIndex toIndex, int fromOffset) { + int relativeOffset = 0; + + Iterator fromTokenItr = fromIndex.referenceTokens().iterator(); + Iterator toTokenItr = toIndex.referenceTokens().iterator(); + while (fromTokenItr.hasNext() && toTokenItr.hasNext()) { + Token fromToken = fromTokenItr.next(); + Token toToken = toTokenItr.next(); + if (fromToken.end > fromOffset) { + break; + } + + relativeOffset = toToken.end - fromToken.end; + } + + return fromOffset + relativeOffset; + } + + @Override + public String toString() { + return remappedIndex.getSource(); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/EnigmaQuickFindDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/EnigmaQuickFindDialog.java new file mode 100644 index 00000000..c912be3a --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/EnigmaQuickFindDialog.java @@ -0,0 +1,90 @@ +package cuchaz.enigma.gui; + +import de.sciss.syntaxpane.actions.DocumentSearchData; +import de.sciss.syntaxpane.actions.gui.QuickFindDialog; + +import javax.swing.*; +import javax.swing.text.JTextComponent; +import java.awt.*; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public class EnigmaQuickFindDialog extends QuickFindDialog { + public EnigmaQuickFindDialog(JTextComponent target) { + super(target, DocumentSearchData.getFromEditor(target)); + + JToolBar toolBar = getToolBar(); + JTextField textField = getTextField(toolBar); + + textField.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + super.keyPressed(e); + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + JToolBar toolBar = getToolBar(); + boolean next = !e.isShiftDown(); + JButton button = next ? getNextButton(toolBar) : getPrevButton(toolBar); + button.doClick(); + } + } + }); + } + + @Override + public void showFor(JTextComponent target) { + String selectedText = target.getSelectedText(); + + try { + super.showFor(target); + } catch (Exception e) { + e.printStackTrace(); + return; + } + + Container view = target.getParent(); + Point loc = new Point(0, view.getHeight() - getSize().height); + setLocationRelativeTo(view); + SwingUtilities.convertPointToScreen(loc, view); + setLocation(loc); + + JToolBar toolBar = getToolBar(); + JTextField textField = getTextField(toolBar); + + if (selectedText != null) { + textField.setText(selectedText); + } + + textField.selectAll(); + } + + private JToolBar getToolBar() { + return components(getContentPane(), JToolBar.class).findFirst().orElse(null); + } + + private JTextField getTextField(JToolBar toolBar) { + return components(toolBar, JTextField.class).findFirst().orElse(null); + } + + private JButton getNextButton(JToolBar toolBar) { + Stream buttons = components(toolBar, JButton.class); + return buttons.skip(1).findFirst().orElse(null); + } + + private JButton getPrevButton(JToolBar toolBar) { + Stream buttons = components(toolBar, JButton.class); + return buttons.findFirst().orElse(null); + } + + private static Stream components(Container container) { + return IntStream.range(0, container.getComponentCount()) + .mapToObj(container::getComponent); + } + + private static Stream components(Container container, Class type) { + return components(container) + .filter(type::isInstance) + .map(type::cast); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java new file mode 100644 index 00000000..2f08a269 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java @@ -0,0 +1,44 @@ +package cuchaz.enigma.gui; + +import cuchaz.enigma.gui.config.Config; +import de.sciss.syntaxpane.components.LineNumbersRuler; +import de.sciss.syntaxpane.syntaxkits.JavaSyntaxKit; +import de.sciss.syntaxpane.util.Configuration; + +public class EnigmaSyntaxKit extends JavaSyntaxKit { + private static Configuration configuration = null; + + @Override + public Configuration getConfig() { + if(configuration == null){ + initConfig(super.getConfig(JavaSyntaxKit.class)); + } + return configuration; + } + + public void initConfig(Configuration baseConfig){ + configuration = baseConfig; + //See de.sciss.syntaxpane.TokenType + configuration.put("Style.KEYWORD", Config.getInstance().highlightColor + ", 0"); + configuration.put("Style.KEYWORD2", Config.getInstance().highlightColor + ", 3"); + configuration.put("Style.STRING", Config.getInstance().stringColor + ", 0"); + configuration.put("Style.STRING2", Config.getInstance().stringColor + ", 1"); + configuration.put("Style.NUMBER", Config.getInstance().numberColor + ", 1"); + configuration.put("Style.OPERATOR", Config.getInstance().operatorColor + ", 0"); + configuration.put("Style.DELIMITER", Config.getInstance().delimiterColor + ", 1"); + configuration.put("Style.TYPE", Config.getInstance().typeColor + ", 2"); + configuration.put("Style.TYPE2", Config.getInstance().typeColor + ", 1"); + configuration.put("Style.IDENTIFIER", Config.getInstance().identifierColor + ", 0"); + configuration.put("Style.DEFAULT", Config.getInstance().defaultTextColor + ", 0"); + configuration.put(LineNumbersRuler.PROPERTY_BACKGROUND, Config.getInstance().lineNumbersBackground + ""); + configuration.put(LineNumbersRuler.PROPERTY_FOREGROUND, Config.getInstance().lineNumbersForeground + ""); + configuration.put(LineNumbersRuler.PROPERTY_CURRENT_BACK, Config.getInstance().lineNumbersSelected + ""); + configuration.put("RightMarginColumn", "999"); //No need to have a right margin, if someone wants it add a config + + configuration.put("Action.quick-find", "cuchaz.enigma.gui.QuickFindAction, menu F"); + } + + public static void invalidate(){ + configuration = null; + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/ExceptionIgnorer.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/ExceptionIgnorer.java new file mode 100644 index 00000000..6246192c --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/ExceptionIgnorer.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.gui; + +public class ExceptionIgnorer { + + public static boolean shouldIgnore(Throwable t) { + + // is this that pesky concurrent access bug in the highlight painter system? + // (ancient ui code is ancient) + if (t instanceof ArrayIndexOutOfBoundsException) { + StackTraceElement[] stackTrace = t.getStackTrace(); + if (stackTrace.length > 1) { + + // does this stack frame match javax.swing.text.DefaultHighlighter.paint*() ? + StackTraceElement frame = stackTrace[1]; + if (frame.getClassName().equals("javax.swing.text.DefaultHighlighter") && frame.getMethodName().startsWith("paint")) { + return true; + } + } + } + + return false; + } + +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java new file mode 100644 index 00000000..2ed1010f --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java @@ -0,0 +1,1058 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.gui; + +import java.awt.*; +import java.awt.event.*; +import java.nio.file.Path; +import java.util.List; +import java.util.*; +import java.util.function.Function; + +import javax.swing.*; +import javax.swing.text.BadLocationException; +import javax.swing.text.Highlighter; +import javax.swing.tree.*; + +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import cuchaz.enigma.Enigma; +import cuchaz.enigma.EnigmaProfile; +import cuchaz.enigma.analysis.*; +import cuchaz.enigma.gui.config.Config; +import cuchaz.enigma.gui.config.Themes; +import cuchaz.enigma.gui.dialog.CrashDialog; +import cuchaz.enigma.gui.dialog.JavadocDialog; +import cuchaz.enigma.gui.dialog.SearchDialog; +import cuchaz.enigma.gui.elements.CollapsibleTabbedPane; +import cuchaz.enigma.gui.elements.MenuBar; +import cuchaz.enigma.gui.elements.PopupMenuBar; +import cuchaz.enigma.gui.filechooser.FileChooserAny; +import cuchaz.enigma.gui.filechooser.FileChooserFolder; +import cuchaz.enigma.gui.highlight.BoxHighlightPainter; +import cuchaz.enigma.gui.highlight.SelectionHighlightPainter; +import cuchaz.enigma.gui.highlight.TokenHighlightType; +import cuchaz.enigma.gui.panels.PanelDeobf; +import cuchaz.enigma.gui.panels.PanelEditor; +import cuchaz.enigma.gui.panels.PanelIdentifier; +import cuchaz.enigma.gui.panels.PanelObf; +import cuchaz.enigma.gui.util.GuiUtil; +import cuchaz.enigma.gui.util.History; +import cuchaz.enigma.network.packet.*; +import cuchaz.enigma.source.Token; +import cuchaz.enigma.translation.mapping.IllegalNameException; +import cuchaz.enigma.translation.mapping.*; +import cuchaz.enigma.translation.representation.entry.*; +import cuchaz.enigma.network.Message; +import cuchaz.enigma.gui.util.ScaleUtil; +import cuchaz.enigma.utils.I18n; +import de.sciss.syntaxpane.DefaultSyntaxKit; + +public class Gui { + + public final PopupMenuBar popupMenu; + private final PanelObf obfPanel; + private final PanelDeobf deobfPanel; + + private final MenuBar menuBar; + // state + public History, Entry>> referenceHistory; + public EntryReference, Entry> renamingReference; + public EntryReference, Entry> cursorReference; + private boolean shouldNavigateOnClick; + private ConnectionState connectionState; + private boolean isJarOpen; + + public FileDialog jarFileChooser; + public FileDialog tinyMappingsFileChooser; + public SearchDialog searchDialog; + public JFileChooser enigmaMappingsFileChooser; + public JFileChooser exportSourceFileChooser; + public FileDialog exportJarFileChooser; + private GuiController controller; + private JFrame frame; + public Config.LookAndFeel editorFeel; + public PanelEditor editor; + public JScrollPane sourceScroller; + private JPanel classesPanel; + private JSplitPane splitClasses; + private PanelIdentifier infoPanel; + public Map boxHighlightPainters; + private SelectionHighlightPainter selectionHighlightPainter; + private JTree inheritanceTree; + private JTree implementationsTree; + private JTree callsTree; + private JList tokens; + private JTabbedPane tabs; + + private JSplitPane splitRight; + private JSplitPane logSplit; + private CollapsibleTabbedPane logTabs; + private JList users; + private DefaultListModel userModel; + private JScrollPane messageScrollPane; + private JList messages; + private DefaultListModel messageModel; + private JTextField chatBox; + + private JPanel statusBar; + private JLabel connectionStatusLabel; + private JLabel statusLabel; + + public JTextField renameTextField; + public JTextArea javadocTextArea; + + public void setEditorTheme(Config.LookAndFeel feel) { + if (editor != null && (editorFeel == null || editorFeel != feel)) { + editor.updateUI(); + editor.setBackground(new Color(Config.getInstance().editorBackground)); + if (editorFeel != null) { + getController().refreshCurrentClass(); + } + + editorFeel = feel; + } + } + + public Gui(EnigmaProfile profile) { + Config.getInstance().lookAndFeel.setGlobalLAF(); + + // init frame + this.frame = new JFrame(Enigma.NAME); + final Container pane = this.frame.getContentPane(); + pane.setLayout(new BorderLayout()); + + if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) { + // install a global exception handler to the event thread + CrashDialog.init(this.frame); + Thread.setDefaultUncaughtExceptionHandler((thread, t) -> { + t.printStackTrace(System.err); + if (!ExceptionIgnorer.shouldIgnore(t)) { + CrashDialog.show(t); + } + }); + } + + this.controller = new GuiController(this, profile); + + Themes.updateTheme(this); + + // init file choosers + this.jarFileChooser = new FileDialog(getFrame(), I18n.translate("menu.file.jar.open"), FileDialog.LOAD); + + this.tinyMappingsFileChooser = new FileDialog(getFrame(), "Open tiny Mappings", FileDialog.LOAD); + this.enigmaMappingsFileChooser = new FileChooserAny(); + this.exportSourceFileChooser = new FileChooserFolder(); + this.exportJarFileChooser = new FileDialog(getFrame(), I18n.translate("menu.file.export.jar"), FileDialog.SAVE); + + this.obfPanel = new PanelObf(this); + this.deobfPanel = new PanelDeobf(this); + + // set up classes panel (don't add the splitter yet) + splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, this.obfPanel, this.deobfPanel); + splitClasses.setResizeWeight(0.3); + this.classesPanel = new JPanel(); + this.classesPanel.setLayout(new BorderLayout()); + this.classesPanel.setPreferredSize(ScaleUtil.getDimension(250, 0)); + + // init info panel + infoPanel = new PanelIdentifier(this); + infoPanel.clearReference(); + + // init editor + selectionHighlightPainter = new SelectionHighlightPainter(); + this.editor = new PanelEditor(this); + this.sourceScroller = new JScrollPane(this.editor); + this.editor.setContentType("text/enigma-sources"); + this.editor.setBackground(new Color(Config.getInstance().editorBackground)); + DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit(); + kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker"); + + // init editor popup menu + this.popupMenu = new PopupMenuBar(this); + this.editor.setComponentPopupMenu(this.popupMenu); + + // init inheritance panel + inheritanceTree = new JTree(); + inheritanceTree.setModel(null); + inheritanceTree.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + if (event.getClickCount() >= 2) { + // get the selected node + TreePath path = inheritanceTree.getSelectionPath(); + if (path == null) { + return; + } + + Object node = path.getLastPathComponent(); + if (node instanceof ClassInheritanceTreeNode) { + ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode) node; + controller.navigateTo(new ClassEntry(classNode.getObfClassName())); + } else if (node instanceof MethodInheritanceTreeNode) { + MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode) node; + if (methodNode.isImplemented()) { + controller.navigateTo(methodNode.getMethodEntry()); + } + } + } + } + }); + TreeCellRenderer cellRenderer = inheritanceTree.getCellRenderer(); + inheritanceTree.setCellRenderer(new MethodTreeCellRenderer(cellRenderer)); + + JPanel inheritancePanel = new JPanel(); + inheritancePanel.setLayout(new BorderLayout()); + inheritancePanel.add(new JScrollPane(inheritanceTree)); + + // init implementations panel + implementationsTree = new JTree(); + implementationsTree.setModel(null); + implementationsTree.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + if (event.getClickCount() >= 2) { + // get the selected node + TreePath path = implementationsTree.getSelectionPath(); + if (path == null) { + return; + } + + Object node = path.getLastPathComponent(); + if (node instanceof ClassImplementationsTreeNode) { + ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode) node; + controller.navigateTo(classNode.getClassEntry()); + } else if (node instanceof MethodImplementationsTreeNode) { + MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode) node; + controller.navigateTo(methodNode.getMethodEntry()); + } + } + } + }); + JPanel implementationsPanel = new JPanel(); + implementationsPanel.setLayout(new BorderLayout()); + implementationsPanel.add(new JScrollPane(implementationsTree)); + + // init call panel + callsTree = new JTree(); + callsTree.setModel(null); + callsTree.addMouseListener(new MouseAdapter() { + @SuppressWarnings("unchecked") + @Override + public void mouseClicked(MouseEvent event) { + if (event.getClickCount() >= 2) { + // get the selected node + TreePath path = callsTree.getSelectionPath(); + if (path == null) { + return; + } + + Object node = path.getLastPathComponent(); + if (node instanceof ReferenceTreeNode) { + ReferenceTreeNode, Entry> referenceNode = ((ReferenceTreeNode, Entry>) node); + if (referenceNode.getReference() != null) { + controller.navigateTo(referenceNode.getReference()); + } else { + controller.navigateTo(referenceNode.getEntry()); + } + } + } + } + }); + tokens = new JList<>(); + tokens.setCellRenderer(new TokenListCellRenderer(this.controller)); + tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + tokens.setLayoutOrientation(JList.VERTICAL); + tokens.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent event) { + if (event.getClickCount() == 2) { + Token selected = tokens.getSelectedValue(); + if (selected != null) { + showToken(selected); + } + } + } + }); + tokens.setPreferredSize(ScaleUtil.getDimension(0, 200)); + tokens.setMinimumSize(ScaleUtil.getDimension(0, 200)); + JSplitPane callPanel = new JSplitPane( + JSplitPane.VERTICAL_SPLIT, + true, + new JScrollPane(callsTree), + new JScrollPane(tokens) + ); + callPanel.setResizeWeight(1); // let the top side take all the slack + callPanel.resetToPreferredSizes(); + + // layout controls + JPanel centerPanel = new JPanel(); + centerPanel.setLayout(new BorderLayout()); + centerPanel.add(infoPanel, BorderLayout.NORTH); + centerPanel.add(sourceScroller, BorderLayout.CENTER); + tabs = new JTabbedPane(); + tabs.setPreferredSize(ScaleUtil.getDimension(250, 0)); + tabs.addTab(I18n.translate("info_panel.tree.inheritance"), inheritancePanel); + tabs.addTab(I18n.translate("info_panel.tree.implementations"), implementationsPanel); + tabs.addTab(I18n.translate("info_panel.tree.calls"), callPanel); + logTabs = new CollapsibleTabbedPane(JTabbedPane.BOTTOM); + userModel = new DefaultListModel<>(); + users = new JList<>(userModel); + messageModel = new DefaultListModel<>(); + messages = new JList<>(messageModel); + messages.setCellRenderer(new MessageListCellRenderer()); + JPanel messagePanel = new JPanel(new BorderLayout()); + messageScrollPane = new JScrollPane(this.messages); + messagePanel.add(messageScrollPane, BorderLayout.CENTER); + JPanel chatPanel = new JPanel(new BorderLayout()); + chatBox = new JTextField(); + AbstractAction sendListener = new AbstractAction("Send") { + @Override + public void actionPerformed(ActionEvent e) { + sendMessage(); + } + }; + chatBox.addActionListener(sendListener); + JButton chatSendButton = new JButton(sendListener); + chatPanel.add(chatBox, BorderLayout.CENTER); + chatPanel.add(chatSendButton, BorderLayout.EAST); + messagePanel.add(chatPanel, BorderLayout.SOUTH); + logTabs.addTab(I18n.translate("log_panel.users"), new JScrollPane(this.users)); + logTabs.addTab(I18n.translate("log_panel.messages"), messagePanel); + logSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, tabs, logTabs); + logSplit.setResizeWeight(0.5); + logSplit.resetToPreferredSizes(); + splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, this.logSplit); + splitRight.setResizeWeight(1); // let the left side take all the slack + splitRight.resetToPreferredSizes(); + JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight); + splitCenter.setResizeWeight(0); // let the right side take all the slack + pane.add(splitCenter, BorderLayout.CENTER); + + // init menus + this.menuBar = new MenuBar(this); + this.frame.setJMenuBar(this.menuBar); + + // init status bar + statusBar = new JPanel(new BorderLayout()); + statusBar.setBorder(BorderFactory.createLoweredBevelBorder()); + connectionStatusLabel = new JLabel(); + statusLabel = new JLabel(); + statusBar.add(statusLabel, BorderLayout.CENTER); + statusBar.add(connectionStatusLabel, BorderLayout.EAST); + pane.add(statusBar, BorderLayout.SOUTH); + + // init state + setConnectionState(ConnectionState.NOT_CONNECTED); + onCloseJar(); + + this.frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent event) { + close(); + } + }); + + // show the frame + pane.doLayout(); + this.frame.setSize(ScaleUtil.getDimension(1024, 576)); + this.frame.setMinimumSize(ScaleUtil.getDimension(640, 480)); + this.frame.setVisible(true); + this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + this.frame.setLocationRelativeTo(null); + } + + public JFrame getFrame() { + return this.frame; + } + + public GuiController getController() { + return this.controller; + } + + public void onStartOpenJar() { + this.classesPanel.removeAll(); + redraw(); + } + + public void onFinishOpenJar(String jarName) { + // update gui + this.frame.setTitle(Enigma.NAME + " - " + jarName); + this.classesPanel.removeAll(); + this.classesPanel.add(splitClasses); + setEditorText(null); + + // update menu + isJarOpen = true; + + updateUiState(); + redraw(); + } + + public void onCloseJar() { + + // update gui + this.frame.setTitle(Enigma.NAME); + setObfClasses(null); + setDeobfClasses(null); + setEditorText(null); + this.classesPanel.removeAll(); + + // update menu + isJarOpen = false; + setMappingsFile(null); + + updateUiState(); + redraw(); + } + + public void setObfClasses(Collection obfClasses) { + this.obfPanel.obfClasses.setClasses(obfClasses); + } + + public void setDeobfClasses(Collection deobfClasses) { + this.deobfPanel.deobfClasses.setClasses(deobfClasses); + } + + public void setMappingsFile(Path path) { + this.enigmaMappingsFileChooser.setSelectedFile(path != null ? path.toFile() : null); + updateUiState(); + } + + public void setEditorText(String source) { + this.editor.getHighlighter().removeAllHighlights(); + this.editor.setText(source); + } + + public void setSource(DecompiledClassSource source) { + editor.setText(source.toString()); + setHighlightedTokens(source.getHighlightedTokens()); + } + + public void showToken(final Token token) { + if (token == null) { + throw new IllegalArgumentException("Token cannot be null!"); + } + CodeReader.navigateToToken(this.editor, token, selectionHighlightPainter); + redraw(); + } + + public void showTokens(Collection tokens) { + Vector sortedTokens = new Vector<>(tokens); + Collections.sort(sortedTokens); + if (sortedTokens.size() > 1) { + // sort the tokens and update the tokens panel + this.tokens.setListData(sortedTokens); + this.tokens.setSelectedIndex(0); + } else { + this.tokens.setListData(new Vector<>()); + } + + // show the first token + showToken(sortedTokens.get(0)); + } + + public void setHighlightedTokens(Map> tokens) { + // remove any old highlighters + this.editor.getHighlighter().removeAllHighlights(); + + if (boxHighlightPainters != null) { + for (TokenHighlightType type : tokens.keySet()) { + BoxHighlightPainter painter = boxHighlightPainters.get(type); + if (painter != null) { + setHighlightedTokens(tokens.get(type), painter); + } + } + } + + redraw(); + } + + private void setHighlightedTokens(Iterable tokens, Highlighter.HighlightPainter painter) { + for (Token token : tokens) { + try { + this.editor.getHighlighter().addHighlight(token.start, token.end, painter); + } catch (BadLocationException ex) { + throw new IllegalArgumentException(ex); + } + } + } + + private void showCursorReference(EntryReference, Entry> reference) { + if (reference == null) { + infoPanel.clearReference(); + return; + } + + this.cursorReference = reference; + + EntryReference, Entry> translatedReference = controller.project.getMapper().deobfuscate(reference); + + infoPanel.removeAll(); + if (translatedReference.entry instanceof ClassEntry) { + showClassEntry((ClassEntry) translatedReference.entry); + } else if (translatedReference.entry instanceof FieldEntry) { + showFieldEntry((FieldEntry) translatedReference.entry); + } else if (translatedReference.entry instanceof MethodEntry) { + showMethodEntry((MethodEntry) translatedReference.entry); + } else if (translatedReference.entry instanceof LocalVariableEntry) { + showLocalVariableEntry((LocalVariableEntry) translatedReference.entry); + } else { + throw new Error("Unknown entry desc: " + translatedReference.entry.getClass().getName()); + } + + redraw(); + } + + private void showLocalVariableEntry(LocalVariableEntry entry) { + addNameValue(infoPanel, I18n.translate("info_panel.identifier.variable"), entry.getName()); + addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getContainingClass().getFullName()); + addNameValue(infoPanel, I18n.translate("info_panel.identifier.method"), entry.getParent().getName()); + addNameValue(infoPanel, I18n.translate("info_panel.identifier.index"), Integer.toString(entry.getIndex())); + } + + private void showClassEntry(ClassEntry entry) { + addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getFullName()); + addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry); + } + + private void showFieldEntry(FieldEntry entry) { + addNameValue(infoPanel, I18n.translate("info_panel.identifier.field"), entry.getName()); + addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getParent().getFullName()); + addNameValue(infoPanel, I18n.translate("info_panel.identifier.type_descriptor"), entry.getDesc().toString()); + addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry); + } + + private void showMethodEntry(MethodEntry entry) { + if (entry.isConstructor()) { + addNameValue(infoPanel, I18n.translate("info_panel.identifier.constructor"), entry.getParent().getFullName()); + } else { + addNameValue(infoPanel, I18n.translate("info_panel.identifier.method"), entry.getName()); + addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getParent().getFullName()); + } + addNameValue(infoPanel, I18n.translate("info_panel.identifier.method_descriptor"), entry.getDesc().toString()); + addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry); + } + + private void addNameValue(JPanel container, String name, String value) { + JPanel panel = new JPanel(); + panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); + + JLabel label = new JLabel(name + ":", JLabel.RIGHT); + label.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height))); + panel.add(label); + + panel.add(GuiUtil.unboldLabel(new JLabel(value, JLabel.LEFT))); + + container.add(panel); + } + + private JComboBox addModifierComboBox(JPanel container, String name, Entry entry) { + if (!getController().project.isRenamable(entry)) + return null; + JPanel panel = new JPanel(); + panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); + JLabel label = new JLabel(name + ":", JLabel.RIGHT); + label.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height))); + panel.add(label); + JComboBox combo = new JComboBox<>(AccessModifier.values()); + ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT); + combo.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height))); + + EntryMapping mapping = controller.project.getMapper().getDeobfMapping(entry); + if (mapping != null) { + combo.setSelectedIndex(mapping.getAccessModifier().ordinal()); + } else { + combo.setSelectedIndex(AccessModifier.UNCHANGED.ordinal()); + } + + combo.addItemListener(controller::modifierChange); + + panel.add(combo); + + container.add(panel); + + return combo; + } + + public void onCaretMove(int pos, boolean fromClick) { + if (controller.project == null) + return; + EntryRemapper mapper = controller.project.getMapper(); + Token token = this.controller.getToken(pos); + boolean isToken = token != null; + + cursorReference = this.controller.getReference(token); + Entry referenceEntry = cursorReference != null ? cursorReference.entry : null; + + if (referenceEntry != null && shouldNavigateOnClick && fromClick) { + shouldNavigateOnClick = false; + Entry navigationEntry = referenceEntry; + if (cursorReference.context == null) { + EntryResolver resolver = mapper.getObfResolver(); + navigationEntry = resolver.resolveFirstEntry(referenceEntry, ResolutionStrategy.RESOLVE_ROOT); + } + controller.navigateTo(navigationEntry); + return; + } + + boolean isClassEntry = isToken && referenceEntry instanceof ClassEntry; + boolean isFieldEntry = isToken && referenceEntry instanceof FieldEntry; + boolean isMethodEntry = isToken && referenceEntry instanceof MethodEntry && !((MethodEntry) referenceEntry).isConstructor(); + boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor(); + boolean isRenamable = isToken && this.controller.project.isRenamable(cursorReference); + + if (!isRenaming()) { + if (isToken) { + showCursorReference(cursorReference); + } else { + infoPanel.clearReference(); + } + } + + this.popupMenu.renameMenu.setEnabled(isRenamable); + this.popupMenu.editJavadocMenu.setEnabled(isRenamable); + this.popupMenu.showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry); + this.popupMenu.showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry); + this.popupMenu.showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry); + this.popupMenu.showCallsSpecificMenu.setEnabled(isMethodEntry); + this.popupMenu.openEntryMenu.setEnabled(isRenamable && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry)); + this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousReference()); + this.popupMenu.openNextMenu.setEnabled(this.controller.hasNextReference()); + this.popupMenu.toggleMappingMenu.setEnabled(isRenamable); + + if (isToken && !Objects.equals(referenceEntry, mapper.deobfuscate(referenceEntry))) { + this.popupMenu.toggleMappingMenu.setText(I18n.translate("popup_menu.reset_obfuscated")); + } else { + this.popupMenu.toggleMappingMenu.setText(I18n.translate("popup_menu.mark_deobfuscated")); + } + } + + public void startDocChange() { + EntryReference, Entry> curReference = cursorReference; + if (isRenaming()) { + finishRename(false); + } + renamingReference = curReference; + + // init the text box + javadocTextArea = new JTextArea(10, 40); + + EntryReference, Entry> translatedReference = controller.project.getMapper().deobfuscate(cursorReference); + javadocTextArea.setText(Strings.nullToEmpty(translatedReference.entry.getJavadocs())); + + JavadocDialog.init(frame, javadocTextArea, this::finishDocChange); + javadocTextArea.grabFocus(); + + redraw(); + } + + private void finishDocChange(JFrame ui, boolean saveName) { + String newName = javadocTextArea.getText(); + if (saveName) { + try { + this.controller.changeDocs(renamingReference, newName); + this.controller.sendPacket(new ChangeDocsC2SPacket(renamingReference.getNameableEntry(), newName)); + } catch (IllegalNameException ex) { + javadocTextArea.setBorder(BorderFactory.createLineBorder(Color.red, 1)); + javadocTextArea.setToolTipText(ex.getReason()); + GuiUtil.showToolTipNow(javadocTextArea); + return; + } + + ui.setVisible(false); + showCursorReference(cursorReference); + return; + } + + // abort the jd change + javadocTextArea = null; + ui.setVisible(false); + showCursorReference(cursorReference); + + this.editor.grabFocus(); + + redraw(); + } + + public void startRename() { + + // init the text box + renameTextField = new JTextField(); + + EntryReference, Entry> translatedReference = controller.project.getMapper().deobfuscate(cursorReference); + renameTextField.setText(translatedReference.getNameableName()); + + renameTextField.setPreferredSize(ScaleUtil.getDimension(360, ScaleUtil.invert(renameTextField.getPreferredSize().height))); + renameTextField.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent event) { + switch (event.getKeyCode()) { + case KeyEvent.VK_ENTER: + finishRename(true); + break; + + case KeyEvent.VK_ESCAPE: + finishRename(false); + break; + default: + break; + } + } + }); + + // find the label with the name and replace it with the text box + JPanel panel = (JPanel) infoPanel.getComponent(0); + panel.remove(panel.getComponentCount() - 1); + panel.add(renameTextField); + renameTextField.grabFocus(); + + int offset = renameTextField.getText().lastIndexOf('/') + 1; + // If it's a class and isn't in the default package, assume that it's deobfuscated. + if (translatedReference.getNameableEntry() instanceof ClassEntry && renameTextField.getText().contains("/") && offset != 0) + renameTextField.select(offset, renameTextField.getText().length()); + else + renameTextField.selectAll(); + + renamingReference = cursorReference; + + redraw(); + } + + private void finishRename(boolean saveName) { + String newName = renameTextField.getText(); + + if (saveName && newName != null && !newName.isEmpty()) { + try { + this.controller.rename(renamingReference, newName, true); + this.controller.sendPacket(new RenameC2SPacket(renamingReference.getNameableEntry(), newName, true)); + renameTextField = null; + } catch (IllegalNameException ex) { + renameTextField.setBorder(BorderFactory.createLineBorder(Color.red, 1)); + renameTextField.setToolTipText(ex.getReason()); + GuiUtil.showToolTipNow(renameTextField); + } + return; + } + + renameTextField = null; + + // abort the rename + showCursorReference(cursorReference); + + this.editor.grabFocus(); + + redraw(); + } + + private boolean isRenaming() { + return renameTextField != null; + } + + public void showInheritance() { + + if (cursorReference == null) { + return; + } + + inheritanceTree.setModel(null); + + if (cursorReference.entry instanceof ClassEntry) { + // get the class inheritance + ClassInheritanceTreeNode classNode = this.controller.getClassInheritance((ClassEntry) cursorReference.entry); + + // show the tree at the root + TreePath path = getPathToRoot(classNode); + inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); + inheritanceTree.expandPath(path); + inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); + } else if (cursorReference.entry instanceof MethodEntry) { + // get the method inheritance + MethodInheritanceTreeNode classNode = this.controller.getMethodInheritance((MethodEntry) cursorReference.entry); + + // show the tree at the root + TreePath path = getPathToRoot(classNode); + inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); + inheritanceTree.expandPath(path); + inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); + } + + tabs.setSelectedIndex(0); + + redraw(); + } + + public void showImplementations() { + + if (cursorReference == null) { + return; + } + + implementationsTree.setModel(null); + + DefaultMutableTreeNode node = null; + + // get the class implementations + if (cursorReference.entry instanceof ClassEntry) + node = this.controller.getClassImplementations((ClassEntry) cursorReference.entry); + else // get the method implementations + if (cursorReference.entry instanceof MethodEntry) + node = this.controller.getMethodImplementations((MethodEntry) cursorReference.entry); + + if (node != null) { + // show the tree at the root + TreePath path = getPathToRoot(node); + implementationsTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); + implementationsTree.expandPath(path); + implementationsTree.setSelectionRow(implementationsTree.getRowForPath(path)); + } + + tabs.setSelectedIndex(1); + + redraw(); + } + + public void showCalls(boolean recurse) { + if (cursorReference == null) { + return; + } + + if (cursorReference.entry instanceof ClassEntry) { + ClassReferenceTreeNode node = this.controller.getClassReferences((ClassEntry) cursorReference.entry); + callsTree.setModel(new DefaultTreeModel(node)); + } else if (cursorReference.entry instanceof FieldEntry) { + FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) cursorReference.entry); + callsTree.setModel(new DefaultTreeModel(node)); + } else if (cursorReference.entry instanceof MethodEntry) { + MethodReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) cursorReference.entry, recurse); + callsTree.setModel(new DefaultTreeModel(node)); + } + + tabs.setSelectedIndex(2); + + redraw(); + } + + public void toggleMapping() { + Entry obfEntry = cursorReference.entry; + Entry deobfEntry = controller.project.getMapper().deobfuscate(obfEntry); + + if (!Objects.equals(obfEntry, deobfEntry)) { + this.controller.removeMapping(cursorReference); + this.controller.sendPacket(new RemoveMappingC2SPacket(cursorReference.getNameableEntry())); + } else { + this.controller.markAsDeobfuscated(cursorReference); + this.controller.sendPacket(new MarkDeobfuscatedC2SPacket(cursorReference.getNameableEntry())); + } + } + + private TreePath getPathToRoot(TreeNode node) { + List nodes = Lists.newArrayList(); + TreeNode n = node; + do { + nodes.add(n); + n = n.getParent(); + } while (n != null); + Collections.reverse(nodes); + return new TreePath(nodes.toArray()); + } + + public void showDiscardDiag(Function callback, String... options) { + int response = JOptionPane.showOptionDialog(this.frame, I18n.translate("prompt.close.summary"), I18n.translate("prompt.close.title"), JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE, null, options, options[2]); + callback.apply(response); + } + + public void saveMapping() { + if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION) + this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath()); + } + + public void close() { + if (!this.controller.isDirty()) { + // everything is saved, we can exit safely + exit(); + } else { + // ask to save before closing + showDiscardDiag((response) -> { + if (response == JOptionPane.YES_OPTION) { + this.saveMapping(); + exit(); + } else if (response == JOptionPane.NO_OPTION) { + exit(); + } + + return null; + }, I18n.translate("prompt.close.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.close.cancel")); + } + } + + private void exit() { + if (searchDialog != null) { + searchDialog.dispose(); + } + this.frame.dispose(); + System.exit(0); + } + + public void redraw() { + this.frame.validate(); + this.frame.repaint(); + } + + public void onPanelRename(Object prevData, Object data, DefaultMutableTreeNode node) throws IllegalNameException { + // package rename + if (data instanceof String) { + for (int i = 0; i < node.getChildCount(); i++) { + DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i); + ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); + ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); + this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getFullName()), dataChild.getFullName(), false); + this.controller.sendPacket(new RenameC2SPacket(prevDataChild, dataChild.getFullName(), false)); + childNode.setUserObject(dataChild); + } + node.setUserObject(data); + // Ob package will never be modified, just reload deob view + this.deobfPanel.deobfClasses.reload(); + } + // class rename + else if (data instanceof ClassEntry) { + this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getFullName()), ((ClassEntry) data).getFullName(), false); + this.controller.sendPacket(new RenameC2SPacket((ClassEntry) prevData, ((ClassEntry) data).getFullName(), false)); + } + } + + public void moveClassTree(EntryReference, Entry> obfReference, String newName) { + String oldEntry = obfReference.entry.getContainingClass().getPackageName(); + String newEntry = new ClassEntry(newName).getPackageName(); + moveClassTree(obfReference, oldEntry == null, newEntry == null); + } + + // TODO: getExpansionState will *not* actually update itself based on name changes! + public void moveClassTree(EntryReference, Entry> obfReference, boolean isOldOb, boolean isNewOb) { + ClassEntry classEntry = obfReference.entry.getContainingClass(); + + List stateDeobf = this.deobfPanel.deobfClasses.getExpansionState(this.deobfPanel.deobfClasses); + List stateObf = this.obfPanel.obfClasses.getExpansionState(this.obfPanel.obfClasses); + + // Ob -> deob + if (!isNewOb) { + this.deobfPanel.deobfClasses.moveClassIn(classEntry); + this.obfPanel.obfClasses.moveClassOut(classEntry); + this.deobfPanel.deobfClasses.reload(); + this.obfPanel.obfClasses.reload(); + } + // Deob -> ob + else if (!isOldOb) { + this.obfPanel.obfClasses.moveClassIn(classEntry); + this.deobfPanel.deobfClasses.moveClassOut(classEntry); + this.deobfPanel.deobfClasses.reload(); + this.obfPanel.obfClasses.reload(); + } + // Local move + else if (isOldOb) { + this.obfPanel.obfClasses.moveClassIn(classEntry); + this.obfPanel.obfClasses.reload(); + } else { + this.deobfPanel.deobfClasses.moveClassIn(classEntry); + this.deobfPanel.deobfClasses.reload(); + } + + this.deobfPanel.deobfClasses.restoreExpansionState(this.deobfPanel.deobfClasses, stateDeobf); + this.obfPanel.obfClasses.restoreExpansionState(this.obfPanel.obfClasses, stateObf); + } + + public PanelObf getObfPanel() { + return obfPanel; + } + + public PanelDeobf getDeobfPanel() { + return deobfPanel; + } + + public void setShouldNavigateOnClick(boolean shouldNavigateOnClick) { + this.shouldNavigateOnClick = shouldNavigateOnClick; + } + + public SearchDialog getSearchDialog() { + if (searchDialog == null) { + searchDialog = new SearchDialog(this); + } + return searchDialog; + } + + + public MenuBar getMenuBar() { + return menuBar; + } + + public void addMessage(Message message) { + JScrollBar verticalScrollBar = messageScrollPane.getVerticalScrollBar(); + boolean isAtBottom = verticalScrollBar.getValue() >= verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent(); + messageModel.addElement(message); + if (isAtBottom) { + SwingUtilities.invokeLater(() -> verticalScrollBar.setValue(verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent())); + } + statusLabel.setText(message.translate()); + } + + public void setUserList(List users) { + userModel.clear(); + users.forEach(userModel::addElement); + connectionStatusLabel.setText(String.format(I18n.translate("status.connected_user_count"), users.size())); + } + + private void sendMessage() { + String text = chatBox.getText().trim(); + if (!text.isEmpty()) { + getController().sendPacket(new MessageC2SPacket(text)); + } + chatBox.setText(""); + } + + /** + * Updates the state of the UI elements (button text, enabled state, ...) to reflect the current program state. + * This is a central place to update the UI state to prevent multiple code paths from changing the same state, + * causing inconsistencies. + */ + public void updateUiState() { + menuBar.connectToServerMenu.setEnabled(isJarOpen && connectionState != ConnectionState.HOSTING); + menuBar.connectToServerMenu.setText(I18n.translate(connectionState != ConnectionState.CONNECTED ? "menu.collab.connect" : "menu.collab.disconnect")); + menuBar.startServerMenu.setEnabled(isJarOpen && connectionState != ConnectionState.CONNECTED); + menuBar.startServerMenu.setText(I18n.translate(connectionState != ConnectionState.HOSTING ? "menu.collab.server.start" : "menu.collab.server.stop")); + + menuBar.closeJarMenu.setEnabled(isJarOpen); + menuBar.openMappingsMenus.forEach(item -> item.setEnabled(isJarOpen)); + menuBar.saveMappingsMenu.setEnabled(isJarOpen && enigmaMappingsFileChooser.getSelectedFile() != null && connectionState != ConnectionState.CONNECTED); + menuBar.saveMappingsMenus.forEach(item -> item.setEnabled(isJarOpen)); + menuBar.closeMappingsMenu.setEnabled(isJarOpen); + menuBar.exportSourceMenu.setEnabled(isJarOpen); + menuBar.exportJarMenu.setEnabled(isJarOpen); + + connectionStatusLabel.setText(I18n.translate(connectionState == ConnectionState.NOT_CONNECTED ? "status.disconnected" : "status.connected")); + + if (connectionState == ConnectionState.NOT_CONNECTED) { + logSplit.setLeftComponent(null); + splitRight.setRightComponent(tabs); + } else { + splitRight.setRightComponent(logSplit); + logSplit.setLeftComponent(tabs); + } + } + + public void setConnectionState(ConnectionState state) { + connectionState = state; + statusLabel.setText(I18n.translate("status.ready")); + updateUiState(); + } + +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java new file mode 100644 index 00000000..94979e77 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java @@ -0,0 +1,719 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.gui; + +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import cuchaz.enigma.Enigma; +import cuchaz.enigma.EnigmaProfile; +import cuchaz.enigma.EnigmaProject; +import cuchaz.enigma.analysis.*; +import cuchaz.enigma.api.service.ObfuscationTestService; +import cuchaz.enigma.gui.config.Config; +import cuchaz.enigma.gui.dialog.ProgressDialog; +import cuchaz.enigma.gui.stats.StatsGenerator; +import cuchaz.enigma.gui.stats.StatsMember; +import cuchaz.enigma.gui.util.GuiUtil; +import cuchaz.enigma.gui.util.History; +import cuchaz.enigma.network.*; +import cuchaz.enigma.network.packet.LoginC2SPacket; +import cuchaz.enigma.network.packet.Packet; +import cuchaz.enigma.source.*; +import cuchaz.enigma.translation.mapping.serde.MappingParseException; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.*; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; +import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.mapping.tree.HashEntryTree; +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.MethodEntry; +import cuchaz.enigma.utils.I18n; +import cuchaz.enigma.utils.Utils; + +import javax.annotation.Nullable; +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; +import java.awt.*; +import java.awt.event.ItemEvent; +import java.io.*; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class GuiController implements ClientPacketHandler { + private static final ExecutorService DECOMPILER_SERVICE = Executors.newSingleThreadExecutor( + new ThreadFactoryBuilder() + .setDaemon(true) + .setNameFormat("decompiler-thread") + .build() + ); + + private final Gui gui; + public final Enigma enigma; + + public EnigmaProject project; + private DecompilerService decompilerService; + private Decompiler decompiler; + private IndexTreeBuilder indexTreeBuilder; + + private Path loadedMappingPath; + private MappingFormat loadedMappingFormat; + + private DecompiledClassSource currentSource; + private Source uncommentedSource; + + private EnigmaClient client; + private EnigmaServer server; + + public GuiController(Gui gui, EnigmaProfile profile) { + this.gui = gui; + this.enigma = Enigma.builder() + .setProfile(profile) + .build(); + + decompilerService = Config.getInstance().decompiler.service; + } + + public boolean isDirty() { + return project != null && project.getMapper().isDirty(); + } + + public CompletableFuture openJar(final Path jarPath) { + this.gui.onStartOpenJar(); + + return ProgressDialog.runOffThread(gui.getFrame(), progress -> { + project = enigma.openJar(jarPath, progress); + indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex()); + decompiler = project.createDecompiler(decompilerService); + gui.onFinishOpenJar(jarPath.getFileName().toString()); + refreshClasses(); + }); + } + + public void closeJar() { + this.project = null; + this.gui.onCloseJar(); + } + + public CompletableFuture openMappings(MappingFormat format, Path path) { + if (project == null) return CompletableFuture.completedFuture(null); + + gui.setMappingsFile(path); + + return ProgressDialog.runOffThread(gui.getFrame(), progress -> { + try { + MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters(); + + EntryTree mappings = format.read(path, progress, saveParameters); + project.setMappings(mappings); + + loadedMappingFormat = format; + loadedMappingPath = path; + + refreshClasses(); + refreshCurrentClass(); + } catch (MappingParseException e) { + JOptionPane.showMessageDialog(gui.getFrame(), e.getMessage()); + } + }); + } + + @Override + public void openMappings(EntryTree mappings) { + if (project == null) return; + + project.setMappings(mappings); + refreshClasses(); + refreshCurrentClass(); + } + + public CompletableFuture saveMappings(Path path) { + return saveMappings(path, loadedMappingFormat); + } + + public CompletableFuture saveMappings(Path path, MappingFormat format) { + if (project == null) return CompletableFuture.completedFuture(null); + + return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { + EntryRemapper mapper = project.getMapper(); + MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters(); + + MappingDelta delta = mapper.takeMappingDelta(); + boolean saveAll = !path.equals(loadedMappingPath); + + loadedMappingFormat = format; + loadedMappingPath = path; + + if (saveAll) { + format.write(mapper.getObfToDeobf(), path, progress, saveParameters); + } else { + format.write(mapper.getObfToDeobf(), delta, path, progress, saveParameters); + } + }); + } + + public void closeMappings() { + if (project == null) return; + + project.setMappings(null); + + this.gui.setMappingsFile(null); + refreshClasses(); + refreshCurrentClass(); + } + + public CompletableFuture dropMappings() { + if (project == null) return CompletableFuture.completedFuture(null); + + return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> project.dropMappings(progress)); + } + + public CompletableFuture exportSource(final Path path) { + if (project == null) return CompletableFuture.completedFuture(null); + + return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { + EnigmaProject.JarExport jar = project.exportRemappedJar(progress); + EnigmaProject.SourceExport source = jar.decompile(progress, decompilerService); + + source.write(path, progress); + }); + } + + public CompletableFuture exportJar(final Path path) { + if (project == null) return CompletableFuture.completedFuture(null); + + return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { + EnigmaProject.JarExport jar = project.exportRemappedJar(progress); + jar.write(path, progress); + }); + } + + public Token getToken(int pos) { + if (this.currentSource == null) { + return null; + } + return this.currentSource.getIndex().getReferenceToken(pos); + } + + @Nullable + public EntryReference, Entry> getReference(Token token) { + if (this.currentSource == null) { + return null; + } + return this.currentSource.getIndex().getReference(token); + } + + public ReadableToken getReadableToken(Token token) { + if (this.currentSource == null) { + return null; + } + + SourceIndex index = this.currentSource.getIndex(); + return new ReadableToken( + index.getLineNumber(token.start), + index.getColumnNumber(token.start), + index.getColumnNumber(token.end) + ); + } + + /** + * Navigates to the declaration with respect to navigation history + * + * @param entry the entry whose declaration will be navigated to + */ + public void openDeclaration(Entry entry) { + if (entry == null) { + throw new IllegalArgumentException("Entry cannot be null!"); + } + openReference(new EntryReference<>(entry, entry.getName())); + } + + /** + * Navigates to the reference with respect to navigation history + * + * @param reference the reference + */ + public void openReference(EntryReference, Entry> reference) { + if (reference == null) { + throw new IllegalArgumentException("Reference cannot be null!"); + } + if (this.gui.referenceHistory == null) { + this.gui.referenceHistory = new History<>(reference); + } else { + if (!reference.equals(this.gui.referenceHistory.getCurrent())) { + this.gui.referenceHistory.push(reference); + } + } + setReference(reference); + } + + /** + * Navigates to the reference without modifying history. If the class is not currently loaded, it will be loaded. + * + * @param reference the reference + */ + private void setReference(EntryReference, Entry> reference) { + // get the reference target class + ClassEntry classEntry = reference.getLocationClassEntry(); + if (!project.isRenamable(classEntry)) { + throw new IllegalArgumentException("Obfuscated class " + classEntry + " was not found in the jar!"); + } + + if (this.currentSource == null || !this.currentSource.getEntry().equals(classEntry)) { + // deobfuscate the class, then navigate to the reference + loadClass(classEntry, () -> showReference(reference)); + } else { + showReference(reference); + } + } + + /** + * Navigates to the reference without modifying history. Assumes the class is loaded. + * + * @param reference + */ + private void showReference(EntryReference, Entry> reference) { + Collection tokens = getTokensForReference(reference); + if (tokens.isEmpty()) { + // DEBUG + System.err.println(String.format("WARNING: no tokens found for %s in %s", reference, this.currentSource.getEntry())); + } else { + this.gui.showTokens(tokens); + } + } + + public Collection getTokensForReference(EntryReference, Entry> reference) { + EntryRemapper mapper = this.project.getMapper(); + + SourceIndex index = this.currentSource.getIndex(); + return mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_CLOSEST) + .stream() + .flatMap(r -> index.getReferenceTokens(r).stream()) + .collect(Collectors.toList()); + } + + public void openPreviousReference() { + if (hasPreviousReference()) { + setReference(gui.referenceHistory.goBack()); + } + } + + public boolean hasPreviousReference() { + return gui.referenceHistory != null && gui.referenceHistory.canGoBack(); + } + + public void openNextReference() { + if (hasNextReference()) { + setReference(gui.referenceHistory.goForward()); + } + } + + public boolean hasNextReference() { + return gui.referenceHistory != null && gui.referenceHistory.canGoForward(); + } + + public void navigateTo(Entry entry) { + if (!project.isRenamable(entry)) { + // entry is not in the jar. Ignore it + return; + } + openDeclaration(entry); + } + + public void navigateTo(EntryReference, Entry> reference) { + if (!project.isRenamable(reference.getLocationClassEntry())) { + return; + } + openReference(reference); + } + + private void refreshClasses() { + List obfClasses = Lists.newArrayList(); + List deobfClasses = Lists.newArrayList(); + this.addSeparatedClasses(obfClasses, deobfClasses); + this.gui.setObfClasses(obfClasses); + this.gui.setDeobfClasses(deobfClasses); + } + + public void addSeparatedClasses(List obfClasses, List deobfClasses) { + EntryRemapper mapper = project.getMapper(); + + Collection classes = project.getJarIndex().getEntryIndex().getClasses(); + Stream visibleClasses = classes.stream() + .filter(entry -> !entry.isInnerClass()); + + visibleClasses.forEach(entry -> { + ClassEntry deobfEntry = mapper.deobfuscate(entry); + + List obfService = enigma.getServices().get(ObfuscationTestService.TYPE); + boolean obfuscated = deobfEntry.equals(entry); + + if (obfuscated && !obfService.isEmpty()) { + if (obfService.stream().anyMatch(service -> service.testDeobfuscated(entry))) { + obfuscated = false; + } + } + + if (obfuscated) { + obfClasses.add(entry); + } else { + deobfClasses.add(entry); + } + }); + } + + public void refreshCurrentClass() { + refreshCurrentClass(null); + } + + private void refreshCurrentClass(EntryReference, Entry> reference) { + refreshCurrentClass(reference, RefreshMode.MINIMAL); + } + + private void refreshCurrentClass(EntryReference, Entry> reference, RefreshMode mode) { + if (currentSource != null) { + if (reference == null) { + int obfSelectionStart = currentSource.getObfuscatedOffset(gui.editor.getSelectionStart()); + int obfSelectionEnd = currentSource.getObfuscatedOffset(gui.editor.getSelectionEnd()); + + Rectangle viewportBounds = gui.sourceScroller.getViewport().getViewRect(); + // Here we pick an "anchor position", which we want to stay in the same vertical location on the screen after the new text has been set + int anchorModelPos = gui.editor.getSelectionStart(); + Rectangle anchorViewPos = GuiUtil.safeModelToView(gui.editor, anchorModelPos); + if (anchorViewPos.y < viewportBounds.y || anchorViewPos.y >= viewportBounds.y + viewportBounds.height) { + anchorModelPos = gui.editor.viewToModel(new Point(0, viewportBounds.y)); + anchorViewPos = GuiUtil.safeModelToView(gui.editor, anchorModelPos); + } + int obfAnchorPos = currentSource.getObfuscatedOffset(anchorModelPos); + Rectangle anchorViewPos_f = anchorViewPos; + int scrollX = gui.sourceScroller.getHorizontalScrollBar().getValue(); + + loadClass(currentSource.getEntry(), () -> SwingUtilities.invokeLater(() -> { + int newAnchorModelPos = currentSource.getDeobfuscatedOffset(obfAnchorPos); + Rectangle newAnchorViewPos = GuiUtil.safeModelToView(gui.editor, newAnchorModelPos); + int newScrollY = newAnchorViewPos.y - (anchorViewPos_f.y - viewportBounds.y); + + gui.editor.select(currentSource.getDeobfuscatedOffset(obfSelectionStart), currentSource.getDeobfuscatedOffset(obfSelectionEnd)); + // Changing the selection scrolls to the caret position inside a SwingUtilities.invokeLater call, so + // we need to wrap our change to the scroll position inside another invokeLater so it happens after + // the caret's own scrolling. + SwingUtilities.invokeLater(() -> { + gui.sourceScroller.getHorizontalScrollBar().setValue(Math.min(scrollX, gui.sourceScroller.getHorizontalScrollBar().getMaximum())); + gui.sourceScroller.getVerticalScrollBar().setValue(Math.min(newScrollY, gui.sourceScroller.getVerticalScrollBar().getMaximum())); + }); + }), mode); + } else { + loadClass(currentSource.getEntry(), () -> showReference(reference), mode); + } + } + } + + private void loadClass(ClassEntry classEntry, Runnable callback) { + loadClass(classEntry, callback, RefreshMode.MINIMAL); + } + + private void loadClass(ClassEntry classEntry, Runnable callback, RefreshMode mode) { + ClassEntry targetClass = classEntry.getOutermostClass(); + + boolean requiresDecompile = mode == RefreshMode.FULL || currentSource == null || !currentSource.getEntry().equals(targetClass); + if (requiresDecompile) { + currentSource = null; // Or the GUI may try to find a nonexistent token + gui.setEditorText(I18n.translate("info_panel.editor.class.decompiling")); + } + + DECOMPILER_SERVICE.submit(() -> { + try { + if (requiresDecompile || mode == RefreshMode.JAVADOCS) { + currentSource = decompileSource(targetClass, mode == RefreshMode.JAVADOCS); + } + + remapSource(project.getMapper().getDeobfuscator()); + callback.run(); + } catch (Throwable t) { + System.err.println("An exception was thrown while decompiling class " + classEntry.getFullName()); + t.printStackTrace(System.err); + } + }); + } + + private DecompiledClassSource decompileSource(ClassEntry targetClass, boolean onlyRefreshJavadocs) { + try { + if (!onlyRefreshJavadocs || currentSource == null || !currentSource.getEntry().equals(targetClass)) { + uncommentedSource = decompiler.getSource(targetClass.getFullName()); + } + + Source source = uncommentedSource.addJavadocs(project.getMapper()); + + if (source == null) { + gui.setEditorText(I18n.translate("info_panel.editor.class.not_found") + " " + targetClass); + return DecompiledClassSource.text(targetClass, "Unable to find class"); + } + + SourceIndex index = source.index(); + index.resolveReferences(project.getMapper().getObfResolver()); + + return new DecompiledClassSource(targetClass, index); + } catch (Throwable t) { + StringWriter traceWriter = new StringWriter(); + t.printStackTrace(new PrintWriter(traceWriter)); + + return DecompiledClassSource.text(targetClass, traceWriter.toString()); + } + } + + private void remapSource(Translator translator) { + if (currentSource == null) { + return; + } + + currentSource.remapSource(project, translator); + + gui.setEditorTheme(Config.getInstance().lookAndFeel); + gui.setSource(currentSource); + } + + public void modifierChange(ItemEvent event) { + if (event.getStateChange() == ItemEvent.SELECTED) { + EntryRemapper mapper = project.getMapper(); + Entry entry = gui.cursorReference.entry; + AccessModifier modifier = (AccessModifier) event.getItem(); + + EntryMapping mapping = mapper.getDeobfMapping(entry); + if (mapping != null) { + mapper.mapFromObf(entry, new EntryMapping(mapping.getTargetName(), modifier)); + } else { + mapper.mapFromObf(entry, new EntryMapping(entry.getName(), modifier)); + } + + refreshCurrentClass(); + } + } + + public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) { + Translator translator = project.getMapper().getDeobfuscator(); + ClassInheritanceTreeNode rootNode = indexTreeBuilder.buildClassInheritance(translator, entry); + return ClassInheritanceTreeNode.findNode(rootNode, entry); + } + + public ClassImplementationsTreeNode getClassImplementations(ClassEntry entry) { + Translator translator = project.getMapper().getDeobfuscator(); + return this.indexTreeBuilder.buildClassImplementations(translator, entry); + } + + public MethodInheritanceTreeNode getMethodInheritance(MethodEntry entry) { + Translator translator = project.getMapper().getDeobfuscator(); + MethodInheritanceTreeNode rootNode = indexTreeBuilder.buildMethodInheritance(translator, entry); + return MethodInheritanceTreeNode.findNode(rootNode, entry); + } + + public MethodImplementationsTreeNode getMethodImplementations(MethodEntry entry) { + Translator translator = project.getMapper().getDeobfuscator(); + List rootNodes = indexTreeBuilder.buildMethodImplementations(translator, entry); + if (rootNodes.isEmpty()) { + return null; + } + if (rootNodes.size() > 1) { + System.err.println("WARNING: Method " + entry + " implements multiple interfaces. Only showing first one."); + } + return MethodImplementationsTreeNode.findNode(rootNodes.get(0), entry); + } + + public ClassReferenceTreeNode getClassReferences(ClassEntry entry) { + Translator deobfuscator = project.getMapper().getDeobfuscator(); + ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, entry); + rootNode.load(project.getJarIndex(), true); + return rootNode; + } + + public FieldReferenceTreeNode getFieldReferences(FieldEntry entry) { + Translator translator = project.getMapper().getDeobfuscator(); + FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, entry); + rootNode.load(project.getJarIndex(), true); + return rootNode; + } + + public MethodReferenceTreeNode getMethodReferences(MethodEntry entry, boolean recursive) { + Translator translator = project.getMapper().getDeobfuscator(); + MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, entry); + rootNode.load(project.getJarIndex(), true, recursive); + return rootNode; + } + + public void rename(EntryReference, Entry> reference, String newName, boolean refreshClassTree) { + rename(reference, newName, refreshClassTree, true); + } + + @Override + public void rename(EntryReference, Entry> reference, String newName, boolean refreshClassTree, boolean jumpToReference) { + Entry entry = reference.getNameableEntry(); + project.getMapper().mapFromObf(entry, new EntryMapping(newName)); + + if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) + this.gui.moveClassTree(reference, newName); + + refreshCurrentClass(jumpToReference ? reference : null); + } + + public void removeMapping(EntryReference, Entry> reference) { + removeMapping(reference, true); + } + + @Override + public void removeMapping(EntryReference, Entry> reference, boolean jumpToReference) { + project.getMapper().removeByObf(reference.getNameableEntry()); + + if (reference.entry instanceof ClassEntry) + this.gui.moveClassTree(reference, false, true); + refreshCurrentClass(jumpToReference ? reference : null); + } + + public void changeDocs(EntryReference, Entry> reference, String updatedDocs) { + changeDocs(reference, updatedDocs, true); + } + + @Override + public void changeDocs(EntryReference, Entry> reference, String updatedDocs, boolean jumpToReference) { + changeDoc(reference.entry, Utils.isBlank(updatedDocs) ? null : updatedDocs); + + refreshCurrentClass(jumpToReference ? reference : null, RefreshMode.JAVADOCS); + } + + private void changeDoc(Entry obfEntry, String newDoc) { + EntryRemapper mapper = project.getMapper(); + if (mapper.getDeobfMapping(obfEntry) == null) { + markAsDeobfuscated(obfEntry, false); // NPE + } + mapper.mapFromObf(obfEntry, mapper.getDeobfMapping(obfEntry).withDocs(newDoc), false); + } + + private void markAsDeobfuscated(Entry obfEntry, boolean renaming) { + EntryRemapper mapper = project.getMapper(); + mapper.mapFromObf(obfEntry, new EntryMapping(mapper.deobfuscate(obfEntry).getName()), renaming); + } + + public void markAsDeobfuscated(EntryReference, Entry> reference) { + markAsDeobfuscated(reference, true); + } + + @Override + public void markAsDeobfuscated(EntryReference, Entry> reference, boolean jumpToReference) { + EntryRemapper mapper = project.getMapper(); + Entry entry = reference.getNameableEntry(); + mapper.mapFromObf(entry, new EntryMapping(mapper.deobfuscate(entry).getName())); + + if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) + this.gui.moveClassTree(reference, true, false); + + refreshCurrentClass(jumpToReference ? reference : null); + } + + public void openStats(Set includedMembers) { + ProgressDialog.runOffThread(gui.getFrame(), progress -> { + String data = new StatsGenerator(project).generate(progress, includedMembers); + + try { + File statsFile = File.createTempFile("stats", ".html"); + + try (FileWriter w = new FileWriter(statsFile)) { + w.write( + Utils.readResourceToString("/stats.html") + .replace("/*data*/", data) + ); + } + + Desktop.getDesktop().open(statsFile); + } catch (IOException e) { + throw new Error(e); + } + }); + } + + public void setDecompiler(DecompilerService service) { + uncommentedSource = null; + decompilerService = service; + decompiler = project.createDecompiler(decompilerService); + refreshCurrentClass(null, RefreshMode.FULL); + } + + public EnigmaClient getClient() { + return client; + } + + public EnigmaServer getServer() { + return server; + } + + public void createClient(String username, String ip, int port, char[] password) throws IOException { + client = new EnigmaClient(this, ip, port); + client.connect(); + client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, username)); + gui.setConnectionState(ConnectionState.CONNECTED); + } + + public void createServer(int port, char[] password) throws IOException { + server = new IntegratedEnigmaServer(project.getJarChecksum(), password, EntryRemapper.mapped(project.getJarIndex(), new HashEntryTree<>(project.getMapper().getObfToDeobf())), port); + server.start(); + client = new EnigmaClient(this, "127.0.0.1", port); + client.connect(); + client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, EnigmaServer.OWNER_USERNAME)); + gui.setConnectionState(ConnectionState.HOSTING); + } + + @Override + public synchronized void disconnectIfConnected(String reason) { + if (client == null && server == null) { + return; + } + + if (client != null) { + client.disconnect(); + } + if (server != null) { + server.stop(); + } + client = null; + server = null; + SwingUtilities.invokeLater(() -> { + if (reason != null) { + JOptionPane.showMessageDialog(gui.getFrame(), I18n.translate(reason), I18n.translate("disconnect.disconnected"), JOptionPane.INFORMATION_MESSAGE); + } + gui.setConnectionState(ConnectionState.NOT_CONNECTED); + }); + } + + @Override + public void sendPacket(Packet packet) { + if (client != null) { + client.sendPacket(packet); + } + } + + @Override + public void addMessage(Message message) { + gui.addMessage(message); + } + + @Override + public void updateUserList(List users) { + gui.setUserList(users); + } + +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java new file mode 100644 index 00000000..1f3aa2cb --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Main.java @@ -0,0 +1,118 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.gui; + +import cuchaz.enigma.EnigmaProfile; +import cuchaz.enigma.gui.config.Config; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; + +import cuchaz.enigma.utils.I18n; +import joptsimple.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import com.google.common.io.MoreFiles; + +public class Main { + + public static void main(String[] args) throws IOException { + OptionParser parser = new OptionParser(); + + OptionSpec jar = parser.accepts("jar", "Jar file to open at startup") + .withRequiredArg() + .withValuesConvertedBy(PathConverter.INSTANCE); + + OptionSpec mappings = parser.accepts("mappings", "Mappings file to open at startup") + .withRequiredArg() + .withValuesConvertedBy(PathConverter.INSTANCE); + + OptionSpec profile = parser.accepts("profile", "Profile json to apply at startup") + .withRequiredArg() + .withValuesConvertedBy(PathConverter.INSTANCE); + + parser.accepts("help", "Displays help information"); + + try { + OptionSet options = parser.parse(args); + + if (options.has("help")) { + parser.printHelpOn(System.out); + return; + } + + EnigmaProfile parsedProfile = EnigmaProfile.read(options.valueOf(profile)); + + I18n.setLanguage(Config.getInstance().language); + Gui gui = new Gui(parsedProfile); + GuiController controller = gui.getController(); + + if (options.has(jar)) { + Path jarPath = options.valueOf(jar); + controller.openJar(jarPath) + .whenComplete((v, t) -> { + if (options.has(mappings)) { + Path mappingsPath = options.valueOf(mappings); + if (Files.isDirectory(mappingsPath)) { + controller.openMappings(MappingFormat.ENIGMA_DIRECTORY, mappingsPath); + } else if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(mappingsPath))) { + controller.openMappings(MappingFormat.ENIGMA_ZIP, mappingsPath); + } else { + controller.openMappings(MappingFormat.ENIGMA_FILE, mappingsPath); + } + } + }); + } + } catch (OptionException e) { + System.out.println("Invalid arguments: " + e.getMessage()); + System.out.println(); + parser.printHelpOn(System.out); + } + } + + public static class PathConverter implements ValueConverter { + public static final ValueConverter INSTANCE = new PathConverter(); + + PathConverter() { + } + + @Override + public Path convert(String path) { + // expand ~ to the home dir + if (path.startsWith("~")) { + // get the home dir + Path dirHome = Paths.get(System.getProperty("user.home")); + + // is the path just ~/ or is it ~user/ ? + if (path.startsWith("~/")) { + return dirHome.resolve(path.substring(2)); + } else { + return dirHome.getParent().resolve(path.substring(1)); + } + } + + return Paths.get(path); + } + + @Override + public Class valueType() { + return Path.class; + } + + @Override + public String valuePattern() { + return "path"; + } + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java new file mode 100644 index 00000000..1d603409 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java @@ -0,0 +1,24 @@ +package cuchaz.enigma.gui; + +import java.awt.Component; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.JList; + +import cuchaz.enigma.network.Message; + +// For now, just render the translated text. +// TODO: Icons or something later? +public class MessageListCellRenderer extends DefaultListCellRenderer { + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + Message message = (Message) value; + if (message != null) { + setText(message.translate()); + } + return this; + } + +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java new file mode 100644 index 00000000..1eead6eb --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.gui; + +import cuchaz.enigma.analysis.MethodInheritanceTreeNode; +import cuchaz.enigma.gui.config.Config; + +import javax.swing.*; +import javax.swing.tree.TreeCellRenderer; +import java.awt.*; + +class MethodTreeCellRenderer implements TreeCellRenderer { + + private final TreeCellRenderer parent; + + MethodTreeCellRenderer(TreeCellRenderer parent) { + this.parent = parent; + } + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { + Component ret = parent.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); + Config config = Config.getInstance(); + if (!(value instanceof MethodInheritanceTreeNode) || ((MethodInheritanceTreeNode) value).isImplemented()) { + ret.setForeground(new Color(config.defaultTextColor)); + ret.setFont(ret.getFont().deriveFont(Font.PLAIN)); + } else { + ret.setForeground(new Color(config.numberColor)); + ret.setFont(ret.getFont().deriveFont(Font.ITALIC)); + } + return ret; + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/QuickFindAction.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/QuickFindAction.java new file mode 100644 index 00000000..b7fa2eba --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/QuickFindAction.java @@ -0,0 +1,45 @@ +package cuchaz.enigma.gui; + +import de.sciss.syntaxpane.SyntaxDocument; +import de.sciss.syntaxpane.actions.DefaultSyntaxAction; + +import javax.swing.text.JTextComponent; +import java.awt.event.ActionEvent; + +public final class QuickFindAction extends DefaultSyntaxAction { + public QuickFindAction() { + super("quick-find"); + } + + @Override + public void actionPerformed(JTextComponent target, SyntaxDocument document, int dot, ActionEvent event) { + Data data = Data.get(target); + data.showFindDialog(target); + } + + private static class Data { + private static final String KEY = "enigma-find-data"; + private EnigmaQuickFindDialog findDialog; + + private Data() { + } + + public static Data get(JTextComponent target) { + Object o = target.getDocument().getProperty(KEY); + if (o instanceof Data) { + return (Data) o; + } + + Data data = new Data(); + target.getDocument().putProperty(KEY, data); + return data; + } + + public void showFindDialog(JTextComponent target) { + if (findDialog == null) { + findDialog = new EnigmaQuickFindDialog(target); + } + findDialog.showFor(target); + } + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/ReadableToken.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/ReadableToken.java new file mode 100644 index 00000000..3e4b30cd --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/ReadableToken.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.gui; + +public class ReadableToken { + + public int line; + public int startColumn; + public int endColumn; + + public ReadableToken(int line, int startColumn, int endColumn) { + this.line = line; + this.startColumn = startColumn; + this.endColumn = endColumn; + } + + @Override + public String toString() { + return "line " + line + " columns " + startColumn + "-" + endColumn; + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/RefreshMode.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/RefreshMode.java new file mode 100644 index 00000000..87cb83b2 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/RefreshMode.java @@ -0,0 +1,7 @@ +package cuchaz.enigma.gui; + +public enum RefreshMode { + MINIMAL, + JAVADOCS, + FULL +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java new file mode 100644 index 00000000..10c418c2 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.gui; + +import cuchaz.enigma.source.Token; + +import javax.swing.*; +import java.awt.*; + +public class TokenListCellRenderer implements ListCellRenderer { + + private GuiController controller; + private DefaultListCellRenderer defaultRenderer; + + public TokenListCellRenderer(GuiController controller) { + this.controller = controller; + this.defaultRenderer = new DefaultListCellRenderer(); + } + + @Override + public Component getListCellRendererComponent(JList list, Token token, int index, boolean isSelected, boolean hasFocus) { + JLabel label = (JLabel) this.defaultRenderer.getListCellRendererComponent(list, token, index, isSelected, hasFocus); + label.setText(this.controller.getReadableToken(token).toString()); + return label; + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Config.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Config.java new file mode 100644 index 00000000..373dcf04 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Config.java @@ -0,0 +1,261 @@ +package cuchaz.enigma.gui.config; + +import com.bulenkov.darcula.DarculaLaf; +import com.google.common.io.Files; +import com.google.gson.*; +import cuchaz.enigma.source.DecompilerService; +import cuchaz.enigma.source.Decompilers; + +import cuchaz.enigma.utils.I18n; + +import javax.swing.*; +import javax.swing.plaf.metal.MetalLookAndFeel; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.charset.Charset; + +public class Config { + public static class AlphaColorEntry { + public Integer rgb; + public float alpha = 1.0f; + + public AlphaColorEntry(Integer rgb, float alpha) { + this.rgb = rgb; + this.alpha = alpha; + } + + public Color get() { + if (rgb == null) { + return new Color(0, 0, 0, 0); + } + + Color baseColor = new Color(rgb); + return new Color(baseColor.getRed(), baseColor.getGreen(), baseColor.getBlue(), (int)(255 * alpha)); + } + } + + public enum LookAndFeel { + DEFAULT("Default"), + DARCULA("Darcula"), + SYSTEM("System"), + NONE("None (JVM default)"); + + // the "JVM default" look and feel, get it at the beginning and store it so we can set it later + private static javax.swing.LookAndFeel NONE_LAF = UIManager.getLookAndFeel(); + private final String name; + + LookAndFeel(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setGlobalLAF() { + try { + switch (this) { + case NONE: + UIManager.setLookAndFeel(NONE_LAF); + break; + case DEFAULT: + UIManager.setLookAndFeel(new MetalLookAndFeel()); + break; + case DARCULA: + UIManager.setLookAndFeel(new DarculaLaf()); + break; + case SYSTEM: + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } + } catch (Exception e){ + throw new Error("Failed to set global look and feel", e); + } + } + + public static boolean isDarkLaf() { + // a bit of a hack because swing doesn't give any API for that, and we need colors that aren't defined in look and feel + JPanel panel = new JPanel(); + panel.setSize(new Dimension(10, 10)); + panel.doLayout(); + + BufferedImage image = new BufferedImage(panel.getSize().width, panel.getSize().height, BufferedImage.TYPE_INT_RGB); + panel.printAll(image.getGraphics()); + + Color c = new Color(image.getRGB(0, 0)); + + // convert the color we got to grayscale + int b = (int) (0.3 * c.getRed() + 0.59 * c.getGreen() + 0.11 * c.getBlue()); + return b < 85; + } + + public void apply(Config config) { + boolean isDark = this == LookAndFeel.DARCULA || isDarkLaf(); + if (!isDark) {//Defaults found here: https://github.com/Sciss/SyntaxPane/blob/122da367ff7a5d31627a70c62a48a9f0f4f85a0a/src/main/resources/de/sciss/syntaxpane/defaultsyntaxkit/config.properties#L139 + config.lineNumbersForeground = 0x333300; + config.lineNumbersBackground = 0xEEEEFF; + config.lineNumbersSelected = 0xCCCCEE; + config.obfuscatedColor = new AlphaColorEntry(0xFFDCDC, 1.0f); + config.obfuscatedColorOutline = new AlphaColorEntry(0xA05050, 1.0f); + config.proposedColor = new AlphaColorEntry(0x000000, 0.075f); + config.proposedColorOutline = new AlphaColorEntry(0x000000, 0.15f); + config.deobfuscatedColor = new AlphaColorEntry(0xDCFFDC, 1.0f); + config.deobfuscatedColorOutline = new AlphaColorEntry(0x50A050, 1.0f); + config.editorBackground = 0xFFFFFF; + config.highlightColor = 0x3333EE; + config.caretColor = 0x000000; + config.selectionHighlightColor = 0x000000; + config.stringColor = 0xCC6600; + config.numberColor = 0x999933; + config.operatorColor = 0x000000; + config.delimiterColor = 0x000000; + config.typeColor = 0x000000; + config.identifierColor = 0x000000; + config.defaultTextColor = 0x000000; + } else {//Based off colors found here: https://github.com/dracula/dracula-theme/ + config.lineNumbersForeground = 0xA4A4A3; + config.lineNumbersBackground = 0x313335; + config.lineNumbersSelected = 0x606366; + config.obfuscatedColor = new AlphaColorEntry(0xFF5555, 0.3f); + config.obfuscatedColorOutline = new AlphaColorEntry(0xFF5555, 0.5f); + config.deobfuscatedColor = new AlphaColorEntry(0x50FA7B, 0.3f); + config.deobfuscatedColorOutline = new AlphaColorEntry(0x50FA7B, 0.5f); + config.proposedColor = new AlphaColorEntry(0x606366, 0.3f); + config.proposedColorOutline = new AlphaColorEntry(0x606366, 0.5f); + config.editorBackground = 0x282A36; + config.highlightColor = 0xFF79C6; + config.caretColor = 0xF8F8F2; + config.selectionHighlightColor = 0xF8F8F2; + config.stringColor = 0xF1FA8C; + config.numberColor = 0xBD93F9; + config.operatorColor = 0xF8F8F2; + config.delimiterColor = 0xF8F8F2; + config.typeColor = 0xF8F8F2; + config.identifierColor = 0xF8F8F2; + config.defaultTextColor = 0xF8F8F2; + } + } + } + + public enum Decompiler { + PROCYON("Procyon", Decompilers.PROCYON), + CFR("CFR", Decompilers.CFR); + + public final DecompilerService service; + public final String name; + + Decompiler(String name, DecompilerService service) { + this.name = name; + this.service = service; + } + } + + private static final File DIR_HOME = new File(System.getProperty("user.home")); + private static final File ENIGMA_DIR = new File(DIR_HOME, ".enigma"); + private static final File CONFIG_FILE = new File(ENIGMA_DIR, "config.json"); + private static final Config INSTANCE = new Config(); + + private final transient Gson gson; // transient to exclude it from being exposed + + public AlphaColorEntry obfuscatedColor; + public AlphaColorEntry obfuscatedColorOutline; + public AlphaColorEntry proposedColor; + public AlphaColorEntry proposedColorOutline; + public AlphaColorEntry deobfuscatedColor; + public AlphaColorEntry deobfuscatedColorOutline; + + public Integer editorBackground; + public Integer highlightColor; + public Integer caretColor; + public Integer selectionHighlightColor; + + public Integer stringColor; + public Integer numberColor; + public Integer operatorColor; + public Integer delimiterColor; + public Integer typeColor; + public Integer identifierColor; + public Integer defaultTextColor; + + public Integer lineNumbersBackground; + public Integer lineNumbersSelected; + public Integer lineNumbersForeground; + + public String language = I18n.DEFAULT_LANGUAGE; + + public LookAndFeel lookAndFeel = LookAndFeel.DEFAULT; + + public float scaleFactor = 1.0f; + + public Decompiler decompiler = Decompiler.PROCYON; + + private Config() { + gson = new GsonBuilder() + .registerTypeAdapter(Integer.class, new IntSerializer()) + .registerTypeAdapter(Integer.class, new IntDeserializer()) + .registerTypeAdapter(Config.class, (InstanceCreator) type -> this) + .setPrettyPrinting() + .create(); + try { + this.loadConfig(); + } catch (IOException ignored) { + try { + this.reset(); + } catch (IOException ignored1) { + } + } + } + + public void loadConfig() throws IOException { + if (!ENIGMA_DIR.exists()) ENIGMA_DIR.mkdirs(); + File configFile = new File(ENIGMA_DIR, "config.json"); + boolean loaded = false; + + if (configFile.exists()) { + try { + gson.fromJson(Files.asCharSource(configFile, Charset.defaultCharset()).read(), Config.class); + loaded = true; + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (!loaded) { + this.reset(); + Files.touch(configFile); + } + saveConfig(); + } + + public void saveConfig() throws IOException { + Files.asCharSink(CONFIG_FILE, Charset.defaultCharset()).write(gson.toJson(this)); + } + + public void reset() throws IOException { + this.lookAndFeel = LookAndFeel.DEFAULT; + this.lookAndFeel.apply(this); + this.decompiler = Decompiler.PROCYON; + this.language = I18n.DEFAULT_LANGUAGE; + this.saveConfig(); + } + + private static class IntSerializer implements JsonSerializer { + @Override + public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive("#" + Integer.toHexString(src).toUpperCase()); + } + } + + private static class IntDeserializer implements JsonDeserializer { + @Override + public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { + return (int) Long.parseLong(json.getAsString().replace("#", ""), 16); + } + } + + public static Config getInstance() { + return INSTANCE; + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Themes.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Themes.java new file mode 100644 index 00000000..035b2381 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Themes.java @@ -0,0 +1,45 @@ +package cuchaz.enigma.gui.config; + +import java.io.IOException; + +import javax.swing.SwingUtilities; + +import com.google.common.collect.ImmutableMap; +import cuchaz.enigma.gui.EnigmaSyntaxKit; +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.gui.highlight.BoxHighlightPainter; +import cuchaz.enigma.gui.highlight.TokenHighlightType; +import cuchaz.enigma.gui.util.ScaleUtil; +import de.sciss.syntaxpane.DefaultSyntaxKit; + +public class Themes { + + public static void setLookAndFeel(Gui gui, Config.LookAndFeel lookAndFeel) { + Config.getInstance().lookAndFeel = lookAndFeel; + updateTheme(gui); + } + + public static void updateTheme(Gui gui) { + Config config = Config.getInstance(); + config.lookAndFeel.setGlobalLAF(); + config.lookAndFeel.apply(config); + try { + config.saveConfig(); + } catch (IOException e) { + e.printStackTrace(); + } + EnigmaSyntaxKit.invalidate(); + DefaultSyntaxKit.initKit(); + DefaultSyntaxKit.registerContentType("text/enigma-sources", EnigmaSyntaxKit.class.getName()); + gui.boxHighlightPainters = ImmutableMap.of( + TokenHighlightType.OBFUSCATED, BoxHighlightPainter.create(config.obfuscatedColor, config.obfuscatedColorOutline), + TokenHighlightType.PROPOSED, BoxHighlightPainter.create(config.proposedColor, config.proposedColorOutline), + TokenHighlightType.DEOBFUSCATED, BoxHighlightPainter.create(config.deobfuscatedColor, config.deobfuscatedColorOutline) + ); + gui.setEditorTheme(config.lookAndFeel); + SwingUtilities.updateComponentTreeUI(gui.getFrame()); + ScaleUtil.applyScaling(); + } + + +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java new file mode 100644 index 00000000..fff755d3 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.gui.dialog; + +import cuchaz.enigma.Enigma; +import cuchaz.enigma.gui.util.GuiUtil; +import cuchaz.enigma.utils.I18n; +import cuchaz.enigma.gui.util.ScaleUtil; +import cuchaz.enigma.utils.Utils; + +import javax.swing.*; +import java.awt.*; +import java.io.IOException; + +public class AboutDialog { + + public static void show(JFrame parent) { + // init frame + final JFrame frame = new JFrame(String.format(I18n.translate("menu.help.about.title"), Enigma.NAME)); + final Container pane = frame.getContentPane(); + pane.setLayout(new FlowLayout()); + + // load the content + try { + String html = Utils.readResourceToString("/about.html"); + html = String.format(html, Enigma.NAME, Enigma.VERSION); + JLabel label = new JLabel(html); + label.setHorizontalAlignment(JLabel.CENTER); + pane.add(label); + } catch (IOException ex) { + throw new Error(ex); + } + + // show the link + String html = "%s"; + html = String.format(html, Enigma.URL, Enigma.URL); + JButton link = new JButton(html); + link.addActionListener(event -> GuiUtil.openUrl(Enigma.URL)); + link.setBorderPainted(false); + link.setOpaque(false); + link.setBackground(Color.WHITE); + link.setCursor(new Cursor(Cursor.HAND_CURSOR)); + link.setFocusable(false); + JPanel linkPanel = new JPanel(); + linkPanel.add(link); + pane.add(linkPanel); + + // show ok button + JButton okButton = new JButton(I18n.translate("menu.help.about.ok")); + pane.add(okButton); + okButton.addActionListener(arg0 -> frame.dispose()); + + // show the frame + pane.doLayout(); + frame.setSize(ScaleUtil.getDimension(400, 220)); + frame.setResizable(false); + frame.setLocationRelativeTo(parent); + frame.setVisible(true); + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java new file mode 100644 index 00000000..64219ab8 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java @@ -0,0 +1,50 @@ +package cuchaz.enigma.gui.dialog; + +import java.awt.BorderLayout; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; + +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.utils.I18n; + +public class ChangeDialog { + + public static void show(Gui gui) { + // init frame + JFrame frame = new JFrame(I18n.translate("menu.view.change.title")); + JPanel textPanel = new JPanel(); + JPanel buttonPanel = new JPanel(); + frame.setLayout(new BorderLayout()); + frame.add(BorderLayout.NORTH, textPanel); + frame.add(BorderLayout.SOUTH, buttonPanel); + + // show text + JLabel text = new JLabel((I18n.translate("menu.view.change.summary"))); + text.setHorizontalAlignment(JLabel.CENTER); + textPanel.add(text); + + // show ok button + JButton okButton = new JButton(I18n.translate("menu.view.change.ok")); + buttonPanel.add(okButton); + okButton.addActionListener(event -> frame.dispose()); + okButton.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + frame.dispose(); + } + } + }); + + // show the frame + frame.pack(); + frame.setVisible(true); + frame.setResizable(false); + frame.setLocationRelativeTo(gui.getFrame()); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java new file mode 100644 index 00000000..c5f505cf --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java @@ -0,0 +1,82 @@ +package cuchaz.enigma.gui.dialog; + +import cuchaz.enigma.network.EnigmaServer; +import cuchaz.enigma.utils.I18n; + +import javax.swing.*; +import java.awt.Frame; + +public class ConnectToServerDialog { + + public static Result show(Frame parentComponent) { + JTextField usernameField = new JTextField(System.getProperty("user.name"), 20); + JPanel usernameRow = new JPanel(); + usernameRow.add(new JLabel(I18n.translate("prompt.connect.username"))); + usernameRow.add(usernameField); + JTextField ipField = new JTextField(20); + JPanel ipRow = new JPanel(); + ipRow.add(new JLabel(I18n.translate("prompt.connect.ip"))); + ipRow.add(ipField); + JTextField portField = new JTextField(String.valueOf(EnigmaServer.DEFAULT_PORT), 10); + JPanel portRow = new JPanel(); + portRow.add(new JLabel(I18n.translate("prompt.port"))); + portRow.add(portField); + JPasswordField passwordField = new JPasswordField(20); + JPanel passwordRow = new JPanel(); + passwordRow.add(new JLabel(I18n.translate("prompt.password"))); + passwordRow.add(passwordField); + + int response = JOptionPane.showConfirmDialog(parentComponent, new Object[]{usernameRow, ipRow, portRow, passwordRow}, I18n.translate("prompt.connect.title"), JOptionPane.OK_CANCEL_OPTION); + if (response != JOptionPane.OK_OPTION) { + return null; + } + + String username = usernameField.getText(); + String ip = ipField.getText(); + int port; + try { + port = Integer.parseInt(portField.getText()); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.nan"), I18n.translate("prompt.connect.title"), JOptionPane.ERROR_MESSAGE); + return null; + } + if (port < 0 || port >= 65536) { + JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.invalid"), I18n.translate("prompt.connect.title"), JOptionPane.ERROR_MESSAGE); + return null; + } + char[] password = passwordField.getPassword(); + + return new Result(username, ip, port, password); + } + + public static class Result { + private final String username; + private final String ip; + private final int port; + private final char[] password; + + public Result(String username, String ip, int port, char[] password) { + this.username = username; + this.ip = ip; + this.port = port; + this.password = password; + } + + public String getUsername() { + return username; + } + + public String getIp() { + return ip; + } + + public int getPort() { + return port; + } + + public char[] getPassword() { + return password; + } + } + +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java new file mode 100644 index 00000000..c2a93fa5 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.gui.dialog; + +import cuchaz.enigma.Enigma; +import cuchaz.enigma.gui.util.GuiUtil; +import cuchaz.enigma.utils.I18n; +import cuchaz.enigma.gui.util.ScaleUtil; + +import javax.swing.*; +import java.awt.*; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.FileWriter; +import java.io.File; +import java.io.IOException; + +public class CrashDialog { + + private static CrashDialog instance = null; + + private JFrame frame; + private JTextArea text; + + private CrashDialog(JFrame parent) { + // init frame + frame = new JFrame(String.format(I18n.translate("crash.title"), Enigma.NAME)); + final Container pane = frame.getContentPane(); + pane.setLayout(new BorderLayout()); + + JLabel label = new JLabel(String.format(I18n.translate("crash.summary"), Enigma.NAME)); + label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + pane.add(label, BorderLayout.NORTH); + + // report panel + text = new JTextArea(); + text.setTabSize(2); + pane.add(new JScrollPane(text), BorderLayout.CENTER); + + // buttons panel + JPanel buttonsPanel = new JPanel(); + buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.LINE_AXIS)); + JButton exportButton = new JButton(I18n.translate("crash.export")); + exportButton.addActionListener(event -> { + JFileChooser chooser = new JFileChooser(); + chooser.setSelectedFile(new File("enigma_crash.log")); + if (chooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) { + try { + File file = chooser.getSelectedFile(); + FileWriter writer = new FileWriter(file); + writer.write(instance.text.getText()); + writer.close(); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + }); + buttonsPanel.add(exportButton); + buttonsPanel.add(Box.createHorizontalGlue()); + buttonsPanel.add(GuiUtil.unboldLabel(new JLabel(I18n.translate("crash.exit.warning")))); + JButton ignoreButton = new JButton(I18n.translate("crash.ignore")); + ignoreButton.addActionListener(event -> { + // close (hide) the dialog + frame.setVisible(false); + }); + buttonsPanel.add(ignoreButton); + JButton exitButton = new JButton(I18n.translate("crash.exit")); + exitButton.addActionListener(event -> { + // exit enigma + System.exit(1); + }); + buttonsPanel.add(exitButton); + pane.add(buttonsPanel, BorderLayout.SOUTH); + + // show the frame + frame.setSize(ScaleUtil.getDimension(600, 400)); + frame.setLocationRelativeTo(parent); + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + } + + public static void init(JFrame parent) { + instance = new CrashDialog(parent); + } + + public static void show(Throwable ex) { + // get the error report + StringWriter buf = new StringWriter(); + ex.printStackTrace(new PrintWriter(buf)); + String report = buf.toString(); + + // show it! + instance.text.setText(report); + instance.frame.doLayout(); + instance.frame.setVisible(true); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java new file mode 100644 index 00000000..eea1dff1 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java @@ -0,0 +1,65 @@ +package cuchaz.enigma.gui.dialog; + +import cuchaz.enigma.network.EnigmaServer; +import cuchaz.enigma.utils.I18n; + +import javax.swing.*; +import java.awt.*; + +public class CreateServerDialog { + + public static Result show(Frame parentComponent) { + JTextField portField = new JTextField(String.valueOf(EnigmaServer.DEFAULT_PORT), 10); + JPanel portRow = new JPanel(); + portRow.add(new JLabel(I18n.translate("prompt.port"))); + portRow.add(portField); + JPasswordField passwordField = new JPasswordField(20); + JPanel passwordRow = new JPanel(); + passwordRow.add(new JLabel(I18n.translate("prompt.password"))); + passwordRow.add(passwordField); + + int response = JOptionPane.showConfirmDialog(parentComponent, new Object[]{portRow, passwordRow}, I18n.translate("prompt.create_server.title"), JOptionPane.OK_CANCEL_OPTION); + if (response != JOptionPane.OK_OPTION) { + return null; + } + + int port; + try { + port = Integer.parseInt(portField.getText()); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.nan"), I18n.translate("prompt.create_server.title"), JOptionPane.ERROR_MESSAGE); + return null; + } + if (port < 0 || port >= 65536) { + JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.invalid"), I18n.translate("prompt.create_server.title"), JOptionPane.ERROR_MESSAGE); + return null; + } + + char[] password = passwordField.getPassword(); + if (password.length > EnigmaServer.MAX_PASSWORD_LENGTH) { + JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.password.too_long"), I18n.translate("prompt.create_server.title"), JOptionPane.ERROR_MESSAGE); + return null; + } + + return new Result(port, password); + } + + public static class Result { + private final int port; + private final char[] password; + + public Result(int port, char[] password) { + this.port = port; + this.password = password; + } + + public int getPort() { + return port; + } + + public char[] getPassword() { + return password; + } + } + +} 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 new file mode 100644 index 00000000..d81460ab --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.gui.dialog; + +import cuchaz.enigma.gui.util.GuiUtil; +import cuchaz.enigma.utils.I18n; +import cuchaz.enigma.gui.util.ScaleUtil; + +import javax.swing.*; +import javax.swing.text.html.HTML; + +import java.awt.*; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; + +public class JavadocDialog { + + private static JavadocDialog instance = null; + + private JFrame frame; + + private JavadocDialog(JFrame parent, JTextArea text, Callback callback) { + // init frame + frame = new JFrame(I18n.translate("javadocs.edit")); + final Container pane = frame.getContentPane(); + pane.setLayout(new BorderLayout()); + + // editor panel + text.setTabSize(2); + pane.add(new JScrollPane(text), BorderLayout.CENTER); + text.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent event) { + switch (event.getKeyCode()) { + case KeyEvent.VK_ENTER: + if (event.isControlDown()) + callback.closeUi(frame, true); + break; + case KeyEvent.VK_ESCAPE: + callback.closeUi(frame, false); + break; + default: + break; + } + } + }); + + // buttons panel + JPanel buttonsPanel = new JPanel(); + FlowLayout buttonsLayout = new FlowLayout(); + buttonsLayout.setAlignment(FlowLayout.RIGHT); + buttonsPanel.setLayout(buttonsLayout); + buttonsPanel.add(GuiUtil.unboldLabel(new JLabel(I18n.translate("javadocs.instruction")))); + JButton cancelButton = new JButton(I18n.translate("javadocs.cancel")); + cancelButton.addActionListener(event -> { + // close (hide) the dialog + callback.closeUi(frame, false); + }); + buttonsPanel.add(cancelButton); + JButton saveButton = new JButton(I18n.translate("javadocs.save")); + saveButton.addActionListener(event -> { + // exit enigma + callback.closeUi(frame, true); + }); + buttonsPanel.add(saveButton); + pane.add(buttonsPanel, BorderLayout.SOUTH); + + // tags panel + JMenuBar tagsMenu = new JMenuBar(); + + // add javadoc tags + for (JavadocTag tag : JavadocTag.values()) { + JButton tagButton = new JButton(tag.getText()); + tagButton.addActionListener(action -> { + boolean textSelected = text.getSelectedText() != null; + String tagText = tag.isInline() ? "{" + tag.getText() + " }" : tag.getText() + " "; + + if (textSelected) { + if (tag.isInline()) { + tagText = "{" + tag.getText() + " " + text.getSelectedText() + "}"; + } else { + tagText = tag.getText() + " " + text.getSelectedText(); + } + text.replaceSelection(tagText); + } else { + text.insert(tagText, text.getCaretPosition()); + } + + if (tag.isInline()) { + text.setCaretPosition(text.getCaretPosition() - 1); + } + text.grabFocus(); + }); + tagsMenu.add(tagButton); + } + + // add html tags + JComboBox htmlList = new JComboBox(); + htmlList.setPreferredSize(new Dimension()); + for (HTML.Tag htmlTag : HTML.getAllTags()) { + htmlList.addItem(htmlTag.toString()); + } + htmlList.addActionListener(action -> { + String tagText = "<" + htmlList.getSelectedItem().toString() + ">"; + text.insert(tagText, text.getCaretPosition()); + text.grabFocus(); + }); + tagsMenu.add(htmlList); + + pane.add(tagsMenu, BorderLayout.NORTH); + + // show the frame + frame.setSize(ScaleUtil.getDimension(600, 400)); + frame.setLocationRelativeTo(parent); + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + } + + public static void init(JFrame parent, JTextArea area, Callback callback) { + instance = new JavadocDialog(parent, area, callback); + instance.frame.doLayout(); + instance.frame.setVisible(true); + } + + public interface Callback { + void closeUi(JFrame frame, boolean save); + } + + private enum JavadocTag { + CODE(true), + LINK(true), + LINKPLAIN(true), + RETURN(false), + SEE(false), + THROWS(false); + + private boolean inline; + + private JavadocTag(boolean inline) { + this.inline = inline; + } + + public String getText() { + return "@" + this.name().toLowerCase(); + } + + public boolean isInline() { + return this.inline; + } + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java new file mode 100644 index 00000000..fa40af75 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.gui.dialog; + +import cuchaz.enigma.Enigma; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.gui.util.GuiUtil; +import cuchaz.enigma.utils.I18n; +import cuchaz.enigma.gui.util.ScaleUtil; + +import javax.swing.*; +import java.awt.*; +import java.util.concurrent.CompletableFuture; + +public class ProgressDialog implements ProgressListener, AutoCloseable { + + private JFrame frame; + private JLabel labelTitle; + private JLabel labelText; + private JProgressBar progress; + + public ProgressDialog(JFrame parent) { + + // init frame + this.frame = new JFrame(String.format(I18n.translate("progress.operation"), Enigma.NAME)); + final Container pane = this.frame.getContentPane(); + FlowLayout layout = new FlowLayout(); + layout.setAlignment(FlowLayout.LEFT); + pane.setLayout(layout); + + this.labelTitle = new JLabel(); + pane.add(this.labelTitle); + + // set up the progress bar + JPanel panel = new JPanel(); + pane.add(panel); + panel.setLayout(new BorderLayout()); + this.labelText = GuiUtil.unboldLabel(new JLabel()); + this.progress = new JProgressBar(); + this.labelText.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); + panel.add(this.labelText, BorderLayout.NORTH); + panel.add(this.progress, BorderLayout.CENTER); + panel.setPreferredSize(ScaleUtil.getDimension(360, 50)); + + // show the frame + pane.doLayout(); + this.frame.setSize(ScaleUtil.getDimension(400, 120)); + this.frame.setResizable(false); + this.frame.setLocationRelativeTo(parent); + this.frame.setVisible(true); + this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + } + + public static CompletableFuture runOffThread(final JFrame parent, final ProgressRunnable runnable) { + CompletableFuture future = new CompletableFuture<>(); + new Thread(() -> + { + try (ProgressDialog progress = new ProgressDialog(parent)) { + runnable.run(progress); + future.complete(null); + } catch (Exception ex) { + future.completeExceptionally(ex); + throw new Error(ex); + } + }).start(); + return future; + } + + @Override + public void close() { + this.frame.dispose(); + } + + @Override + public void init(int totalWork, String title) { + this.labelTitle.setText(title); + this.progress.setMinimum(0); + this.progress.setMaximum(totalWork); + this.progress.setValue(0); + } + + @Override + public void step(int numDone, String message) { + this.labelText.setText(message); + if (numDone != -1) { + this.progress.setValue(numDone); + this.progress.setIndeterminate(false); + } else { + this.progress.setIndeterminate(true); + } + + // update the frame + this.frame.validate(); + this.frame.repaint(); + } + + public interface ProgressRunnable { + void run(ProgressListener listener) throws Exception; + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java new file mode 100644 index 00000000..2d396c36 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java @@ -0,0 +1,261 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.gui.dialog; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.event.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.gui.GuiController; +import cuchaz.enigma.gui.util.AbstractListCellRenderer; +import cuchaz.enigma.gui.util.ScaleUtil; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.utils.I18n; +import cuchaz.enigma.gui.search.SearchEntry; +import cuchaz.enigma.gui.search.SearchUtil; + +public class SearchDialog { + + private final JTextField searchField; + private DefaultListModel classListModel; + private final JList classList; + private final JDialog dialog; + + private final Gui parent; + private final SearchUtil su; + private SearchUtil.SearchControl currentSearch; + + public SearchDialog(Gui parent) { + this.parent = parent; + + su = new SearchUtil<>(); + + dialog = new JDialog(parent.getFrame(), I18n.translate("menu.view.search"), true); + JPanel contentPane = new JPanel(); + contentPane.setBorder(ScaleUtil.createEmptyBorder(4, 4, 4, 4)); + contentPane.setLayout(new BorderLayout(ScaleUtil.scale(4), ScaleUtil.scale(4))); + + searchField = new JTextField(); + searchField.getDocument().addDocumentListener(new DocumentListener() { + + @Override + public void insertUpdate(DocumentEvent e) { + updateList(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + updateList(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + updateList(); + } + + }); + searchField.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_DOWN) { + int next = classList.isSelectionEmpty() ? 0 : classList.getSelectedIndex() + 1; + classList.setSelectedIndex(next); + classList.ensureIndexIsVisible(next); + } else if (e.getKeyCode() == KeyEvent.VK_UP) { + int prev = classList.isSelectionEmpty() ? classList.getModel().getSize() : classList.getSelectedIndex() - 1; + classList.setSelectedIndex(prev); + classList.ensureIndexIsVisible(prev); + } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + close(); + } + } + }); + searchField.addActionListener(e -> openSelected()); + contentPane.add(searchField, BorderLayout.NORTH); + + classListModel = new DefaultListModel<>(); + classList = new JList<>(); + classList.setModel(classListModel); + classList.setCellRenderer(new ListCellRendererImpl()); + classList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + classList.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent mouseEvent) { + if (mouseEvent.getClickCount() >= 2) { + int idx = classList.locationToIndex(mouseEvent.getPoint()); + SearchEntryImpl entry = classList.getModel().getElementAt(idx); + openEntry(entry); + } + } + }); + contentPane.add(new JScrollPane(classList, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), BorderLayout.CENTER); + + JPanel buttonBar = new JPanel(); + buttonBar.setLayout(new FlowLayout(FlowLayout.RIGHT)); + JButton open = new JButton(I18n.translate("prompt.open")); + open.addActionListener(event -> openSelected()); + buttonBar.add(open); + JButton cancel = new JButton(I18n.translate("prompt.cancel")); + cancel.addActionListener(event -> close()); + buttonBar.add(cancel); + contentPane.add(buttonBar, BorderLayout.SOUTH); + + // apparently the class list doesn't update by itself when the list + // state changes and the dialog is hidden + dialog.addComponentListener(new ComponentAdapter() { + @Override + public void componentShown(ComponentEvent e) { + classList.updateUI(); + } + }); + + dialog.setContentPane(contentPane); + dialog.setSize(ScaleUtil.getDimension(400, 500)); + dialog.setLocationRelativeTo(parent.getFrame()); + } + + public void show() { + su.clear(); + parent.getController().project.getJarIndex().getEntryIndex().getClasses().parallelStream() + .filter(e -> !e.isInnerClass()) + .map(e -> SearchEntryImpl.from(e, parent.getController())) + .map(SearchUtil.Entry::from) + .sequential() + .forEach(su::add); + + updateList(); + + searchField.requestFocus(); + searchField.selectAll(); + + dialog.setVisible(true); + } + + private void openSelected() { + SearchEntryImpl selectedValue = classList.getSelectedValue(); + if (selectedValue != null) { + openEntry(selectedValue); + } + } + + private void openEntry(SearchEntryImpl e) { + close(); + su.hit(e); + parent.getController().navigateTo(e.obf); + if (e.deobf != null) { + parent.getDeobfPanel().deobfClasses.setSelectionClass(e.deobf); + } else { + parent.getObfPanel().obfClasses.setSelectionClass(e.obf); + } + } + + private void close() { + dialog.setVisible(false); + } + + // Updates the list of class names + private void updateList() { + if (currentSearch != null) currentSearch.stop(); + + DefaultListModel classListModel = new DefaultListModel<>(); + this.classListModel = classListModel; + classList.setModel(classListModel); + + currentSearch = su.asyncSearch(searchField.getText(), (idx, e) -> SwingUtilities.invokeLater(() -> classListModel.insertElementAt(e, idx))); + } + + public void dispose() { + dialog.dispose(); + } + + private static final class SearchEntryImpl implements SearchEntry { + + public final ClassEntry obf; + public final ClassEntry deobf; + + private SearchEntryImpl(ClassEntry obf, ClassEntry deobf) { + this.obf = obf; + this.deobf = deobf; + } + + @Override + public List getSearchableNames() { + if (deobf != null) { + return Arrays.asList(obf.getSimpleName(), deobf.getSimpleName()); + } else { + return Collections.singletonList(obf.getSimpleName()); + } + } + + @Override + public String getIdentifier() { + return obf.getFullName(); + } + + @Override + public String toString() { + return String.format("SearchEntryImpl { obf: %s, deobf: %s }", obf, deobf); + } + + public static SearchEntryImpl from(ClassEntry e, GuiController controller) { + ClassEntry deobf = controller.project.getMapper().deobfuscate(e); + if (deobf.equals(e)) deobf = null; + return new SearchEntryImpl(e, deobf); + } + + } + + private static final class ListCellRendererImpl extends AbstractListCellRenderer { + + private final JLabel mainName; + private final JLabel secondaryName; + + public ListCellRendererImpl() { + this.setLayout(new BorderLayout()); + + mainName = new JLabel(); + this.add(mainName, BorderLayout.WEST); + + secondaryName = new JLabel(); + secondaryName.setFont(secondaryName.getFont().deriveFont(Font.ITALIC)); + secondaryName.setForeground(Color.GRAY); + this.add(secondaryName, BorderLayout.EAST); + } + + @Override + public void updateUiForEntry(JList list, SearchEntryImpl value, int index, boolean isSelected, boolean cellHasFocus) { + if (value.deobf == null) { + mainName.setText(value.obf.getSimpleName()); + mainName.setToolTipText(value.obf.getFullName()); + secondaryName.setText(""); + secondaryName.setToolTipText(""); + } else { + mainName.setText(value.deobf.getSimpleName()); + mainName.setToolTipText(value.deobf.getFullName()); + secondaryName.setText(value.obf.getSimpleName()); + secondaryName.setToolTipText(value.obf.getFullName()); + } + } + + } + +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java new file mode 100644 index 00000000..868eba79 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java @@ -0,0 +1,82 @@ +package cuchaz.enigma.gui.dialog; + +import java.awt.BorderLayout; +import java.util.Arrays; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JFrame; +import javax.swing.JPanel; + +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.gui.stats.StatsMember; +import cuchaz.enigma.gui.util.ScaleUtil; +import cuchaz.enigma.utils.I18n; + +public class StatsDialog { + + public static void show(Gui gui) { + // init frame + JFrame frame = new JFrame(I18n.translate("menu.file.stats.title")); + JPanel checkboxesPanel = new JPanel(); + JPanel buttonPanel = new JPanel(); + frame.setLayout(new BorderLayout()); + frame.add(BorderLayout.NORTH, checkboxesPanel); + frame.add(BorderLayout.SOUTH, buttonPanel); + + // show checkboxes + Map checkboxes = Arrays + .stream(StatsMember.values()) + .collect(Collectors.toMap(m -> m, m -> { + JCheckBox checkbox = new JCheckBox(I18n.translate("type." + m.name().toLowerCase(Locale.ROOT))); + checkboxesPanel.add(checkbox); + return checkbox; + })); + + // show generate button + JButton button = new JButton(I18n.translate("menu.file.stats.generate")); + buttonPanel.add(button); + button.setEnabled(false); + button.addActionListener(action -> { + frame.dispose(); + generateStats(gui, checkboxes); + }); + + // add action listener to each checkbox + checkboxes.entrySet().forEach(checkbox -> { + checkbox.getValue().addActionListener(action -> { + if (!button.isEnabled()) { + button.setEnabled(true); + } else if (checkboxes.entrySet().stream().allMatch(entry -> !entry.getValue().isSelected())) { + button.setEnabled(false); + } + }); + }); + + // show the frame + frame.pack(); + frame.setVisible(true); + frame.setSize(ScaleUtil.getDimension(500, 120)); + frame.setResizable(false); + frame.setLocationRelativeTo(gui.getFrame()); + } + + private static void generateStats(Gui gui, Map checkboxes) { + // get members from selected checkboxes + Set includedMembers = checkboxes + .entrySet() + .stream() + .filter(entry -> entry.getValue().isSelected()) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + + // checks if a projet is open + if (gui.getController().project != null) { + gui.getController().openStats(includedMembers); + } + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java new file mode 100644 index 00000000..fb497b11 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java @@ -0,0 +1,40 @@ +package cuchaz.enigma.gui.elements; + +import java.awt.event.MouseEvent; + +import javax.swing.JTabbedPane; + +public class CollapsibleTabbedPane extends JTabbedPane { + + public CollapsibleTabbedPane() { + } + + public CollapsibleTabbedPane(int tabPlacement) { + super(tabPlacement); + } + + public CollapsibleTabbedPane(int tabPlacement, int tabLayoutPolicy) { + super(tabPlacement, tabLayoutPolicy); + } + + @Override + protected void processMouseEvent(MouseEvent e) { + int id = e.getID(); + if (id == MouseEvent.MOUSE_PRESSED) { + if (!isEnabled()) return; + int tabIndex = getUI().tabForCoordinate(this, e.getX(), e.getY()); + if (tabIndex >= 0 && isEnabledAt(tabIndex)) { + if (tabIndex == getSelectedIndex()) { + if (isFocusOwner() && isRequestFocusEnabled()) { + requestFocus(); + } else { + setSelectedIndex(-1); + } + return; + } + } + } + super.processMouseEvent(e); + } + +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java new file mode 100644 index 00000000..24f42ff0 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java @@ -0,0 +1,386 @@ +package cuchaz.enigma.gui.elements; + +import cuchaz.enigma.gui.config.Config; +import cuchaz.enigma.gui.config.Themes; +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.gui.dialog.AboutDialog; +import cuchaz.enigma.gui.dialog.ChangeDialog; +import cuchaz.enigma.gui.dialog.ConnectToServerDialog; +import cuchaz.enigma.gui.dialog.CreateServerDialog; +import cuchaz.enigma.gui.dialog.StatsDialog; +import cuchaz.enigma.gui.util.ScaleUtil; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; +import cuchaz.enigma.utils.I18n; +import cuchaz.enigma.utils.Pair; + +import java.awt.Desktop; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import javax.swing.*; + +public class MenuBar extends JMenuBar { + + public final JMenuItem closeJarMenu; + public final List openMappingsMenus; + public final JMenuItem saveMappingsMenu; + public final List saveMappingsMenus; + public final JMenuItem closeMappingsMenu; + public final JMenuItem dropMappingsMenu; + public final JMenuItem exportSourceMenu; + public final JMenuItem exportJarMenu; + public final JMenuItem connectToServerMenu; + public final JMenuItem startServerMenu; + private final Gui gui; + + public MenuBar(Gui gui) { + this.gui = gui; + + /* + * File menu + */ + { + JMenu menu = new JMenu(I18n.translate("menu.file")); + this.add(menu); + { + JMenuItem item = new JMenuItem(I18n.translate("menu.file.jar.open")); + menu.add(item); + item.addActionListener(event -> { + this.gui.jarFileChooser.setVisible(true); + String file = this.gui.jarFileChooser.getFile(); + // checks if the file name is not empty + if (file != null) { + Path path = Paths.get(this.gui.jarFileChooser.getDirectory()).resolve(file); + // checks if the file name corresponds to an existing file + if (Files.exists(path)) { + gui.getController().openJar(path); + } + } + }); + } + { + JMenuItem item = new JMenuItem(I18n.translate("menu.file.jar.close")); + menu.add(item); + item.addActionListener(event -> this.gui.getController().closeJar()); + this.closeJarMenu = item; + } + menu.addSeparator(); + JMenu openMenu = new JMenu(I18n.translate("menu.file.mappings.open")); + menu.add(openMenu); + { + openMappingsMenus = new ArrayList<>(); + for (MappingFormat format : MappingFormat.values()) { + if (format.getReader() != null) { + JMenuItem item = new JMenuItem(I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT))); + openMenu.add(item); + item.addActionListener(event -> { + if (this.gui.enigmaMappingsFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + File selectedFile = this.gui.enigmaMappingsFileChooser.getSelectedFile(); + this.gui.getController().openMappings(format, selectedFile.toPath()); + } + }); + openMappingsMenus.add(item); + } + } + } + { + JMenuItem item = new JMenuItem(I18n.translate("menu.file.mappings.save")); + menu.add(item); + item.addActionListener(event -> { + this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); + }); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); + this.saveMappingsMenu = item; + } + JMenu saveMenu = new JMenu(I18n.translate("menu.file.mappings.save_as")); + menu.add(saveMenu); + { + saveMappingsMenus = new ArrayList<>(); + for (MappingFormat format : MappingFormat.values()) { + if (format.getWriter() != null) { + JMenuItem item = new JMenuItem(I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT))); + saveMenu.add(item); + item.addActionListener(event -> { + // TODO: Use a specific file chooser for it + if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), format); + this.saveMappingsMenu.setEnabled(true); + } + }); + saveMappingsMenus.add(item); + } + } + } + { + JMenuItem item = new JMenuItem(I18n.translate("menu.file.mappings.close")); + menu.add(item); + item.addActionListener(event -> { + if (this.gui.getController().isDirty()) { + this.gui.showDiscardDiag((response -> { + if (response == JOptionPane.YES_OPTION) { + gui.saveMapping(); + this.gui.getController().closeMappings(); + } else if (response == JOptionPane.NO_OPTION) + this.gui.getController().closeMappings(); + return null; + }), I18n.translate("prompt.close.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.close.cancel")); + } else + this.gui.getController().closeMappings(); + + }); + this.closeMappingsMenu = item; + } + { + JMenuItem item = new JMenuItem(I18n.translate("menu.file.mappings.drop")); + menu.add(item); + item.addActionListener(event -> this.gui.getController().dropMappings()); + this.dropMappingsMenu = item; + } + menu.addSeparator(); + { + JMenuItem item = new JMenuItem(I18n.translate("menu.file.export.source")); + menu.add(item); + item.addActionListener(event -> { + if (this.gui.exportSourceFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { + this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile().toPath()); + } + }); + this.exportSourceMenu = item; + } + { + JMenuItem item = new JMenuItem(I18n.translate("menu.file.export.jar")); + menu.add(item); + item.addActionListener(event -> { + this.gui.exportJarFileChooser.setVisible(true); + if (this.gui.exportJarFileChooser.getFile() != null) { + Path path = Paths.get(this.gui.exportJarFileChooser.getDirectory(), this.gui.exportJarFileChooser.getFile()); + this.gui.getController().exportJar(path); + } + }); + this.exportJarMenu = item; + } + menu.addSeparator(); + { + JMenuItem stats = new JMenuItem(I18n.translate("menu.file.stats")); + menu.add(stats); + stats.addActionListener(event -> StatsDialog.show(this.gui)); + } + menu.addSeparator(); + { + JMenuItem item = new JMenuItem(I18n.translate("menu.file.exit")); + menu.add(item); + item.addActionListener(event -> this.gui.close()); + } + } + + /* + * Decompiler menu + */ + { + JMenu menu = new JMenu(I18n.translate("menu.decompiler")); + this.add(menu); + + ButtonGroup decompilerGroup = new ButtonGroup(); + + for (Config.Decompiler decompiler : Config.Decompiler.values()) { + JRadioButtonMenuItem decompilerButton = new JRadioButtonMenuItem(decompiler.name); + decompilerGroup.add(decompilerButton); + if (decompiler.equals(Config.getInstance().decompiler)) { + decompilerButton.setSelected(true); + } + menu.add(decompilerButton); + decompilerButton.addActionListener(event -> { + gui.getController().setDecompiler(decompiler.service); + + try { + Config.getInstance().decompiler = decompiler; + Config.getInstance().saveConfig(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + } + + /* + * View menu + */ + { + JMenu menu = new JMenu(I18n.translate("menu.view")); + this.add(menu); + { + JMenu themes = new JMenu(I18n.translate("menu.view.themes")); + menu.add(themes); + ButtonGroup themeGroup = new ButtonGroup(); + for (Config.LookAndFeel lookAndFeel : Config.LookAndFeel.values()) { + JRadioButtonMenuItem themeButton = new JRadioButtonMenuItem(I18n.translate("menu.view.themes." + lookAndFeel.name().toLowerCase(Locale.ROOT))); + themeGroup.add(themeButton); + if (lookAndFeel.equals(Config.getInstance().lookAndFeel)) { + themeButton.setSelected(true); + } + themes.add(themeButton); + themeButton.addActionListener(event -> Themes.setLookAndFeel(gui, lookAndFeel)); + } + } + { + JMenu languages = new JMenu(I18n.translate("menu.view.languages")); + menu.add(languages); + ButtonGroup languageGroup = new ButtonGroup(); + for (String lang : I18n.getAvailableLanguages()) { + JRadioButtonMenuItem languageButton = new JRadioButtonMenuItem(I18n.getLanguageName(lang)); + languageGroup.add(languageButton); + if (lang.equals(Config.getInstance().language)) { + languageButton.setSelected(true); + } + languages.add(languageButton); + languageButton.addActionListener(event -> { + I18n.setLanguage(lang); + ChangeDialog.show(this.gui); + }); + } + } + { + JMenu scale = new JMenu(I18n.translate("menu.view.scale")); + { + ButtonGroup scaleGroup = new ButtonGroup(); + Map map = IntStream.of(100, 125, 150, 175, 200) + .mapToObj(scaleFactor -> { + float realScaleFactor = scaleFactor / 100f; + JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(String.format("%d%%", scaleFactor)); + menuItem.addActionListener(event -> ScaleUtil.setScaleFactor(realScaleFactor)); + menuItem.addActionListener(event -> ChangeDialog.show(this.gui)); + scaleGroup.add(menuItem); + scale.add(menuItem); + return new Pair<>(realScaleFactor, menuItem); + }) + .collect(Collectors.toMap(x -> x.a, x -> x.b)); + + JMenuItem customScale = new JMenuItem(I18n.translate("menu.view.scale.custom")); + customScale.addActionListener(event -> { + String answer = (String) JOptionPane.showInputDialog(gui.getFrame(), I18n.translate("menu.view.scale.custom.title"), I18n.translate("menu.view.scale.custom.title"), + JOptionPane.QUESTION_MESSAGE, null, null, Float.toString(ScaleUtil.getScaleFactor() * 100)); + if (answer == null) return; + float newScale = 1.0f; + try { + newScale = Float.parseFloat(answer) / 100f; + } catch (NumberFormatException ignored) { + } + ScaleUtil.setScaleFactor(newScale); + ChangeDialog.show(this.gui); + }); + scale.add(customScale); + ScaleUtil.addListener((newScale, _oldScale) -> { + JRadioButtonMenuItem mi = map.get(newScale); + if (mi != null) { + mi.setSelected(true); + } else { + scaleGroup.clearSelection(); + } + }); + JRadioButtonMenuItem mi = map.get(ScaleUtil.getScaleFactor()); + if (mi != null) { + mi.setSelected(true); + } + } + menu.add(scale); + } + menu.addSeparator(); + { + JMenuItem search = new JMenuItem(I18n.translate("menu.view.search")); + search.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.SHIFT_MASK)); + menu.add(search); + search.addActionListener(event -> { + if (this.gui.getController().project != null) { + this.gui.getSearchDialog().show(); + } + }); + } + } + + /* + * Collab menu + */ + { + JMenu menu = new JMenu(I18n.translate("menu.collab")); + this.add(menu); + { + JMenuItem item = new JMenuItem(I18n.translate("menu.collab.connect")); + menu.add(item); + item.addActionListener(event -> { + if (this.gui.getController().getClient() != null) { + this.gui.getController().disconnectIfConnected(null); + return; + } + ConnectToServerDialog.Result result = ConnectToServerDialog.show(this.gui.getFrame()); + if (result == null) { + return; + } + this.gui.getController().disconnectIfConnected(null); + try { + this.gui.getController().createClient(result.getUsername(), result.getIp(), result.getPort(), result.getPassword()); + } catch (IOException e) { + JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.connect.error"), JOptionPane.ERROR_MESSAGE); + this.gui.getController().disconnectIfConnected(null); + } + Arrays.fill(result.getPassword(), (char)0); + }); + this.connectToServerMenu = item; + } + { + JMenuItem item = new JMenuItem(I18n.translate("menu.collab.server.start")); + menu.add(item); + item.addActionListener(event -> { + if (this.gui.getController().getServer() != null) { + this.gui.getController().disconnectIfConnected(null); + return; + } + CreateServerDialog.Result result = CreateServerDialog.show(this.gui.getFrame()); + if (result == null) { + return; + } + this.gui.getController().disconnectIfConnected(null); + try { + this.gui.getController().createServer(result.getPort(), result.getPassword()); + } catch (IOException e) { + JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.server.start.error"), JOptionPane.ERROR_MESSAGE); + this.gui.getController().disconnectIfConnected(null); + } + }); + this.startServerMenu = item; + } + } + + /* + * Help menu + */ + { + JMenu menu = new JMenu(I18n.translate("menu.help")); + this.add(menu); + { + JMenuItem item = new JMenuItem(I18n.translate("menu.help.about")); + menu.add(item); + item.addActionListener(event -> AboutDialog.show(this.gui.getFrame())); + } + { + JMenuItem item = new JMenuItem(I18n.translate("menu.help.github")); + menu.add(item); + item.addActionListener(event -> { + try { + Desktop.getDesktop().browse(new URL("https://github.com/FabricMC/Enigma").toURI()); + } catch (URISyntaxException | IOException ignored) { + } + }); + } + } + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java new file mode 100644 index 00000000..b92041c3 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java @@ -0,0 +1,125 @@ +package cuchaz.enigma.gui.elements; + +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.utils.I18n; + +import javax.swing.*; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +public class PopupMenuBar extends JPopupMenu { + + public final JMenuItem renameMenu; + public final JMenuItem editJavadocMenu; + public final JMenuItem showInheritanceMenu; + public final JMenuItem showImplementationsMenu; + public final JMenuItem showCallsMenu; + public final JMenuItem showCallsSpecificMenu; + public final JMenuItem openEntryMenu; + public final JMenuItem openPreviousMenu; + public final JMenuItem openNextMenu; + public final JMenuItem toggleMappingMenu; + + public PopupMenuBar(Gui gui) { + { + JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.rename")); + menu.addActionListener(event -> gui.startRename()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_DOWN_MASK)); + menu.setEnabled(false); + this.add(menu); + this.renameMenu = menu; + } + { + JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.javadoc")); + menu.addActionListener(event -> gui.startDocChange()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, InputEvent.CTRL_DOWN_MASK)); + menu.setEnabled(false); + this.add(menu); + this.editJavadocMenu = menu; + } + { + JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.inheritance")); + menu.addActionListener(event -> gui.showInheritance()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, InputEvent.CTRL_DOWN_MASK)); + menu.setEnabled(false); + this.add(menu); + this.showInheritanceMenu = menu; + } + { + JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.implementations")); + menu.addActionListener(event -> gui.showImplementations()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, InputEvent.CTRL_DOWN_MASK)); + menu.setEnabled(false); + this.add(menu); + this.showImplementationsMenu = menu; + } + { + JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.calls")); + menu.addActionListener(event -> gui.showCalls(true)); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK)); + menu.setEnabled(false); + this.add(menu); + this.showCallsMenu = menu; + } + { + JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.calls.specific")); + menu.addActionListener(event -> gui.showCalls(false)); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK + InputEvent.SHIFT_DOWN_MASK)); + menu.setEnabled(false); + this.add(menu); + this.showCallsSpecificMenu = menu; + } + { + JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.declaration")); + menu.addActionListener(event -> gui.getController().navigateTo(gui.cursorReference.entry)); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK)); + menu.setEnabled(false); + this.add(menu); + this.openEntryMenu = menu; + } + { + JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.back")); + menu.addActionListener(event -> gui.getController().openPreviousReference()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, InputEvent.CTRL_DOWN_MASK)); + menu.setEnabled(false); + this.add(menu); + this.openPreviousMenu = menu; + } + { + JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.forward")); + menu.addActionListener(event -> gui.getController().openNextReference()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, InputEvent.CTRL_DOWN_MASK)); + menu.setEnabled(false); + this.add(menu); + this.openNextMenu = menu; + } + { + JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.mark_deobfuscated")); + menu.addActionListener(event -> gui.toggleMapping()); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK)); + menu.setEnabled(false); + this.add(menu); + this.toggleMappingMenu = menu; + } + { + this.add(new JSeparator()); + } + { + JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.in")); + menu.addActionListener(event -> gui.editor.offsetEditorZoom(2)); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, InputEvent.CTRL_DOWN_MASK)); + this.add(menu); + } + { + JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.out")); + menu.addActionListener(event -> gui.editor.offsetEditorZoom(-2)); + menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_DOWN_MASK)); + this.add(menu); + } + { + JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.reset")); + menu.addActionListener(event -> gui.editor.resetEditorZoom()); + this.add(menu); + } + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java new file mode 100644 index 00000000..f5f66287 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java @@ -0,0 +1,10 @@ +package cuchaz.enigma.gui.filechooser; + +import javax.swing.*; + +public class FileChooserAny extends JFileChooser { + public FileChooserAny() { + this.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + this.setAcceptAllFileFilterUsed(false); + } +} \ No newline at end of file diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java new file mode 100644 index 00000000..cea11a68 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java @@ -0,0 +1,8 @@ +package cuchaz.enigma.gui.filechooser; + +import javax.swing.*; + +public class FileChooserFile extends JFileChooser { + public FileChooserFile() { + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java new file mode 100644 index 00000000..c16e0afc --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java @@ -0,0 +1,11 @@ +package cuchaz.enigma.gui.filechooser; + +import javax.swing.*; + +public class FileChooserFolder extends JFileChooser { + + public FileChooserFolder() { + this.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + this.setAcceptAllFileFilterUsed(false); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java new file mode 100644 index 00000000..3ae4380f --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.gui.highlight; + +import cuchaz.enigma.gui.config.Config; + +import javax.swing.text.BadLocationException; +import javax.swing.text.Highlighter; +import javax.swing.text.JTextComponent; +import java.awt.*; + +public class BoxHighlightPainter implements Highlighter.HighlightPainter { + private Color fillColor; + private Color borderColor; + + protected BoxHighlightPainter(Color fillColor, Color borderColor) { + this.fillColor = fillColor; + this.borderColor = borderColor; + } + + public static BoxHighlightPainter create(Config.AlphaColorEntry entry, Config.AlphaColorEntry entryOutline) { + return new BoxHighlightPainter(entry != null ? entry.get() : null, entryOutline != null ? entryOutline.get() : null); + } + + public static Rectangle getBounds(JTextComponent text, int start, int end) { + try { + // determine the bounds of the text + Rectangle startRect = text.modelToView(start); + Rectangle endRect = text.modelToView(end); + Rectangle bounds = startRect.union(endRect); + + // adjust the box so it looks nice + bounds.x -= 2; + bounds.width += 2; + bounds.y += 1; + bounds.height -= 2; + + return bounds; + } catch (BadLocationException ex) { + // don't care... just return something + return new Rectangle(0, 0, 0, 0); + } + } + + @Override + public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { + Rectangle bounds = getBounds(text, start, end); + + // fill the area + if (this.fillColor != null) { + g.setColor(this.fillColor); + g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); + } + + // draw a box around the area + g.setColor(this.borderColor); + g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); + } + +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java new file mode 100644 index 00000000..2e4e462a --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.gui.highlight; + +import cuchaz.enigma.gui.config.Config; + +import javax.swing.text.Highlighter; +import javax.swing.text.JTextComponent; +import java.awt.*; + +public class SelectionHighlightPainter implements Highlighter.HighlightPainter { + + @Override + public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { + // draw a thick border + Graphics2D g2d = (Graphics2D) g; + Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end); + g2d.setColor(new Color(Config.getInstance().selectionHighlightColor)); + g2d.setStroke(new BasicStroke(2.0f)); + g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java new file mode 100644 index 00000000..ae23f324 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java @@ -0,0 +1,7 @@ +package cuchaz.enigma.gui.highlight; + +public enum TokenHighlightType { + OBFUSCATED, + DEOBFUSCATED, + PROPOSED +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java new file mode 100644 index 00000000..922f8f24 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.gui.node; + +import cuchaz.enigma.translation.representation.entry.ClassEntry; + +import javax.swing.tree.DefaultMutableTreeNode; + +public class ClassSelectorClassNode extends DefaultMutableTreeNode { + + private final ClassEntry obfEntry; + private ClassEntry classEntry; + + public ClassSelectorClassNode(ClassEntry obfEntry, ClassEntry classEntry) { + this.obfEntry = obfEntry; + this.classEntry = classEntry; + this.setUserObject(classEntry); + } + + public ClassEntry getObfEntry() { + return obfEntry; + } + + public ClassEntry getClassEntry() { + return this.classEntry; + } + + @Override + public String toString() { + return this.classEntry.getSimpleName(); + } + + @Override + public boolean equals(Object other) { + return other instanceof ClassSelectorClassNode && equals((ClassSelectorClassNode) other); + } + + @Override + public int hashCode() { + return 17 + (classEntry != null ? classEntry.hashCode() : 0); + } + + @Override + public Object getUserObject() { + return classEntry; + } + + @Override + public void setUserObject(Object userObject) { + String packageName = ""; + if (classEntry.getPackageName() != null) + packageName = classEntry.getPackageName() + "/"; + if (userObject instanceof String) + this.classEntry = new ClassEntry(packageName + userObject); + else if (userObject instanceof ClassEntry) + this.classEntry = (ClassEntry) userObject; + super.setUserObject(classEntry); + } + + public boolean equals(ClassSelectorClassNode other) { + return this.classEntry.equals(other.classEntry); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java new file mode 100644 index 00000000..caa985c9 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.gui.node; + +import javax.swing.tree.DefaultMutableTreeNode; + +public class ClassSelectorPackageNode extends DefaultMutableTreeNode { + + private String packageName; + + public ClassSelectorPackageNode(String packageName) { + this.packageName = packageName != null ? packageName : "(none)"; + } + + public String getPackageName() { + return packageName; + } + + @Override + public Object getUserObject() { + return packageName; + } + + @Override + public void setUserObject(Object userObject) { + if (userObject instanceof String) + this.packageName = (String) userObject; + super.setUserObject(userObject); + } + + @Override + public String toString() { + return !packageName.equals("(none)") ? this.packageName : "(none)"; + } + + @Override + public boolean equals(Object other) { + return other instanceof ClassSelectorPackageNode && equals((ClassSelectorPackageNode) other); + } + + @Override + public int hashCode() { + return packageName.hashCode(); + } + + public boolean equals(ClassSelectorPackageNode other) { + return other != null && this.packageName.equals(other.packageName); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java new file mode 100644 index 00000000..c24226b3 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java @@ -0,0 +1,26 @@ +package cuchaz.enigma.gui.panels; + +import cuchaz.enigma.gui.ClassSelector; +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.utils.I18n; + +import javax.swing.*; +import java.awt.*; + +public class PanelDeobf extends JPanel { + + public final ClassSelector deobfClasses; + private final Gui gui; + + public PanelDeobf(Gui gui) { + this.gui = gui; + + this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true); + this.deobfClasses.setSelectionListener(gui.getController()::navigateTo); + this.deobfClasses.setRenameSelectionListener(gui::onPanelRename); + + this.setLayout(new BorderLayout()); + this.add(new JLabel(I18n.translate("info_panel.classes.deobfuscated")), BorderLayout.NORTH); + this.add(new JScrollPane(this.deobfClasses), BorderLayout.CENTER); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java new file mode 100644 index 00000000..346d6655 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java @@ -0,0 +1,171 @@ +package cuchaz.enigma.gui.panels; + +import cuchaz.enigma.EnigmaProject; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.gui.config.Config; +import cuchaz.enigma.gui.BrowserCaret; +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.gui.util.ScaleUtil; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class PanelEditor extends JEditorPane { + private boolean mouseIsPressed = false; + public int fontSize = 12; + + public PanelEditor(Gui gui) { + this.setEditable(false); + this.setSelectionColor(new Color(31, 46, 90)); + this.setCaret(new BrowserCaret()); + this.setFont(ScaleUtil.getFont(this.getFont().getFontName(), Font.PLAIN, this.fontSize)); + this.addCaretListener(event -> gui.onCaretMove(event.getDot(), mouseIsPressed)); + final PanelEditor self = this; + this.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent mouseEvent) { + mouseIsPressed = true; + } + + @Override + public void mouseReleased(MouseEvent e) { + switch (e.getButton()) { + case MouseEvent.BUTTON3: // Right click + self.setCaretPosition(self.viewToModel(e.getPoint())); + break; + + case 4: // Back navigation + gui.getController().openPreviousReference(); + break; + + case 5: // Forward navigation + gui.getController().openNextReference(); + break; + } + mouseIsPressed = false; + } + }); + this.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent event) { + if (event.isControlDown()) { + gui.setShouldNavigateOnClick(false); + switch (event.getKeyCode()) { + case KeyEvent.VK_I: + gui.popupMenu.showInheritanceMenu.doClick(); + break; + + case KeyEvent.VK_M: + gui.popupMenu.showImplementationsMenu.doClick(); + break; + + case KeyEvent.VK_N: + gui.popupMenu.openEntryMenu.doClick(); + break; + + case KeyEvent.VK_P: + gui.popupMenu.openPreviousMenu.doClick(); + break; + + case KeyEvent.VK_E: + gui.popupMenu.openNextMenu.doClick(); + break; + + case KeyEvent.VK_C: + if (event.isShiftDown()) { + gui.popupMenu.showCallsSpecificMenu.doClick(); + } else { + gui.popupMenu.showCallsMenu.doClick(); + } + break; + + case KeyEvent.VK_O: + gui.popupMenu.toggleMappingMenu.doClick(); + break; + + case KeyEvent.VK_R: + gui.popupMenu.renameMenu.doClick(); + break; + + case KeyEvent.VK_D: + gui.popupMenu.editJavadocMenu.doClick(); + break; + + case KeyEvent.VK_F5: + gui.getController().refreshCurrentClass(); + break; + + case KeyEvent.VK_F: + // prevent navigating on click when quick find activated + break; + + case KeyEvent.VK_ADD: + case KeyEvent.VK_EQUALS: + case KeyEvent.VK_PLUS: + self.offsetEditorZoom(2); + break; + case KeyEvent.VK_SUBTRACT: + case KeyEvent.VK_MINUS: + self.offsetEditorZoom(-2); + break; + + default: + gui.setShouldNavigateOnClick(true); // CTRL + break; + } + } + } + + @Override + public void keyTyped(KeyEvent event) { + if (!gui.popupMenu.renameMenu.isEnabled()) return; + + if (!event.isControlDown() && !event.isAltDown() && Character.isJavaIdentifierPart(event.getKeyChar())) { + EnigmaProject project = gui.getController().project; + EntryReference, Entry> reference = project.getMapper().deobfuscate(gui.cursorReference); + Entry entry = reference.getNameableEntry(); + + String name = String.valueOf(event.getKeyChar()); + if (entry instanceof ClassEntry && ((ClassEntry) entry).getParent() == null) { + String packageName = ((ClassEntry) entry).getPackageName(); + if (packageName != null) { + name = packageName + "/" + name; + } + } + + gui.popupMenu.renameMenu.doClick(); + gui.renameTextField.setText(name); + } + } + + @Override + public void keyReleased(KeyEvent event) { + gui.setShouldNavigateOnClick(event.isControlDown()); + } + }); + } + + public void offsetEditorZoom(int zoomAmount) { + int newResult = this.fontSize + zoomAmount; + if (newResult > 8 && newResult < 72) { + this.fontSize = newResult; + this.setFont(ScaleUtil.getFont(this.getFont().getFontName(), Font.PLAIN, this.fontSize)); + } + } + + public void resetEditorZoom() { + this.fontSize = 12; + this.setFont(ScaleUtil.getFont(this.getFont().getFontName(), Font.PLAIN, this.fontSize)); + } + + @Override + public Color getCaretColor() { + return new Color(Config.getInstance().caretColor); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java new file mode 100644 index 00000000..8c19efb5 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java @@ -0,0 +1,32 @@ +package cuchaz.enigma.gui.panels; + +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.gui.util.GuiUtil; +import cuchaz.enigma.utils.I18n; +import cuchaz.enigma.gui.util.ScaleUtil; + +import javax.swing.*; +import java.awt.*; + +public class PanelIdentifier extends JPanel { + + private final Gui gui; + + public PanelIdentifier(Gui gui) { + this.gui = gui; + + this.setLayout(new GridLayout(4, 1, 0, 0)); + this.setPreferredSize(ScaleUtil.getDimension(0, 100)); + this.setBorder(BorderFactory.createTitledBorder(I18n.translate("info_panel.identifier"))); + } + + public void clearReference() { + this.removeAll(); + JLabel label = new JLabel(I18n.translate("info_panel.identifier.none")); + GuiUtil.unboldLabel(label); + label.setHorizontalAlignment(JLabel.CENTER); + this.add(label); + + gui.redraw(); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java new file mode 100644 index 00000000..dd7f9f97 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java @@ -0,0 +1,37 @@ +package cuchaz.enigma.gui.panels; + +import cuchaz.enigma.gui.ClassSelector; +import cuchaz.enigma.gui.Gui; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.utils.I18n; + +import javax.swing.*; +import java.awt.*; +import java.util.Comparator; + +public class PanelObf extends JPanel { + + public final ClassSelector obfClasses; + private final Gui gui; + + public PanelObf(Gui gui) { + this.gui = gui; + + Comparator obfClassComparator = (a, b) -> { + String aname = a.getFullName(); + String bname = b.getFullName(); + if (aname.length() != bname.length()) { + return aname.length() - bname.length(); + } + return aname.compareTo(bname); + }; + + this.obfClasses = new ClassSelector(gui, obfClassComparator, false); + this.obfClasses.setSelectionListener(gui.getController()::navigateTo); + this.obfClasses.setRenameSelectionListener(gui::onPanelRename); + + this.setLayout(new BorderLayout()); + this.add(new JLabel(I18n.translate("info_panel.classes.obfuscated")), BorderLayout.NORTH); + this.add(new JScrollPane(this.obfClasses), BorderLayout.CENTER); + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/search/SearchEntry.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/search/SearchEntry.java new file mode 100644 index 00000000..91727c38 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/search/SearchEntry.java @@ -0,0 +1,17 @@ +package cuchaz.enigma.gui.search; + +import java.util.List; + +public interface SearchEntry { + + List getSearchableNames(); + + /** + * Returns a type that uniquely identifies this search entry across possible changes. + * This is used for tracking the amount of times this entry has been selected. + * + * @return a unique identifier for this search entry + */ + String getIdentifier(); + +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/search/SearchUtil.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/search/SearchUtil.java new file mode 100644 index 00000000..a3b35faa --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/search/SearchUtil.java @@ -0,0 +1,268 @@ +package cuchaz.enigma.gui.search; + +import java.util.*; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiFunction; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import cuchaz.enigma.utils.Pair; + +public class SearchUtil { + + private final Map> entries = new HashMap<>(); + private final Map hitCount = new HashMap<>(); + private final Executor searchExecutor = Executors.newWorkStealingPool(); + + public void add(T entry) { + Entry e = Entry.from(entry); + entries.put(entry, e); + } + + public void add(Entry entry) { + entries.put(entry.searchEntry, entry); + } + + public void addAll(Collection entries) { + this.entries.putAll(entries.parallelStream().collect(Collectors.toMap(e -> e, Entry::from))); + } + + public void remove(T entry) { + entries.remove(entry); + } + + public void clear() { + entries.clear(); + } + + public void clearHits() { + hitCount.clear(); + } + + public Stream search(String term) { + return entries.values().parallelStream() + .map(e -> new Pair<>(e, e.getScore(term, hitCount.getOrDefault(e.searchEntry.getIdentifier(), 0)))) + .filter(e -> e.b > 0) + .sorted(Comparator.comparingDouble(o -> -o.b)) + .map(e -> e.a.searchEntry) + .sequential(); + } + + public SearchControl asyncSearch(String term, SearchResultConsumer consumer) { + Map hitCount = new HashMap<>(this.hitCount); + Map> entries = new HashMap<>(this.entries); + float[] scores = new float[entries.size()]; + Lock scoresLock = new ReentrantLock(); + AtomicInteger size = new AtomicInteger(); + AtomicBoolean control = new AtomicBoolean(false); + AtomicInteger elapsed = new AtomicInteger(); + for (Entry value : entries.values()) { + searchExecutor.execute(() -> { + try { + if (control.get()) return; + float score = value.getScore(term, hitCount.getOrDefault(value.searchEntry.getIdentifier(), 0)); + if (score <= 0) return; + score = -score; // sort descending + try { + scoresLock.lock(); + if (control.get()) return; + int dataSize = size.getAndIncrement(); + int index = Arrays.binarySearch(scores, 0, dataSize, score); + if (index < 0) { + index = ~index; + } + System.arraycopy(scores, index, scores, index + 1, dataSize - index); + scores[index] = score; + consumer.add(index, value.searchEntry); + } finally { + scoresLock.unlock(); + } + } finally { + elapsed.incrementAndGet(); + } + }); + } + + return new SearchControl() { + @Override + public void stop() { + control.set(true); + } + + @Override + public boolean isFinished() { + return entries.size() == elapsed.get(); + } + + @Override + public float getProgress() { + return (float) elapsed.get() / entries.size(); + } + }; + } + + public void hit(T entry) { + if (entries.containsKey(entry)) { + hitCount.compute(entry.getIdentifier(), (_id, i) -> i == null ? 1 : i + 1); + } + } + + public static final class Entry { + + public final T searchEntry; + private final String[][] components; + + private Entry(T searchEntry, String[][] components) { + this.searchEntry = searchEntry; + this.components = components; + } + + public float getScore(String term, int hits) { + String ucTerm = term.toUpperCase(Locale.ROOT); + float maxScore = (float) Arrays.stream(components) + .mapToDouble(name -> getScoreFor(ucTerm, name)) + .max().orElse(0.0); + return maxScore * (hits + 1); + } + + /** + * Computes the score for the given name against the given search term. + * + * @param term the search term (expected to be upper-case) + * @param name the entry name, split at word boundaries (see {@link Entry#wordwiseSplit(String)}) + * @return the computed score for the entry + */ + private static float getScoreFor(String term, String[] name) { + int totalLength = Arrays.stream(name).mapToInt(String::length).sum(); + float scorePerChar = 1f / totalLength; + + // This map contains a snapshot of all the states the search has + // been in. The keys are the remaining characters of the search + // term, the values are the maximum scores for that remaining + // search term part. + Map snapshots = new HashMap<>(); + snapshots.put(term, 0f); + + // For each component, start at each existing snapshot, searching + // for the next longest match, and calculate the new score for each + // match length until the maximum. Then the new scores are put back + // into the snapshot map. + for (int componentIndex = 0; componentIndex < name.length; componentIndex++) { + String component = name[componentIndex]; + float posMultiplier = (name.length - componentIndex) * 0.3f; + Map newSnapshots = new HashMap<>(); + for (Map.Entry snapshot : snapshots.entrySet()) { + String remaining = snapshot.getKey(); + float score = snapshot.getValue(); + component = component.toUpperCase(Locale.ROOT); + int l = compareEqualLength(remaining, component); + for (int i = 1; i <= l; i++) { + float baseScore = scorePerChar * i; + float chainBonus = (i - 1) * 0.5f; + merge(newSnapshots, Collections.singletonMap(remaining.substring(i), score + baseScore * posMultiplier + chainBonus), Math::max); + } + } + merge(snapshots, newSnapshots, Math::max); + } + + // Only return the score for when the search term was completely + // consumed. + return snapshots.getOrDefault("", 0f); + } + + private static void merge(Map self, Map source, BiFunction combiner) { + source.forEach((k, v) -> self.compute(k, (_k, v1) -> v1 == null ? v : v == null ? v1 : combiner.apply(v, v1))); + } + + public static Entry from(T e) { + String[][] components = e.getSearchableNames().parallelStream() + .map(Entry::wordwiseSplit) + .toArray(String[][]::new); + return new Entry<>(e, components); + } + + private static int compareEqualLength(String s1, String s2) { + int len = 0; + while (len < s1.length() && len < s2.length() && s1.charAt(len) == s2.charAt(len)) { + len += 1; + } + return len; + } + + /** + * Splits the given input into components, trying to detect word parts. + *

+ * Example of how words get split (using | as seperator): + *

MinecraftClientGame -> Minecraft|Client|Game

+ *

HTTPInputStream -> HTTP|Input|Stream

+ *

class_932 -> class|_|932

+ *

X11FontManager -> X|11|Font|Manager

+ *

openHTTPConnection -> open|HTTP|Connection

+ *

open_http_connection -> open|_|http|_|connection

+ * + * @param input the input to split + * @return the resulting components + */ + private static String[] wordwiseSplit(String input) { + List list = new ArrayList<>(); + while (!input.isEmpty()) { + int take; + if (Character.isLetter(input.charAt(0))) { + if (input.length() == 1) { + take = 1; + } else { + boolean nextSegmentIsUppercase = Character.isUpperCase(input.charAt(0)) && Character.isUpperCase(input.charAt(1)); + if (nextSegmentIsUppercase) { + int nextLowercase = 1; + while (Character.isUpperCase(input.charAt(nextLowercase))) { + nextLowercase += 1; + if (nextLowercase == input.length()) { + nextLowercase += 1; + break; + } + } + take = nextLowercase - 1; + } else { + int nextUppercase = 1; + while (nextUppercase < input.length() && Character.isLowerCase(input.charAt(nextUppercase))) { + nextUppercase += 1; + } + take = nextUppercase; + } + } + } else if (Character.isDigit(input.charAt(0))) { + int nextNonNum = 1; + while (nextNonNum < input.length() && Character.isLetter(input.charAt(nextNonNum)) && !Character.isLowerCase(input.charAt(nextNonNum))) { + nextNonNum += 1; + } + take = nextNonNum; + } else { + take = 1; + } + list.add(input.substring(0, take)); + input = input.substring(take); + } + return list.toArray(new String[0]); + } + + } + + @FunctionalInterface + public interface SearchResultConsumer { + void add(int index, T entry); + } + + public interface SearchControl { + void stop(); + + boolean isFinished(); + + float getProgress(); + } + +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java new file mode 100644 index 00000000..d7f7ec0a --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java @@ -0,0 +1,197 @@ +package cuchaz.enigma.gui.stats; + +import com.google.gson.GsonBuilder; +import cuchaz.enigma.EnigmaProject; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.analysis.index.EntryIndex; +import cuchaz.enigma.api.service.NameProposalService; +import cuchaz.enigma.api.service.ObfuscationTestService; +import cuchaz.enigma.translation.mapping.EntryRemapper; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.*; +import cuchaz.enigma.utils.I18n; + +import java.util.*; + +public class StatsGenerator { + private final EntryIndex entryIndex; + private final EntryRemapper mapper; + private final EntryResolver entryResolver; + private final List obfuscationTestServices; + private final List nameProposalServices; + + public StatsGenerator(EnigmaProject project) { + entryIndex = project.getJarIndex().getEntryIndex(); + mapper = project.getMapper(); + entryResolver = project.getJarIndex().getEntryResolver(); + obfuscationTestServices = project.getEnigma().getServices().get(ObfuscationTestService.TYPE); + nameProposalServices = project.getEnigma().getServices().get(NameProposalService.TYPE); + } + + public String generate(ProgressListener progress, Set includedMembers) { + includedMembers = EnumSet.copyOf(includedMembers); + int totalWork = 0; + + if (includedMembers.contains(StatsMember.METHODS) || includedMembers.contains(StatsMember.PARAMETERS)) { + totalWork += entryIndex.getMethods().size(); + } + + if (includedMembers.contains(StatsMember.FIELDS)) { + totalWork += entryIndex.getFields().size(); + } + + if (includedMembers.contains(StatsMember.CLASSES)) { + totalWork += entryIndex.getClasses().size(); + } + + progress.init(totalWork, "progress.stats"); + + Map counts = new HashMap<>(); + + int numDone = 0; + if (includedMembers.contains(StatsMember.METHODS) || includedMembers.contains(StatsMember.PARAMETERS)) { + for (MethodEntry method : entryIndex.getMethods()) { + progress.step(numDone++, I18n.translate("type.methods")); + MethodEntry root = entryResolver + .resolveEntry(method, ResolutionStrategy.RESOLVE_ROOT) + .stream() + .findFirst() + .orElseThrow(AssertionError::new); + + if (root == method && !((MethodDefEntry) method).getAccess().isSynthetic()) { + if (includedMembers.contains(StatsMember.METHODS)) { + update(counts, method); + } + + if (includedMembers.contains(StatsMember.PARAMETERS)) { + int index = ((MethodDefEntry) method).getAccess().isStatic() ? 0 : 1; + for (TypeDescriptor argument : method.getDesc().getArgumentDescs()) { + update(counts, new LocalVariableEntry(method, index, "", true,null)); + index += argument.getSize(); + } + } + } + } + } + + if (includedMembers.contains(StatsMember.FIELDS)) { + for (FieldEntry field : entryIndex.getFields()) { + progress.step(numDone++, I18n.translate("type.fields")); + update(counts, field); + } + } + + if (includedMembers.contains(StatsMember.CLASSES)) { + for (ClassEntry clazz : entryIndex.getClasses()) { + progress.step(numDone++, I18n.translate("type.classes")); + update(counts, clazz); + } + } + + progress.step(-1, I18n.translate("progress.stats.data")); + + Tree tree = new Tree<>(); + + for (Map.Entry entry : counts.entrySet()) { + if (entry.getKey().startsWith("com.mojang")) continue; // just a few unmapped names, no point in having a subsection + tree.getNode(entry.getKey()).value = entry.getValue(); + } + + tree.collapse(tree.root); + return new GsonBuilder().setPrettyPrinting().create().toJson(tree.root); + } + + private void update(Map counts, Entry entry) { + if (isObfuscated(entry)) { + String parent = mapper.deobfuscate(entry.getAncestry().get(0)).getName().replace('/', '.'); + counts.put(parent, counts.getOrDefault(parent, 0) + 1); + } + } + + private boolean isObfuscated(Entry entry) { + String name = entry.getName(); + + if (!obfuscationTestServices.isEmpty()) { + for (ObfuscationTestService service : obfuscationTestServices) { + if (service.testDeobfuscated(entry)) { + return false; + } + } + } + + if (!nameProposalServices.isEmpty()) { + for (NameProposalService service : nameProposalServices) { + if (service.proposeName(entry, mapper).isPresent()) { + return false; + } + } + } + + String mappedName = mapper.deobfuscate(entry).getName(); + if (mappedName != null && !mappedName.isEmpty() && !mappedName.equals(name)) { + return false; + } + + return true; + } + + private static class Tree { + public final Node root; + private final Map> nodes = new HashMap<>(); + + public static class Node { + public String name; + public T value; + public List> children = new ArrayList<>(); + private final transient Map> namedChildren = new HashMap<>(); + + public Node(String name, T value) { + this.name = name; + this.value = value; + } + } + + public Tree() { + root = new Node<>("", null); + } + + public Node getNode(String name) { + Node node = nodes.get(name); + + if (node == null) { + node = root; + + for (String part : name.split("\\.")) { + Node child = node.namedChildren.get(part); + + if (child == null) { + child = new Node<>(part, null); + node.namedChildren.put(part, child); + node.children.add(child); + } + + node = child; + } + + nodes.put(name, node); + } + + return node; + } + + public void collapse(Node node) { + while (node.children.size() == 1) { + Node child = node.children.get(0); + node.name = node.name.isEmpty() ? child.name : node.name + "." + child.name; + node.children = child.children; + node.value = child.value; + } + + for (Node child : node.children) { + collapse(child); + } + } + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/stats/StatsMember.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/stats/StatsMember.java new file mode 100644 index 00000000..70b4f40d --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/stats/StatsMember.java @@ -0,0 +1,8 @@ +package cuchaz.enigma.gui.stats; + +public enum StatsMember { + METHODS, + FIELDS, + PARAMETERS, + CLASSES +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/AbstractListCellRenderer.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/AbstractListCellRenderer.java new file mode 100644 index 00000000..612e3e92 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/AbstractListCellRenderer.java @@ -0,0 +1,77 @@ +package cuchaz.enigma.gui.util; + +import java.awt.Component; +import java.awt.event.MouseEvent; + +import javax.swing.*; +import javax.swing.border.Border; + +public abstract class AbstractListCellRenderer extends JPanel implements ListCellRenderer { + + private static final Border NO_FOCUS_BORDER = BorderFactory.createEmptyBorder(1, 1, 1, 1); + + private Border noFocusBorder; + + public AbstractListCellRenderer() { + setBorder(getNoFocusBorder()); + } + + protected Border getNoFocusBorder() { + if (noFocusBorder == null) { + Border border = UIManager.getLookAndFeel().getDefaults().getBorder("List.List.cellNoFocusBorder"); + noFocusBorder = border != null ? border : NO_FOCUS_BORDER; + } + return noFocusBorder; + } + + protected Border getBorder(boolean isSelected, boolean cellHasFocus) { + Border b = null; + if (cellHasFocus) { + UIDefaults defaults = UIManager.getLookAndFeel().getDefaults(); + if (isSelected) { + b = defaults.getBorder("List.focusSelectedCellHighlightBorder"); + } + if (b == null) { + b = defaults.getBorder("List.focusCellHighlightBorder"); + } + } else { + b = getNoFocusBorder(); + } + return b; + } + + public abstract void updateUiForEntry(JList list, E value, int index, boolean isSelected, boolean cellHasFocus); + + @Override + public Component getListCellRendererComponent(JList list, E value, int index, boolean isSelected, boolean cellHasFocus) { + updateUiForEntry(list, value, index, isSelected, cellHasFocus); + + if (isSelected) { + setBackground(list.getSelectionBackground()); + setForeground(list.getSelectionForeground()); + } else { + setBackground(list.getBackground()); + setForeground(list.getForeground()); + } + + setEnabled(list.isEnabled()); + setFont(list.getFont()); + + setBorder(getBorder(isSelected, cellHasFocus)); + + // This isn't the width of the cell, but it's close enough for where it's needed (getComponentAt in getToolTipText) + setSize(list.getWidth(), getPreferredSize().height); + + return this; + } + + @Override + public String getToolTipText(MouseEvent event) { + Component c = getComponentAt(event.getPoint()); + if (c instanceof JComponent) { + return ((JComponent) c).getToolTipText(); + } + return getToolTipText(); + } + +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java new file mode 100644 index 00000000..70172fe7 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java @@ -0,0 +1,56 @@ +package cuchaz.enigma.gui.util; + +import javax.swing.*; +import javax.swing.text.BadLocationException; +import javax.swing.text.JTextComponent; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Locale; +import java.util.StringJoiner; + +public class GuiUtil { + public static void openUrl(String url) { + if (Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + try { + desktop.browse(new URI(url)); + } catch (IOException ex) { + throw new Error(ex); + } catch (URISyntaxException ex) { + throw new IllegalArgumentException(ex); + } + } + } + + public static JLabel unboldLabel(JLabel label) { + Font font = label.getFont(); + label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD)); + return label; + } + + public static void showToolTipNow(JComponent component) { + // HACKHACK: trick the tooltip manager into showing the tooltip right now + ToolTipManager manager = ToolTipManager.sharedInstance(); + int oldDelay = manager.getInitialDelay(); + manager.setInitialDelay(0); + manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false)); + manager.setInitialDelay(oldDelay); + } + + public static Rectangle safeModelToView(JTextComponent component, int modelPos) { + if (modelPos < 0) { + modelPos = 0; + } else if (modelPos >= component.getText().length()) { + modelPos = component.getText().length(); + } + try { + return component.modelToView(modelPos); + } catch (BadLocationException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/History.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/History.java new file mode 100644 index 00000000..b1286998 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/History.java @@ -0,0 +1,49 @@ +package cuchaz.enigma.gui.util; + +import com.google.common.collect.Queues; + +import java.util.Deque; + +public class History { + private final Deque previous = Queues.newArrayDeque(); + private final Deque next = Queues.newArrayDeque(); + private T current; + + public History(T initial) { + current = initial; + } + + public T getCurrent() { + return current; + } + + public void push(T value) { + previous.addLast(current); + current = value; + next.clear(); + } + + public void replace(T value) { + current = value; + } + + public boolean canGoBack() { + return !previous.isEmpty(); + } + + public T goBack() { + next.addFirst(current); + current = previous.removeLast(); + return current; + } + + public boolean canGoForward() { + return !next.isEmpty(); + } + + public T goForward() { + previous.addLast(current); + current = next.removeFirst(); + return current; + } +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/ScaleChangeListener.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/ScaleChangeListener.java new file mode 100644 index 00000000..d045c6d5 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/ScaleChangeListener.java @@ -0,0 +1,8 @@ +package cuchaz.enigma.gui.util; + +@FunctionalInterface +public interface ScaleChangeListener { + + void onScaleChanged(float scale, float oldScale); + +} diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java new file mode 100644 index 00000000..e7ee5657 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java @@ -0,0 +1,110 @@ +package cuchaz.enigma.gui.util; + +import java.awt.Dimension; +import java.awt.Font; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.UIManager; +import javax.swing.border.Border; + +import com.github.swingdpi.UiDefaultsScaler; +import com.github.swingdpi.plaf.BasicTweaker; +import com.github.swingdpi.plaf.MetalTweaker; +import com.github.swingdpi.plaf.NimbusTweaker; +import com.github.swingdpi.plaf.WindowsTweaker; +import cuchaz.enigma.gui.config.Config; +import de.sciss.syntaxpane.DefaultSyntaxKit; + +public class ScaleUtil { + + private static List listeners = new ArrayList<>(); + + public static float getScaleFactor() { + return Config.getInstance().scaleFactor; + } + + public static void setScaleFactor(float scaleFactor) { + float oldScale = getScaleFactor(); + float clamped = Math.min(Math.max(0.25f, scaleFactor), 10.0f); + Config.getInstance().scaleFactor = clamped; + try { + Config.getInstance().saveConfig(); + } catch (IOException e) { + e.printStackTrace(); + } + listeners.forEach(l -> l.onScaleChanged(clamped, oldScale)); + } + + public static void addListener(ScaleChangeListener listener) { + listeners.add(listener); + } + + public static void removeListener(ScaleChangeListener listener) { + listeners.remove(listener); + } + + public static Dimension getDimension(int width, int height) { + return new Dimension(scale(width), scale(height)); + } + + public static Font getFont(String fontName, int plain, int fontSize) { + return scaleFont(new Font(fontName, plain, fontSize)); + } + + public static Font scaleFont(Font font) { + return createTweakerForCurrentLook(getScaleFactor()).modifyFont("", font); + } + + public static float scale(float f) { + return f * getScaleFactor(); + } + + public static float invert(float f) { + return f / getScaleFactor(); + } + + public static int scale(int i) { + return (int) (i * getScaleFactor()); + } + + public static Border createEmptyBorder(int top, int left, int bottom, int right) { + return BorderFactory.createEmptyBorder(scale(top), scale(left), scale(bottom), scale(right)); + } + + public static int invert(int i) { + return (int) (i / getScaleFactor()); + } + + public static void applyScaling() { + float scale = getScaleFactor(); + UiDefaultsScaler.updateAndApplyGlobalScaling((int) (100 * scale), true); + try { + Field defaultFontField = DefaultSyntaxKit.class.getDeclaredField("DEFAULT_FONT"); + defaultFontField.setAccessible(true); + Font font = (Font) defaultFontField.get(null); + font = font.deriveFont(12 * scale); + defaultFontField.set(null, font); + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + } + + private static BasicTweaker createTweakerForCurrentLook(float dpiScaling) { + String testString = UIManager.getLookAndFeel().getName().toLowerCase(); + if (testString.contains("windows")) { + return new WindowsTweaker(dpiScaling, testString.contains("classic")); + } + if (testString.contains("metal")) { + return new MetalTweaker(dpiScaling); + } + if (testString.contains("nimbus")) { + return new NimbusTweaker(dpiScaling); + } + return new BasicTweaker(dpiScaling); + } + +} diff --git a/enigma-swing/src/main/resources/about.html b/enigma-swing/src/main/resources/about.html new file mode 100644 index 00000000..b75c1bf0 --- /dev/null +++ b/enigma-swing/src/main/resources/about.html @@ -0,0 +1,6 @@ + +

%s

+

A tool for debofuscation of Java code

+

+

Version: %s

+ \ No newline at end of file diff --git a/enigma-swing/src/main/resources/stats.html b/enigma-swing/src/main/resources/stats.html new file mode 100644 index 00000000..fcff7c0f --- /dev/null +++ b/enigma-swing/src/main/resources/stats.html @@ -0,0 +1,34 @@ + + + + + + Stats + + + + + +
+ + + + + + + diff --git a/enigma/build.gradle b/enigma/build.gradle new file mode 100644 index 00000000..7cebcd16 --- /dev/null +++ b/enigma/build.gradle @@ -0,0 +1,63 @@ +configurations { + proGuard +} + +dependencies { + implementation 'org.ow2.asm:asm:8.0' + implementation 'org.ow2.asm:asm-commons:8.0' + implementation 'org.ow2.asm:asm-tree:8.0' + implementation 'org.ow2.asm:asm-util:8.0' + + implementation 'net.fabricmc:procyon-fabric-compilertools:0.5.35.13' + implementation 'net.fabricmc:cfr:0.0.1' + + testImplementation 'junit:junit:4.+' + testImplementation 'org.hamcrest:hamcrest-all:1.+' + proGuard 'net.sf.proguard:proguard-base:6.+' +} + +// Generate "version.txt" file + +ext.genOutputDir = file("$buildDir/generated-resources") + +task generateVersionFile { + ext.outputFile = file("$genOutputDir/version.txt") + outputs.file(outputFile) + doLast { + outputFile.text = "${project.version}" + } +} + +sourceSets.main.output.dir genOutputDir, builtBy: generateVersionFile + +// Generate obfuscated JARs for tests + +def libraryJarsArg = JavaVersion.current().java9Compatible ? "/jmods" : "/lib/rt.jar" + +file('src/test/java/cuchaz/enigma/inputs').listFiles().each { theFile -> + if (theFile.directory) { + task("${theFile.name}TestJar", type: Jar) { + from(sourceSets.test.output) { + include "cuchaz/enigma/inputs/$theFile.name/**/*.class" + include 'cuchaz/enigma/inputs/Keep.class' + } + + archiveFileName = theFile.name + '.jar' + destinationDirectory = file('build/test-inputs') + } + + task("${theFile.name}TestObf", type: JavaExec, + dependsOn: "${theFile.name}TestJar") { + main 'proguard.ProGuard' + classpath configurations.proGuard + + args '@src/test/resources/proguard-test.conf', '-injars', file('build/test-inputs/' + + "${theFile.name}.jar"), '-libraryjars', libraryJarsArg, + '-outjars', file('build/test-obf/' + "${theFile.name}.jar") + } + + test.dependsOn "${theFile.name}TestObf" + } +} + +test.dependsOn 'translationTestObf' diff --git a/enigma/src/main/java/cuchaz/enigma/ClassProvider.java b/enigma/src/main/java/cuchaz/enigma/ClassProvider.java new file mode 100644 index 00000000..2b913792 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/ClassProvider.java @@ -0,0 +1,10 @@ +package cuchaz.enigma; + +import org.objectweb.asm.tree.ClassNode; + +import javax.annotation.Nullable; + +public interface ClassProvider { + @Nullable + ClassNode getClassNode(String name); +} diff --git a/enigma/src/main/java/cuchaz/enigma/Enigma.java b/enigma/src/main/java/cuchaz/enigma/Enigma.java new file mode 100644 index 00000000..73c9a090 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/Enigma.java @@ -0,0 +1,139 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableListMultimap; +import cuchaz.enigma.analysis.ClassCache; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.api.EnigmaPlugin; +import cuchaz.enigma.api.EnigmaPluginContext; +import cuchaz.enigma.api.service.EnigmaService; +import cuchaz.enigma.api.service.EnigmaServiceFactory; +import cuchaz.enigma.api.service.EnigmaServiceType; +import cuchaz.enigma.api.service.JarIndexerService; +import cuchaz.enigma.utils.Utils; +import org.objectweb.asm.Opcodes; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.ServiceLoader; + +public class Enigma { + public static final String NAME = "Enigma"; + public static final String VERSION; + public static final String URL = "https://fabricmc.net"; + public static final int ASM_VERSION = Opcodes.ASM8; + + private final EnigmaProfile profile; + private final EnigmaServices services; + + private Enigma(EnigmaProfile profile, EnigmaServices services) { + this.profile = profile; + this.services = services; + } + + public static Enigma create() { + return new Builder().build(); + } + + public static Builder builder() { + return new Builder(); + } + + public EnigmaProject openJar(Path path, ProgressListener progress) throws IOException { + ClassCache classCache = ClassCache.of(path); + JarIndex jarIndex = classCache.index(progress); + + services.get(JarIndexerService.TYPE).forEach(indexer -> indexer.acceptJar(classCache, jarIndex)); + + return new EnigmaProject(this, classCache, jarIndex, Utils.zipSha1(path)); + } + + public EnigmaProfile getProfile() { + return profile; + } + + public EnigmaServices getServices() { + return services; + } + + public static class Builder { + private EnigmaProfile profile = EnigmaProfile.EMPTY; + private Iterable plugins = ServiceLoader.load(EnigmaPlugin.class); + + private Builder() { + } + + public Builder setProfile(EnigmaProfile profile) { + Preconditions.checkNotNull(profile, "profile cannot be null"); + this.profile = profile; + return this; + } + + public Builder setPlugins(Iterable plugins) { + Preconditions.checkNotNull(plugins, "plugins cannot be null"); + this.plugins = plugins; + return this; + } + + public Enigma build() { + PluginContext pluginContext = new PluginContext(profile); + for (EnigmaPlugin plugin : plugins) { + plugin.init(pluginContext); + } + + EnigmaServices services = pluginContext.buildServices(); + return new Enigma(profile, services); + } + } + + private static class PluginContext implements EnigmaPluginContext { + private final EnigmaProfile profile; + + private final ImmutableListMultimap.Builder, EnigmaService> services = ImmutableListMultimap.builder(); + + PluginContext(EnigmaProfile profile) { + this.profile = profile; + } + + @Override + public void registerService(String id, EnigmaServiceType serviceType, EnigmaServiceFactory factory) { + List serviceProfiles = profile.getServiceProfiles(serviceType); + + for (EnigmaProfile.Service serviceProfile : serviceProfiles) { + if (serviceProfile.matches(id)) { + T service = factory.create(serviceProfile::getArgument); + services.put(serviceType, service); + break; + } + } + } + + EnigmaServices buildServices() { + return new EnigmaServices(services.build()); + } + } + + static { + String version = null; + + try { + version = Utils.readResourceToString("/version.txt"); + } catch (Throwable t) { + version = "Unknown Version"; + } + + VERSION = version; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/EnigmaProfile.java b/enigma/src/main/java/cuchaz/enigma/EnigmaProfile.java new file mode 100644 index 00000000..daf27274 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/EnigmaProfile.java @@ -0,0 +1,131 @@ +package cuchaz.enigma; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; +import cuchaz.enigma.api.service.EnigmaServiceType; +import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat; +import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; + +import javax.annotation.Nullable; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public final class EnigmaProfile { + public static final EnigmaProfile EMPTY = new EnigmaProfile(new ServiceContainer(ImmutableMap.of())); + + private static final MappingSaveParameters DEFAULT_MAPPING_SAVE_PARAMETERS = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); + private static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(ServiceContainer.class, (JsonDeserializer) EnigmaProfile::loadServiceContainer) + .create(); + private static final Type SERVICE_LIST_TYPE = new TypeToken>() { + }.getType(); + + @SerializedName("services") + private final ServiceContainer serviceProfiles; + + @SerializedName("mapping_save_parameters") + private final MappingSaveParameters mappingSaveParameters = null; + + private EnigmaProfile(ServiceContainer serviceProfiles) { + this.serviceProfiles = serviceProfiles; + } + + public static EnigmaProfile read(@Nullable Path file) throws IOException { + if (file != null) { + try (BufferedReader reader = Files.newBufferedReader(file)) { + return EnigmaProfile.parse(reader); + } + } else { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(EnigmaProfile.class.getResourceAsStream("/profile.json"), StandardCharsets.UTF_8))) { + return EnigmaProfile.parse(reader); + } catch (IOException ex) { + System.err.println("Failed to load default profile, will use empty profile: " + ex.getMessage()); + return EnigmaProfile.EMPTY; + } + } + } + + public static EnigmaProfile parse(Reader reader) { + return GSON.fromJson(reader, EnigmaProfile.class); + } + + private static ServiceContainer loadServiceContainer(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + if (!json.isJsonObject()) { + throw new JsonParseException("services must be an Object!"); + } + + JsonObject object = json.getAsJsonObject(); + + ImmutableMap.Builder> builder = ImmutableMap.builder(); + + for (Map.Entry entry : object.entrySet()) { + JsonElement value = entry.getValue(); + if (value.isJsonObject()) { + builder.put(entry.getKey(), Collections.singletonList(GSON.fromJson(value, Service.class))); + } else if (value.isJsonArray()) { + builder.put(entry.getKey(), GSON.fromJson(value, SERVICE_LIST_TYPE)); + } else { + throw new JsonParseException(String.format("Don't know how to convert %s to a list of service!", value)); + } + } + + return new ServiceContainer(builder.build()); + } + + public List getServiceProfiles(EnigmaServiceType serviceType) { + return serviceProfiles.get(serviceType.key); + } + + public MappingSaveParameters getMappingSaveParameters() { + //noinspection ConstantConditions + return mappingSaveParameters == null ? EnigmaProfile.DEFAULT_MAPPING_SAVE_PARAMETERS : mappingSaveParameters; + } + + public static class Service { + private final String id; + private final Map args; + + Service(String id, Map args) { + this.id = id; + this.args = args; + } + + public boolean matches(String id) { + return this.id.equals(id); + } + + public Optional getArgument(String key) { + return args != null ? Optional.ofNullable(args.get(key)) : Optional.empty(); + } + } + + static final class ServiceContainer { + private final Map> services; + + ServiceContainer(Map> services) { + this.services = services; + } + + List get(String key) { + return services.getOrDefault(key, Collections.emptyList()); + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java b/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java new file mode 100644 index 00000000..43ea14ed --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java @@ -0,0 +1,288 @@ +package cuchaz.enigma; + +import com.google.common.base.Functions; +import com.google.common.base.Preconditions; +import cuchaz.enigma.analysis.ClassCache; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.api.service.NameProposalService; +import cuchaz.enigma.bytecode.translators.SourceFixVisitor; +import cuchaz.enigma.bytecode.translators.TranslationClassVisitor; +import cuchaz.enigma.source.*; +import cuchaz.enigma.translation.ProposingTranslator; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.*; +import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; +import cuchaz.enigma.utils.I18n; + +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.stream.Collectors; + +public class EnigmaProject { + private final Enigma enigma; + + private final ClassCache classCache; + private final JarIndex jarIndex; + private final byte[] jarChecksum; + + private EntryRemapper mapper; + + public EnigmaProject(Enigma enigma, ClassCache classCache, JarIndex jarIndex, byte[] jarChecksum) { + Preconditions.checkArgument(jarChecksum.length == 20); + this.enigma = enigma; + this.classCache = classCache; + this.jarIndex = jarIndex; + this.jarChecksum = jarChecksum; + + this.mapper = EntryRemapper.empty(jarIndex); + } + + public void setMappings(EntryTree mappings) { + if (mappings != null) { + mapper = EntryRemapper.mapped(jarIndex, mappings); + } else { + mapper = EntryRemapper.empty(jarIndex); + } + } + + public Enigma getEnigma() { + return enigma; + } + + public ClassCache getClassCache() { + return classCache; + } + + public JarIndex getJarIndex() { + return jarIndex; + } + + public byte[] getJarChecksum() { + return jarChecksum; + } + + public EntryRemapper getMapper() { + return mapper; + } + + public void dropMappings(ProgressListener progress) { + DeltaTrackingTree mappings = mapper.getObfToDeobf(); + + Collection> dropped = dropMappings(mappings, progress); + for (Entry entry : dropped) { + mappings.trackChange(entry); + } + } + + private Collection> dropMappings(EntryTree mappings, ProgressListener progress) { + // drop mappings that don't match the jar + MappingsChecker checker = new MappingsChecker(jarIndex, mappings); + MappingsChecker.Dropped dropped = checker.dropBrokenMappings(progress); + + Map, String> droppedMappings = dropped.getDroppedMappings(); + for (Map.Entry, String> mapping : droppedMappings.entrySet()) { + System.out.println("WARNING: Couldn't find " + mapping.getKey() + " (" + mapping.getValue() + ") in jar. Mapping was dropped."); + } + + return droppedMappings.keySet(); + } + + public Decompiler createDecompiler(DecompilerService decompilerService) { + return decompilerService.create(name -> { + ClassNode node = this.getClassCache().getClassNode(name); + + if (node == null) { + return null; + } + + ClassNode fixedNode = new ClassNode(); + node.accept(new SourceFixVisitor(Enigma.ASM_VERSION, fixedNode, getJarIndex())); + return fixedNode; + }, new SourceSettings(true, true)); + } + + public boolean isRenamable(Entry obfEntry) { + if (obfEntry instanceof MethodEntry) { + // HACKHACK: Object methods are not obfuscated identifiers + MethodEntry obfMethodEntry = (MethodEntry) obfEntry; + String name = obfMethodEntry.getName(); + String sig = obfMethodEntry.getDesc().toString(); + if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) { + return false; + } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) { + return false; + } else if (name.equals("finalize") && sig.equals("()V")) { + return false; + } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) { + return false; + } else if (name.equals("hashCode") && sig.equals("()I")) { + return false; + } else if (name.equals("notify") && sig.equals("()V")) { + return false; + } else if (name.equals("notifyAll") && sig.equals("()V")) { + return false; + } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) { + return false; + } else if (name.equals("wait") && sig.equals("()V")) { + return false; + } else if (name.equals("wait") && sig.equals("(J)V")) { + return false; + } else if (name.equals("wait") && sig.equals("(JI)V")) { + return false; + } + } else if (obfEntry instanceof LocalVariableEntry && !((LocalVariableEntry) obfEntry).isArgument()) { + return false; + } + + return this.jarIndex.getEntryIndex().hasEntry(obfEntry); + } + + public boolean isRenamable(EntryReference, Entry> obfReference) { + return obfReference.isNamed() && isRenamable(obfReference.getNameableEntry()); + } + + public JarExport exportRemappedJar(ProgressListener progress) { + Collection classEntries = jarIndex.getEntryIndex().getClasses(); + + NameProposalService[] nameProposalServices = getEnigma().getServices().get(NameProposalService.TYPE).toArray(new NameProposalService[0]); + Translator deobfuscator = nameProposalServices.length == 0 ? mapper.getDeobfuscator() : new ProposingTranslator(mapper, nameProposalServices); + + AtomicInteger count = new AtomicInteger(); + progress.init(classEntries.size(), I18n.translate("progress.classes.deobfuscating")); + + Map compiled = classEntries.parallelStream() + .map(entry -> { + ClassEntry translatedEntry = deobfuscator.translate(entry); + progress.step(count.getAndIncrement(), translatedEntry.toString()); + + ClassNode node = classCache.getClassNode(entry.getFullName()); + if (node != null) { + ClassNode translatedNode = new ClassNode(); + node.accept(new TranslationClassVisitor(deobfuscator, Enigma.ASM_VERSION, new SourceFixVisitor(Enigma.ASM_VERSION, translatedNode, jarIndex))); + return translatedNode; + } + + return null; + }) + .filter(Objects::nonNull) + .collect(Collectors.toMap(n -> n.name, Functions.identity())); + + return new JarExport(jarIndex, compiled); + } + + public static final class JarExport { + private final JarIndex jarIndex; + private final Map compiled; + + JarExport(JarIndex jarIndex, Map compiled) { + this.jarIndex = jarIndex; + this.compiled = compiled; + } + + public void write(Path path, ProgressListener progress) throws IOException { + progress.init(this.compiled.size(), I18n.translate("progress.jar.writing")); + + try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(path))) { + AtomicInteger count = new AtomicInteger(); + + for (ClassNode node : this.compiled.values()) { + progress.step(count.getAndIncrement(), node.name); + + String entryName = node.name.replace('.', '/') + ".class"; + + ClassWriter writer = new ClassWriter(0); + node.accept(writer); + + out.putNextEntry(new JarEntry(entryName)); + out.write(writer.toByteArray()); + out.closeEntry(); + } + } + } + + public SourceExport decompile(ProgressListener progress, DecompilerService decompilerService) { + Collection classes = this.compiled.values().stream() + .filter(classNode -> classNode.name.indexOf('$') == -1) + .collect(Collectors.toList()); + + progress.init(classes.size(), I18n.translate("progress.classes.decompiling")); + + //create a common instance outside the loop as mappings shouldn't be changing while this is happening + Decompiler decompiler = decompilerService.create(compiled::get, new SourceSettings(false, false)); + + AtomicInteger count = new AtomicInteger(); + + Collection decompiled = classes.parallelStream() + .map(translatedNode -> { + progress.step(count.getAndIncrement(), translatedNode.name); + + String source = decompileClass(translatedNode, decompiler); + return new ClassSource(translatedNode.name, source); + }) + .collect(Collectors.toList()); + + return new SourceExport(decompiled); + } + + private String decompileClass(ClassNode translatedNode, Decompiler decompiler) { + return decompiler.getSource(translatedNode.name).asString(); + } + } + + public static final class SourceExport { + private final Collection decompiled; + + SourceExport(Collection decompiled) { + this.decompiled = decompiled; + } + + public void write(Path path, ProgressListener progress) throws IOException { + progress.init(decompiled.size(), I18n.translate("progress.sources.writing")); + + int count = 0; + for (ClassSource source : decompiled) { + progress.step(count++, source.name); + + Path sourcePath = source.resolvePath(path); + source.writeTo(sourcePath); + } + } + } + + private static class ClassSource { + private final String name; + private final String source; + + ClassSource(String name, String source) { + this.name = name; + this.source = source; + } + + void writeTo(Path path) throws IOException { + Files.createDirectories(path.getParent()); + try (BufferedWriter writer = Files.newBufferedWriter(path)) { + writer.write(source); + } + } + + Path resolvePath(Path root) { + return root.resolve(name.replace('.', '/') + ".java"); + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/EnigmaServices.java b/enigma/src/main/java/cuchaz/enigma/EnigmaServices.java new file mode 100644 index 00000000..df3b7bba --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/EnigmaServices.java @@ -0,0 +1,20 @@ +package cuchaz.enigma; + +import com.google.common.collect.ImmutableListMultimap; +import cuchaz.enigma.api.service.EnigmaService; +import cuchaz.enigma.api.service.EnigmaServiceType; + +import java.util.List; + +public final class EnigmaServices { + private final ImmutableListMultimap, EnigmaService> services; + + EnigmaServices(ImmutableListMultimap, EnigmaService> services) { + this.services = services; + } + + @SuppressWarnings("unchecked") + public List get(EnigmaServiceType type) { + return (List) services.get(type); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/ProgressListener.java b/enigma/src/main/java/cuchaz/enigma/ProgressListener.java new file mode 100644 index 00000000..6da3b81a --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/ProgressListener.java @@ -0,0 +1,19 @@ +package cuchaz.enigma; + +public interface ProgressListener { + static ProgressListener none() { + return new ProgressListener() { + @Override + public void init(int totalWork, String title) { + } + + @Override + public void step(int numDone, String message) { + } + }; + } + + void init(int totalWork, String title); + + void step(int numDone, String message); +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/Access.java b/enigma/src/main/java/cuchaz/enigma/analysis/Access.java new file mode 100644 index 00000000..82ca6692 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/Access.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.analysis; + +import cuchaz.enigma.translation.representation.AccessFlags; + +import java.lang.reflect.Modifier; + +public enum Access { + + PUBLIC, PROTECTED, PACKAGE, PRIVATE; + + public static Access get(AccessFlags flags) { + return get(flags.getFlags()); + } + + public static Access get(int modifiers) { + boolean isPublic = Modifier.isPublic(modifiers); + boolean isProtected = Modifier.isProtected(modifiers); + boolean isPrivate = Modifier.isPrivate(modifiers); + + if (isPublic && !isProtected && !isPrivate) { + return PUBLIC; + } else if (!isPublic && isProtected && !isPrivate) { + return PROTECTED; + } else if (!isPublic && !isProtected && isPrivate) { + return PRIVATE; + } else if (!isPublic && !isProtected && !isPrivate) { + return PACKAGE; + } + // assume public by default + return PUBLIC; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java b/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java new file mode 100644 index 00000000..4685b390 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java @@ -0,0 +1,156 @@ +package cuchaz.enigma.analysis; + +import cuchaz.enigma.Enigma; +import cuchaz.enigma.api.EnigmaPlugin; +import cuchaz.enigma.api.EnigmaPluginContext; +import cuchaz.enigma.api.service.JarIndexerService; +import cuchaz.enigma.api.service.NameProposalService; +import cuchaz.enigma.source.DecompilerService; +import cuchaz.enigma.source.Decompilers; +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.utils.Pair; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.analysis.Analyzer; +import org.objectweb.asm.tree.analysis.Frame; +import org.objectweb.asm.tree.analysis.SourceInterpreter; +import org.objectweb.asm.tree.analysis.SourceValue; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public final class BuiltinPlugin implements EnigmaPlugin { + + public BuiltinPlugin() { + } + + @Override + public void init(EnigmaPluginContext ctx) { + registerEnumNamingService(ctx); + registerDecompilerServices(ctx); + } + + private void registerEnumNamingService(EnigmaPluginContext ctx) { + final Map, String> names = new HashMap<>(); + final EnumFieldNameFindingVisitor visitor = new EnumFieldNameFindingVisitor(names); + + ctx.registerService("enigma:enum_initializer_indexer", JarIndexerService.TYPE, ctx1 -> (classCache, jarIndex) -> classCache.visit(() -> visitor, ClassReader.SKIP_FRAMES)); + ctx.registerService("enigma:enum_name_proposer", NameProposalService.TYPE, ctx1 -> (obfEntry, remapper) -> Optional.ofNullable(names.get(obfEntry))); + } + + private void registerDecompilerServices(EnigmaPluginContext ctx) { + ctx.registerService("enigma:procyon", DecompilerService.TYPE, ctx1 -> Decompilers.PROCYON); + ctx.registerService("enigma:cfr", DecompilerService.TYPE, ctx1 -> Decompilers.CFR); + } + + private static final class EnumFieldNameFindingVisitor extends ClassVisitor { + + private ClassEntry clazz; + private String className; + private final Map, String> mappings; + private final Set> enumFields = new HashSet<>(); + private final List classInits = new ArrayList<>(); + + EnumFieldNameFindingVisitor(Map, String> mappings) { + super(Enigma.ASM_VERSION); + this.mappings = mappings; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + this.className = name; + this.clazz = new ClassEntry(name); + this.enumFields.clear(); + this.classInits.clear(); + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + if ((access & Opcodes.ACC_ENUM) != 0) { + if (!enumFields.add(new Pair<>(name, descriptor))) { + throw new IllegalArgumentException("Found two enum fields with the same name \"" + name + "\" and desc \"" + descriptor + "\"!"); + } + } + return super.visitField(access, name, descriptor, signature, value); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + if ("".equals(name)) { + MethodNode node = new MethodNode(api, access, name, descriptor, signature, exceptions); + classInits.add(node); + return node; + } + return super.visitMethod(access, name, descriptor, signature, exceptions); + } + + @Override + public void visitEnd() { + super.visitEnd(); + try { + collectResults(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + private void collectResults() throws Exception { + String owner = className; + Analyzer analyzer = new Analyzer<>(new SourceInterpreter()); + + for (MethodNode mn : classInits) { + Frame[] frames = analyzer.analyze(className, mn); + + InsnList instrs = mn.instructions; + for (int i = 1; i < instrs.size(); i++) { + AbstractInsnNode instr1 = instrs.get(i - 1); + AbstractInsnNode instr2 = instrs.get(i); + String s = null; + + if (instr2.getOpcode() == Opcodes.PUTSTATIC + && ((FieldInsnNode) instr2).owner.equals(owner) + && enumFields.contains(new Pair<>(((FieldInsnNode) instr2).name, ((FieldInsnNode) instr2).desc)) + && instr1.getOpcode() == Opcodes.INVOKESPECIAL + && "".equals(((MethodInsnNode) instr1).name)) { + + for (int j = 0; j < frames[i - 1].getStackSize(); j++) { + SourceValue sv = frames[i - 1].getStack(j); + for (AbstractInsnNode ci : sv.insns) { + if (ci instanceof LdcInsnNode && ((LdcInsnNode) ci).cst instanceof String) { + //if (s == null || !s.equals(((LdcInsnNode) ci).cst)) { + if (s == null) { + s = (String) (((LdcInsnNode) ci).cst); + // stringsFound++; + } + } + } + } + } + + if (s != null) { + mappings.put(new FieldEntry(clazz, ((FieldInsnNode) instr2).name, new TypeDescriptor(((FieldInsnNode) instr2).desc)), s); + } + // report otherwise? + } + } + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/ClassCache.java b/enigma/src/main/java/cuchaz/enigma/analysis/ClassCache.java new file mode 100644 index 00000000..a3d24e32 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/ClassCache.java @@ -0,0 +1,126 @@ +package cuchaz.enigma.analysis; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.collect.ImmutableSet; +import cuchaz.enigma.ClassProvider; +import cuchaz.enigma.Enigma; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.bytecode.translators.LocalVariableFixVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.tree.ClassNode; + +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.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +public final class ClassCache implements AutoCloseable, ClassProvider { + private final FileSystem fileSystem; + private final ImmutableSet classNames; + + private final Cache nodeCache = CacheBuilder.newBuilder() + .maximumSize(128) + .expireAfterAccess(1, TimeUnit.MINUTES) + .build(); + + private ClassCache(FileSystem fileSystem, ImmutableSet classNames) { + this.fileSystem = fileSystem; + this.classNames = classNames; + } + + public static ClassCache of(Path jarPath) throws IOException { + FileSystem fileSystem = FileSystems.newFileSystem(jarPath, (ClassLoader) null); + ImmutableSet classNames = collectClassNames(fileSystem); + + return new ClassCache(fileSystem, classNames); + } + + private static ImmutableSet collectClassNames(FileSystem fileSystem) throws IOException { + ImmutableSet.Builder classNames = ImmutableSet.builder(); + for (Path root : fileSystem.getRootDirectories()) { + Files.walk(root).map(Path::toString) + .forEach(path -> { + if (path.endsWith(".class")) { + String name = path.substring(1, path.length() - ".class".length()); + classNames.add(name); + } + }); + } + + return classNames.build(); + } + + @Nullable + @Override + public ClassNode getClassNode(String name) { + if (!classNames.contains(name)) { + return null; + } + + try { + return nodeCache.get(name, () -> parseNode(name)); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + + private ClassNode parseNode(String name) throws IOException { + ClassReader reader = getReader(name); + + ClassNode node = new ClassNode(); + + LocalVariableFixVisitor visitor = new LocalVariableFixVisitor(Enigma.ASM_VERSION, node); + reader.accept(visitor, 0); + + return node; + } + + private ClassReader getReader(String name) throws IOException { + Path path = fileSystem.getPath(name + ".class"); + byte[] bytes = Files.readAllBytes(path); + return new ClassReader(bytes); + } + + public int getClassCount() { + return classNames.size(); + } + + public void visit(Supplier visitorSupplier, int readFlags) { + for (String className : classNames) { + ClassVisitor visitor = visitorSupplier.get(); + + ClassNode cached = nodeCache.getIfPresent(className); + if (cached != null) { + cached.accept(visitor); + continue; + } + + try { + ClassReader reader = getReader(className); + reader.accept(visitor, readFlags); + } catch (IOException e) { + System.out.println("Failed to visit class " + className); + e.printStackTrace(); + } + } + } + + @Override + public void close() throws IOException { + this.fileSystem.close(); + } + + public JarIndex index(ProgressListener progress) { + JarIndex index = JarIndex.empty(); + index.indexJar(this, progress); + return index; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/enigma/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java new file mode 100644 index 00000000..0fc44ca6 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.analysis; + +import com.google.common.collect.Lists; +import cuchaz.enigma.analysis.index.InheritanceIndex; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import javax.swing.tree.DefaultMutableTreeNode; +import java.util.Collection; +import java.util.List; + +public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { + private final Translator translator; + private final ClassEntry entry; + + public ClassImplementationsTreeNode(Translator translator, ClassEntry entry) { + this.translator = translator; + this.entry = entry; + } + + public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) { + // is this the node? + if (node.entry.equals(entry.getParent())) { + return node; + } + + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + ClassImplementationsTreeNode foundNode = findNode((ClassImplementationsTreeNode) node.getChildAt(i), entry); + if (foundNode != null) { + return foundNode; + } + } + return null; + } + + public ClassEntry getClassEntry() { + return this.entry; + } + + @Override + public String toString() { + return translator.translate(entry).toString(); + } + + public void load(JarIndex index) { + // get all method implementations + List nodes = Lists.newArrayList(); + InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); + + Collection inheritors = inheritanceIndex.getChildren(entry); + for (ClassEntry inheritor : inheritors) { + nodes.add(new ClassImplementationsTreeNode(translator, inheritor)); + } + + // add them to this node + nodes.forEach(this::add); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java b/enigma/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java new file mode 100644 index 00000000..7904c5f0 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.analysis; + +import com.google.common.collect.Lists; +import cuchaz.enigma.analysis.index.InheritanceIndex; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.entry.ClassEntry; + +import javax.swing.tree.DefaultMutableTreeNode; +import java.util.List; + +public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { + private final Translator translator; + private final ClassEntry obfClassEntry; + + public ClassInheritanceTreeNode(Translator translator, String obfClassName) { + this.translator = translator; + this.obfClassEntry = new ClassEntry(obfClassName); + } + + public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) { + // is this the node? + if (node.getObfClassName().equals(entry.getFullName())) { + return node; + } + + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + ClassInheritanceTreeNode foundNode = findNode((ClassInheritanceTreeNode) node.getChildAt(i), entry); + if (foundNode != null) { + return foundNode; + } + } + return null; + } + + public String getObfClassName() { + return this.obfClassEntry.getFullName(); + } + + @Override + public String toString() { + return translator.translate(obfClassEntry).getFullName(); + } + + public void load(InheritanceIndex ancestries, boolean recurse) { + // get all the child nodes + List nodes = Lists.newArrayList(); + for (ClassEntry inheritor : ancestries.getChildren(this.obfClassEntry)) { + nodes.add(new ClassInheritanceTreeNode(translator, inheritor.getFullName())); + } + + // add them to this node + nodes.forEach(this::add); + + if (recurse) { + for (ClassInheritanceTreeNode node : nodes) { + node.load(ancestries, true); + } + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java b/enigma/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java new file mode 100644 index 00000000..90d8a6cf --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.analysis; + +import com.google.common.collect.Sets; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.analysis.index.ReferenceIndex; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; + +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; +import java.util.Set; + +public class ClassReferenceTreeNode extends DefaultMutableTreeNode + implements ReferenceTreeNode { + + private Translator deobfuscatingTranslator; + private ClassEntry entry; + private EntryReference reference; + + public ClassReferenceTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) { + this.deobfuscatingTranslator = deobfuscatingTranslator; + this.entry = entry; + this.reference = null; + } + + public ClassReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference reference) { + this.deobfuscatingTranslator = deobfuscatingTranslator; + this.entry = reference.entry; + this.reference = reference; + } + + @Override + public ClassEntry getEntry() { + return this.entry; + } + + @Override + public EntryReference getReference() { + return this.reference; + } + + @Override + public String toString() { + if (this.reference != null) { + return String.format("%s", this.deobfuscatingTranslator.translate(this.reference.context)); + } + return this.deobfuscatingTranslator.translate(this.entry).getFullName(); + } + + public void load(JarIndex index, boolean recurse) { + ReferenceIndex referenceIndex = index.getReferenceIndex(); + + // get all the child nodes + for (EntryReference reference : referenceIndex.getReferencesToClass(this.entry)) { + add(new ClassReferenceTreeNode(this.deobfuscatingTranslator, reference)); + } + + if (recurse && this.children != null) { + for (Object child : this.children) { + if (child instanceof ClassReferenceTreeNode) { + ClassReferenceTreeNode node = (ClassReferenceTreeNode) child; + + // don't recurse into ancestor + Set> ancestors = Sets.newHashSet(); + TreeNode n = node; + while (n.getParent() != null) { + n = n.getParent(); + if (n instanceof ClassReferenceTreeNode) { + ancestors.add(((ClassReferenceTreeNode) n).getEntry()); + } + } + if (ancestors.contains(node.getEntry())) { + continue; + } + + node.load(index, true); + } + } + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/EntryReference.java b/enigma/src/main/java/cuchaz/enigma/analysis/EntryReference.java new file mode 100644 index 00000000..320f9450 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/EntryReference.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.analysis; + +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class EntryReference, C extends Entry> implements Translatable { + + private static final List CONSTRUCTOR_NON_NAMES = Arrays.asList("this", "super", "static"); + public E entry; + public C context; + public ReferenceTargetType targetType; + + private boolean sourceName; + + public EntryReference(E entry, String sourceName) { + this(entry, sourceName, null); + } + + public EntryReference(E entry, String sourceName, C context) { + this(entry, sourceName, context, ReferenceTargetType.none()); + } + + public EntryReference(E entry, String sourceName, C context, ReferenceTargetType targetType) { + if (entry == null) { + throw new IllegalArgumentException("Entry cannot be null!"); + } + + this.entry = entry; + this.context = context; + this.targetType = targetType; + + this.sourceName = sourceName != null && !sourceName.isEmpty(); + if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor() && CONSTRUCTOR_NON_NAMES.contains(sourceName)) { + this.sourceName = false; + } + } + + public EntryReference(E entry, C context, EntryReference other) { + this.entry = entry; + this.context = context; + this.sourceName = other.sourceName; + this.targetType = other.targetType; + } + + public ClassEntry getLocationClassEntry() { + if (context != null) { + return context.getContainingClass(); + } + return entry.getContainingClass(); + } + + public boolean isNamed() { + return this.sourceName; + } + + public Entry getNameableEntry() { + if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor()) { + // renaming a constructor really means renaming the class + return entry.getContainingClass(); + } + return entry; + } + + public String getNameableName() { + return getNameableEntry().getName(); + } + + @Override + public int hashCode() { + if (context != null) { + return Objects.hash(entry.hashCode(), context.hashCode()); + } + return entry.hashCode(); + } + + @Override + public boolean equals(Object other) { + return other instanceof EntryReference && equals((EntryReference) other); + } + + public boolean equals(EntryReference other) { + // check entry first + boolean isEntrySame = entry.equals(other.entry); + if (!isEntrySame) { + return false; + } + + // check caller + if (context == null && other.context == null) { + return true; + } else if (context != null && other.context != null) { + return context.equals(other.context); + } + return false; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(entry); + + if (context != null) { + buf.append(" called from "); + buf.append(context); + } + + if (targetType != null && targetType.getKind() != ReferenceTargetType.Kind.NONE) { + buf.append(" on target of type "); + buf.append(targetType); + } + + return buf.toString(); + } + + @Override + public Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + return new EntryReference<>(translator.translate(entry), translator.translate(context), this); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/enigma/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java new file mode 100644 index 00000000..4beab7f8 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.analysis; + +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.analysis.index.ReferenceIndex; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import javax.swing.tree.DefaultMutableTreeNode; + +public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode { + + private final Translator translator; + private FieldEntry entry; + private EntryReference reference; + + public FieldReferenceTreeNode(Translator translator, FieldEntry entry) { + this.translator = translator; + this.entry = entry; + this.reference = null; + } + + private FieldReferenceTreeNode(Translator translator, EntryReference reference) { + this.translator = translator; + this.entry = reference.entry; + this.reference = reference; + } + + @Override + public FieldEntry getEntry() { + return this.entry; + } + + @Override + public EntryReference getReference() { + return this.reference; + } + + @Override + public String toString() { + if (this.reference != null) { + return String.format("%s", translator.translate(this.reference.context)); + } + return translator.translate(entry).toString(); + } + + public void load(JarIndex index, boolean recurse) { + ReferenceIndex referenceIndex = index.getReferenceIndex(); + + // get all the child nodes + if (this.reference == null) { + for (EntryReference reference : referenceIndex.getReferencesToField(this.entry)) { + add(new FieldReferenceTreeNode(translator, reference)); + } + } else { + for (EntryReference reference : referenceIndex.getReferencesToMethod(this.reference.context)) { + add(new MethodReferenceTreeNode(translator, reference)); + } + } + + if (recurse && children != null) { + for (Object node : children) { + if (node instanceof MethodReferenceTreeNode) { + ((MethodReferenceTreeNode) node).load(index, true, false); + } else if (node instanceof FieldReferenceTreeNode) { + ((FieldReferenceTreeNode) node).load(index, true); + } + } + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java b/enigma/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java new file mode 100644 index 00000000..ec8f3237 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java @@ -0,0 +1,154 @@ +package cuchaz.enigma.analysis; + +import cuchaz.enigma.Enigma; +import cuchaz.enigma.analysis.index.EntryIndex; +import cuchaz.enigma.analysis.index.InheritanceIndex; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.entry.ClassDefEntry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.analysis.BasicValue; +import org.objectweb.asm.tree.analysis.SimpleVerifier; + +import java.util.Set; + +public class IndexSimpleVerifier extends SimpleVerifier { + private static final Type OBJECT_TYPE = Type.getType("Ljava/lang/Object;"); + private final EntryIndex entryIndex; + private final InheritanceIndex inheritanceIndex; + + public IndexSimpleVerifier(EntryIndex entryIndex, InheritanceIndex inheritanceIndex) { + super(Enigma.ASM_VERSION, null, null, null, false); + this.entryIndex = entryIndex; + this.inheritanceIndex = inheritanceIndex; + } + + @Override + protected boolean isSubTypeOf(BasicValue value, BasicValue expected) { + Type expectedType = expected.getType(); + Type type = value.getType(); + switch (expectedType.getSort()) { + case Type.INT: + case Type.FLOAT: + case Type.LONG: + case Type.DOUBLE: + return type.equals(expectedType); + case Type.ARRAY: + case Type.OBJECT: + if (type.equals(NULL_TYPE)) { + return true; + } else if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) { + if (isAssignableFrom(expectedType, type)) { + return true; + } else if (isInterface(expectedType)) { + return isAssignableFrom(OBJECT_TYPE, type); + } else { + return false; + } + } else { + return false; + } + default: + throw new AssertionError(); + } + } + + @Override + protected boolean isInterface(Type type) { + AccessFlags classAccess = entryIndex.getClassAccess(new ClassEntry(type.getInternalName())); + if (classAccess != null) { + return classAccess.isInterface(); + } + + Class clazz = getClass(type); + if (clazz != null) { + return clazz.isInterface(); + } + + return false; + } + + @Override + protected Type getSuperClass(Type type) { + ClassDefEntry definition = entryIndex.getDefinition(new ClassEntry(type.getInternalName())); + if (definition != null) { + return Type.getType('L' + definition.getSuperClass().getFullName() + ';'); + } + + Class clazz = getClass(type); + if (clazz != null) { + return Type.getType(clazz.getSuperclass()); + } + + return OBJECT_TYPE; + } + + @Override + protected boolean isAssignableFrom(Type type1, Type type2) { + if (type1.equals(type2)) { + return true; + } + + if (type2.equals(NULL_TYPE)) { + return true; + } + + if (type1.getSort() == Type.ARRAY) { + return type2.getSort() == Type.ARRAY && isAssignableFrom(Type.getType(type1.getDescriptor().substring(1)), Type.getType(type2.getDescriptor().substring(1))); + } + + if (type2.getSort() == Type.ARRAY) { + return type1.equals(OBJECT_TYPE); + } + + if (type1.getSort() == Type.OBJECT && type2.getSort() == Type.OBJECT) { + if (type1.equals(OBJECT_TYPE)) { + return true; + } + + ClassEntry class1 = new ClassEntry(type1.getInternalName()); + ClassEntry class2 = new ClassEntry(type2.getInternalName()); + + if (entryIndex.hasClass(class1) && entryIndex.hasClass(class2)) { + return inheritanceIndex.getAncestors(class2).contains(class1); + } + + Class class1Class = getClass(Type.getType('L' + class1.getFullName() + ';')); + Class class2Class = getClass(Type.getType('L' + class2.getFullName() + ';')); + + if (class1Class == null) { + return true; // missing classes to find out + } + + if (class2Class != null) { + return class1Class.isAssignableFrom(class2Class); + } + + if (entryIndex.hasClass(class2)) { + Set ancestors = inheritanceIndex.getAncestors(class2); + + for (ClassEntry ancestorEntry : ancestors) { + Class ancestor = getClass(Type.getType('L' + ancestorEntry.getFullName() + ';')); + if (ancestor == null || class1Class.isAssignableFrom(ancestor)) { + return true; // assignable, or missing classes to find out + } + } + + return false; + } + + return true; // missing classes to find out + } + + return false; + } + + @Override + protected final Class getClass(Type type) { + try { + return Class.forName(type.getSort() == Type.ARRAY ? type.getDescriptor().replace('/', '.') : type.getClassName(), false, null); + } catch (ClassNotFoundException e) { + return null; + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java b/enigma/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java new file mode 100644 index 00000000..0c2dfd77 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java @@ -0,0 +1,74 @@ +package cuchaz.enigma.analysis; + +import com.google.common.collect.Lists; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import java.util.Collection; +import java.util.List; + +public class IndexTreeBuilder { + private final JarIndex index; + + public IndexTreeBuilder(JarIndex index) { + this.index = index; + } + + public ClassInheritanceTreeNode buildClassInheritance(Translator translator, ClassEntry obfClassEntry) { + // get the root node + List ancestry = Lists.newArrayList(); + ancestry.add(obfClassEntry.getFullName()); + for (ClassEntry classEntry : index.getInheritanceIndex().getAncestors(obfClassEntry)) { + ancestry.add(classEntry.getFullName()); + } + + ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(translator, ancestry.get(ancestry.size() - 1)); + + // expand all children recursively + rootNode.load(index.getInheritanceIndex(), true); + + return rootNode; + } + + public ClassImplementationsTreeNode buildClassImplementations(Translator translator, ClassEntry obfClassEntry) { + if (index.getInheritanceIndex().isParent(obfClassEntry)) { + ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(translator, obfClassEntry); + node.load(index); + return node; + } + return null; + } + + public MethodInheritanceTreeNode buildMethodInheritance(Translator translator, MethodEntry obfMethodEntry) { + MethodEntry resolvedEntry = index.getEntryResolver().resolveFirstEntry(obfMethodEntry, ResolutionStrategy.RESOLVE_ROOT); + + // make a root node at the base + MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode( + translator, resolvedEntry, + index.getEntryIndex().hasMethod(resolvedEntry) + ); + + // expand the full tree + rootNode.load(index); + + return rootNode; + } + + public List buildMethodImplementations(Translator translator, MethodEntry obfMethodEntry) { + EntryResolver resolver = index.getEntryResolver(); + Collection resolvedEntries = resolver.resolveEntry(obfMethodEntry, ResolutionStrategy.RESOLVE_ROOT); + + List nodes = Lists.newArrayList(); + for (MethodEntry resolvedEntry : resolvedEntries) { + MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(translator, resolvedEntry); + node.load(index); + nodes.add(node); + } + + return nodes; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java b/enigma/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java new file mode 100644 index 00000000..fad10bf7 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java @@ -0,0 +1,130 @@ +package cuchaz.enigma.analysis; + +import cuchaz.enigma.Enigma; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.analysis.AnalyzerException; +import org.objectweb.asm.tree.analysis.Interpreter; +import org.objectweb.asm.tree.analysis.Value; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class InterpreterPair extends Interpreter> { + private final Interpreter left; + private final Interpreter right; + + public InterpreterPair(Interpreter left, Interpreter right) { + super(Enigma.ASM_VERSION); + this.left = left; + this.right = right; + } + + @Override + public PairValue newValue(Type type) { + return pair( + left.newValue(type), + right.newValue(type) + ); + } + + @Override + public PairValue newOperation(AbstractInsnNode insn) throws AnalyzerException { + return pair( + left.newOperation(insn), + right.newOperation(insn) + ); + } + + @Override + public PairValue copyOperation(AbstractInsnNode insn, PairValue value) throws AnalyzerException { + return pair( + left.copyOperation(insn, value.left), + right.copyOperation(insn, value.right) + ); + } + + @Override + public PairValue unaryOperation(AbstractInsnNode insn, PairValue value) throws AnalyzerException { + return pair( + left.unaryOperation(insn, value.left), + right.unaryOperation(insn, value.right) + ); + } + + @Override + public PairValue binaryOperation(AbstractInsnNode insn, PairValue value1, PairValue value2) throws AnalyzerException { + return pair( + left.binaryOperation(insn, value1.left, value2.left), + right.binaryOperation(insn, value1.right, value2.right) + ); + } + + @Override + public PairValue ternaryOperation(AbstractInsnNode insn, PairValue value1, PairValue value2, PairValue value3) throws AnalyzerException { + return pair( + left.ternaryOperation(insn, value1.left, value2.left, value3.left), + right.ternaryOperation(insn, value1.right, value2.right, value3.right) + ); + } + + @Override + public PairValue naryOperation(AbstractInsnNode insn, List> values) throws AnalyzerException { + return pair( + left.naryOperation(insn, values.stream().map(v -> v.left).collect(Collectors.toList())), + right.naryOperation(insn, values.stream().map(v -> v.right).collect(Collectors.toList())) + ); + } + + @Override + public void returnOperation(AbstractInsnNode insn, PairValue value, PairValue expected) throws AnalyzerException { + left.returnOperation(insn, value.left, expected.left); + right.returnOperation(insn, value.right, expected.right); + } + + @Override + public PairValue merge(PairValue value1, PairValue value2) { + return pair( + left.merge(value1.left, value2.left), + right.merge(value1.right, value2.right) + ); + } + + private PairValue pair(V left, W right) { + if (left == null && right == null) { + return null; + } + + return new PairValue<>(left, right); + } + + public static final class PairValue implements Value { + public final V left; + public final W right; + + public PairValue(V left, W right) { + if (left == null && right == null) { + throw new IllegalArgumentException("should use null rather than pair of nulls"); + } + + this.left = left; + this.right = right; + } + + @Override + public boolean equals(Object o) { + return o instanceof InterpreterPair.PairValue && Objects.equals(left, ((PairValue) o).left) && Objects.equals(right, ((PairValue) o).right); + } + + @Override + public int hashCode() { + return left.hashCode() * 31 + right.hashCode(); + } + + @Override + public int getSize() { + return (left == null ? right : left).getSize(); + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/enigma/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java new file mode 100644 index 00000000..b09f7ac6 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.analysis; + +import com.google.common.collect.Lists; +import cuchaz.enigma.analysis.index.EntryIndex; +import cuchaz.enigma.analysis.index.InheritanceIndex; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import javax.swing.tree.DefaultMutableTreeNode; +import java.util.Collection; +import java.util.List; + +public class MethodImplementationsTreeNode extends DefaultMutableTreeNode { + + private final Translator translator; + private MethodEntry entry; + + public MethodImplementationsTreeNode(Translator translator, MethodEntry entry) { + this.translator = translator; + if (entry == null) { + throw new IllegalArgumentException("Entry cannot be null!"); + } + + this.entry = entry; + } + + public static MethodImplementationsTreeNode findNode(MethodImplementationsTreeNode node, MethodEntry entry) { + // is this the node? + if (node.getMethodEntry().equals(entry)) { + return node; + } + + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + MethodImplementationsTreeNode foundNode = findNode((MethodImplementationsTreeNode) node.getChildAt(i), entry); + if (foundNode != null) { + return foundNode; + } + } + return null; + } + + public MethodEntry getMethodEntry() { + return this.entry; + } + + @Override + public String toString() { + MethodEntry translatedEntry = translator.translate(entry); + String className = translatedEntry.getParent().getFullName(); + String methodName = translatedEntry.getName(); + return className + "." + methodName + "()"; + } + + public void load(JarIndex index) { + // get all method implementations + List nodes = Lists.newArrayList(); + EntryIndex entryIndex = index.getEntryIndex(); + InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); + + Collection descendants = inheritanceIndex.getDescendants(entry.getParent()); + for (ClassEntry inheritor : descendants) { + MethodEntry methodEntry = entry.withParent(inheritor); + if (entryIndex.hasMethod(methodEntry)) { + nodes.add(new MethodImplementationsTreeNode(translator, methodEntry)); + } + } + + // add them to this node + nodes.forEach(this::add); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/enigma/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java new file mode 100644 index 00000000..e77b5cce --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.analysis; + +import cuchaz.enigma.analysis.index.EntryIndex; +import cuchaz.enigma.analysis.index.InheritanceIndex; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import javax.swing.tree.DefaultMutableTreeNode; + +public class MethodInheritanceTreeNode extends DefaultMutableTreeNode { + + private final Translator translator; + private MethodEntry entry; + private boolean implemented; + + public MethodInheritanceTreeNode(Translator translator, MethodEntry entry, boolean implemented) { + this.translator = translator; + this.entry = entry; + this.implemented = implemented; + } + + public static MethodInheritanceTreeNode findNode(MethodInheritanceTreeNode node, MethodEntry entry) { + // is this the node? + if (node.getMethodEntry().equals(entry)) { + return node; + } + + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + MethodInheritanceTreeNode foundNode = findNode((MethodInheritanceTreeNode) node.getChildAt(i), entry); + if (foundNode != null) { + return foundNode; + } + } + return null; + } + + public MethodEntry getMethodEntry() { + return this.entry; + } + + public boolean isImplemented() { + return this.implemented; + } + + @Override + public String toString() { + MethodEntry translatedEntry = translator.translate(entry); + String className = translatedEntry.getContainingClass().getFullName(); + + if (!this.implemented) { + return className; + } else { + String methodName = translatedEntry.getName(); + return className + "." + methodName + "()"; + } + } + + /** + * Returns true if there is sub-node worthy to display. + */ + public boolean load(JarIndex index) { + // get all the child nodes + EntryIndex entryIndex = index.getEntryIndex(); + InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); + + boolean ret = false; + for (ClassEntry inheritorEntry : inheritanceIndex.getChildren(this.entry.getParent())) { + MethodEntry methodEntry = new MethodEntry(inheritorEntry, this.entry.getName(), this.entry.getDesc()); + + MethodInheritanceTreeNode node = new MethodInheritanceTreeNode(translator, methodEntry, entryIndex.hasMethod(methodEntry)); + boolean childOverride = node.load(index); + + if (childOverride || node.implemented) { + this.add(node); + ret = true; + } + } + + return ret; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java b/enigma/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java new file mode 100644 index 00000000..81171038 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java @@ -0,0 +1,19 @@ +package cuchaz.enigma.analysis; + +import org.objectweb.asm.tree.MethodNode; + +import java.util.function.Consumer; + +public class MethodNodeWithAction extends MethodNode { + private final Consumer action; + + public MethodNodeWithAction(int api, int access, String name, String descriptor, String signature, String[] exceptions, Consumer action) { + super(api, access, name, descriptor, signature, exceptions); + this.action = action; + } + + @Override + public void visitEnd() { + action.accept(this); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java b/enigma/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java new file mode 100644 index 00000000..8995eb5c --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.analysis; + +import com.google.common.collect.Sets; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.analysis.index.ReferenceIndex; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Set; + +public class MethodReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode { + + private final Translator translator; + private MethodEntry entry; + private EntryReference reference; + + public MethodReferenceTreeNode(Translator translator, MethodEntry entry) { + this.translator = translator; + this.entry = entry; + this.reference = null; + } + + public MethodReferenceTreeNode(Translator translator, EntryReference reference) { + this.translator = translator; + this.entry = reference.entry; + this.reference = reference; + } + + @Override + public MethodEntry getEntry() { + return this.entry; + } + + @Override + public EntryReference getReference() { + return this.reference; + } + + @Override + public String toString() { + if (this.reference != null) { + return String.format("%s", translator.translate(this.reference.context)); + } + return translator.translate(this.entry).getName(); + } + + public void load(JarIndex index, boolean recurse, boolean recurseMethod) { + // get all the child nodes + Collection> references = getReferences(index, recurseMethod); + + for (EntryReference reference : references) { + add(new MethodReferenceTreeNode(translator, reference)); + } + + if (recurse && this.children != null) { + for (Object child : this.children) { + if (child instanceof MethodReferenceTreeNode) { + MethodReferenceTreeNode node = (MethodReferenceTreeNode) child; + + // don't recurse into ancestor + Set> ancestors = Sets.newHashSet(); + TreeNode n = node; + while (n.getParent() != null) { + n = n.getParent(); + if (n instanceof MethodReferenceTreeNode) { + ancestors.add(((MethodReferenceTreeNode) n).getEntry()); + } + } + if (ancestors.contains(node.getEntry())) { + continue; + } + + node.load(index, true, false); + } + } + } + } + + private Collection> getReferences(JarIndex index, boolean recurseMethod) { + ReferenceIndex referenceIndex = index.getReferenceIndex(); + + if (recurseMethod) { + Collection> references = new ArrayList<>(); + + EntryResolver entryResolver = index.getEntryResolver(); + for (MethodEntry methodEntry : entryResolver.resolveEquivalentMethods(entry)) { + references.addAll(referenceIndex.getReferencesToMethod(methodEntry)); + } + + return references; + } else { + return referenceIndex.getReferencesToMethod(entry); + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/ReferenceTargetType.java b/enigma/src/main/java/cuchaz/enigma/analysis/ReferenceTargetType.java new file mode 100644 index 00000000..5b19d189 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/ReferenceTargetType.java @@ -0,0 +1,74 @@ +package cuchaz.enigma.analysis; + +import cuchaz.enigma.translation.representation.entry.ClassEntry; + +public abstract class ReferenceTargetType { + private static final None NONE = new None(); + private static final Uninitialized UNINITIALIZED = new Uninitialized(); + + public abstract Kind getKind(); + + public static None none() { + return NONE; + } + + public static Uninitialized uninitialized() { + return UNINITIALIZED; + } + + public static ClassType classType(ClassEntry name) { + return new ClassType(name); + } + + public enum Kind { + NONE, + UNINITIALIZED, + CLASS_TYPE + } + + public static class None extends ReferenceTargetType { + @Override + public Kind getKind() { + return Kind.NONE; + } + + @Override + public String toString() { + return "(none)"; + } + } + + public static class Uninitialized extends ReferenceTargetType { + @Override + public Kind getKind() { + return Kind.UNINITIALIZED; + } + + @Override + public String toString() { + return "(uninitialized)"; + } + } + + public static class ClassType extends ReferenceTargetType { + private final ClassEntry entry; + + private ClassType(ClassEntry entry) { + this.entry = entry; + } + + public ClassEntry getEntry() { + return entry; + } + + @Override + public Kind getKind() { + return Kind.CLASS_TYPE; + } + + @Override + public String toString() { + return entry.toString(); + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java b/enigma/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java new file mode 100644 index 00000000..c0a3a754 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.analysis; + +import cuchaz.enigma.translation.representation.entry.Entry; + +public interface ReferenceTreeNode, C extends Entry> { + E getEntry(); + + EntryReference getReference(); +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java new file mode 100644 index 00000000..a4b1aac9 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java @@ -0,0 +1,156 @@ +package cuchaz.enigma.analysis.index; + +import com.google.common.collect.Maps; +import cuchaz.enigma.translation.representation.AccessFlags; +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.MethodDefEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import javax.annotation.Nullable; +import java.util.*; + +public class BridgeMethodIndex implements JarIndexer { + private final EntryIndex entryIndex; + private final InheritanceIndex inheritanceIndex; + private final ReferenceIndex referenceIndex; + + private final Map bridgeToSpecialized = Maps.newHashMap(); + private final Map specializedToBridge = Maps.newHashMap(); + + public BridgeMethodIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex) { + this.entryIndex = entryIndex; + this.inheritanceIndex = inheritanceIndex; + this.referenceIndex = referenceIndex; + } + + public void findBridgeMethods() { + // look for access and bridged methods + for (MethodEntry methodEntry : entryIndex.getMethods()) { + MethodDefEntry methodDefEntry = (MethodDefEntry) methodEntry; + + AccessFlags access = methodDefEntry.getAccess(); + if (access == null || !access.isSynthetic()) { + continue; + } + + indexSyntheticMethod(methodDefEntry, access); + } + } + + @Override + public void processIndex(JarIndex index) { + Map copiedAccessToBridge = new HashMap<>(specializedToBridge); + + for (Map.Entry entry : copiedAccessToBridge.entrySet()) { + MethodEntry specializedEntry = entry.getKey(); + MethodEntry bridgeEntry = entry.getValue(); + if (bridgeEntry.getName().equals(specializedEntry.getName())) { + continue; + } + + MethodEntry renamedSpecializedEntry = specializedEntry.withName(bridgeEntry.getName()); + specializedToBridge.put(renamedSpecializedEntry, specializedToBridge.get(specializedEntry)); + } + } + + private void indexSyntheticMethod(MethodDefEntry syntheticMethod, AccessFlags access) { + MethodEntry specializedMethod = findSpecializedMethod(syntheticMethod); + if (specializedMethod == null) { + return; + } + + if (access.isBridge() || isPotentialBridge(syntheticMethod, specializedMethod)) { + bridgeToSpecialized.put(syntheticMethod, specializedMethod); + specializedToBridge.put(specializedMethod, syntheticMethod); + } + } + + private MethodEntry findSpecializedMethod(MethodEntry method) { + // we want to find all compiler-added methods that directly call another with no processing + + // get all the methods that we call + final Collection referencedMethods = referenceIndex.getMethodsReferencedBy(method); + + // is there just one? + if (referencedMethods.size() != 1) { + return null; + } + + return referencedMethods.stream().findFirst().orElse(null); + } + + private boolean isPotentialBridge(MethodDefEntry bridgeMethod, MethodEntry specializedMethod) { + // Bridge methods only exist for inheritance purposes, if we're private, final, or static, we cannot be inherited + AccessFlags bridgeAccess = bridgeMethod.getAccess(); + if (bridgeAccess.isPrivate() || bridgeAccess.isFinal() || bridgeAccess.isStatic()) { + return false; + } + + MethodDescriptor bridgeDesc = bridgeMethod.getDesc(); + MethodDescriptor specializedDesc = specializedMethod.getDesc(); + List bridgeArguments = bridgeDesc.getArgumentDescs(); + List specializedArguments = specializedDesc.getArgumentDescs(); + + // A bridge method will always have the same number of arguments + if (bridgeArguments.size() != specializedArguments.size()) { + return false; + } + + // Check that all argument types are bridge-compatible + for (int i = 0; i < bridgeArguments.size(); i++) { + if (!areTypesBridgeCompatible(bridgeArguments.get(i), specializedArguments.get(i))) { + return false; + } + } + + // Check that the return type is bridge-compatible + return areTypesBridgeCompatible(bridgeDesc.getReturnDesc(), specializedDesc.getReturnDesc()); + } + + private boolean areTypesBridgeCompatible(TypeDescriptor bridgeDesc, TypeDescriptor specializedDesc) { + if (bridgeDesc.equals(specializedDesc)) { + return true; + } + + // Either the descs will be equal, or they are both types and different through a generic + if (bridgeDesc.isType() && specializedDesc.isType()) { + ClassEntry bridgeType = bridgeDesc.getTypeEntry(); + ClassEntry accessedType = specializedDesc.getTypeEntry(); + + // If the given types are completely unrelated to each other, this can't be bridge compatible + InheritanceIndex.Relation relation = inheritanceIndex.computeClassRelation(accessedType, bridgeType); + return relation != InheritanceIndex.Relation.UNRELATED; + } + + return false; + } + + public boolean isBridgeMethod(MethodEntry entry) { + return bridgeToSpecialized.containsKey(entry); + } + + public boolean isSpecializedMethod(MethodEntry entry) { + return specializedToBridge.containsKey(entry); + } + + @Nullable + public MethodEntry getBridgeFromSpecialized(MethodEntry specialized) { + return specializedToBridge.get(specialized); + } + + public MethodEntry getSpecializedFromBridge(MethodEntry bridge) { + return bridgeToSpecialized.get(bridge); + } + + /** Includes "renamed specialized -> bridge" entries. */ + public Map getSpecializedToBridge() { + return Collections.unmodifiableMap(specializedToBridge); + } + + /** Only "bridge -> original name" entries. **/ + public Map getBridgeToSpecialized() { + return Collections.unmodifiableMap(bridgeToSpecialized); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java new file mode 100644 index 00000000..9a2726e5 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java @@ -0,0 +1,102 @@ +package cuchaz.enigma.analysis.index; + +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.entry.*; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class EntryIndex implements JarIndexer { + private Map classes = new HashMap<>(); + private Map fields = new HashMap<>(); + private Map methods = new HashMap<>(); + private Map definitions = new HashMap<>(); + + @Override + public void indexClass(ClassDefEntry classEntry) { + definitions.put(classEntry, classEntry); + classes.put(classEntry, classEntry.getAccess()); + } + + @Override + public void indexMethod(MethodDefEntry methodEntry) { + methods.put(methodEntry, methodEntry.getAccess()); + } + + @Override + public void indexField(FieldDefEntry fieldEntry) { + fields.put(fieldEntry, fieldEntry.getAccess()); + } + + public boolean hasClass(ClassEntry entry) { + return classes.containsKey(entry); + } + + public boolean hasMethod(MethodEntry entry) { + return methods.containsKey(entry); + } + + public boolean hasField(FieldEntry entry) { + return fields.containsKey(entry); + } + + public boolean hasEntry(Entry entry) { + if (entry instanceof ClassEntry) { + return hasClass((ClassEntry) entry); + } else if (entry instanceof MethodEntry) { + return hasMethod((MethodEntry) entry); + } else if (entry instanceof FieldEntry) { + return hasField((FieldEntry) entry); + } else if (entry instanceof LocalVariableEntry) { + return hasMethod(((LocalVariableEntry) entry).getParent()); + } + + return false; + } + + @Nullable + public AccessFlags getMethodAccess(MethodEntry entry) { + return methods.get(entry); + } + + @Nullable + public AccessFlags getFieldAccess(FieldEntry entry) { + return fields.get(entry); + } + + @Nullable + public AccessFlags getClassAccess(ClassEntry entry) { + return classes.get(entry); + } + + @Nullable + public AccessFlags getEntryAccess(Entry entry) { + if (entry instanceof MethodEntry) { + return getMethodAccess((MethodEntry) entry); + } else if (entry instanceof FieldEntry) { + return getFieldAccess((FieldEntry) entry); + } else if (entry instanceof LocalVariableEntry) { + return getMethodAccess(((LocalVariableEntry) entry).getParent()); + } + + return null; + } + + public ClassDefEntry getDefinition(ClassEntry entry) { + return definitions.get(entry); + } + + public Collection getClasses() { + return classes.keySet(); + } + + public Collection getMethods() { + return methods.keySet(); + } + + public Collection getFields() { + return fields.keySet(); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java new file mode 100644 index 00000000..f9cb23ce --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java @@ -0,0 +1,40 @@ +package cuchaz.enigma.analysis.index; + +import cuchaz.enigma.translation.representation.entry.ClassDefEntry; +import cuchaz.enigma.translation.representation.entry.FieldDefEntry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; + +public class IndexClassVisitor extends ClassVisitor { + private final JarIndexer indexer; + private ClassDefEntry classEntry; + + public IndexClassVisitor(JarIndex indexer, int api) { + super(api); + this.indexer = indexer; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + classEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces); + indexer.indexClass(classEntry); + + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + indexer.indexField(FieldDefEntry.parse(classEntry, access, name, desc, signature)); + + return super.visitField(access, name, desc, signature, value); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + indexer.indexMethod(MethodDefEntry.parse(classEntry, access, name, desc, signature)); + + return super.visitMethod(access, name, desc, signature, exceptions); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java new file mode 100644 index 00000000..f3d419ee --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java @@ -0,0 +1,180 @@ +package cuchaz.enigma.analysis.index; + +import cuchaz.enigma.analysis.IndexSimpleVerifier; +import cuchaz.enigma.analysis.InterpreterPair; +import cuchaz.enigma.analysis.MethodNodeWithAction; +import cuchaz.enigma.analysis.ReferenceTargetType; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.Lambda; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.Signature; +import cuchaz.enigma.translation.representation.entry.*; +import org.objectweb.asm.*; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.InvokeDynamicInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.analysis.*; + +import java.util.List; +import java.util.stream.Collectors; + +public class IndexReferenceVisitor extends ClassVisitor { + private final JarIndexer indexer; + private final EntryIndex entryIndex; + private final InheritanceIndex inheritanceIndex; + private ClassEntry classEntry; + private String className; + + public IndexReferenceVisitor(JarIndexer indexer, EntryIndex entryIndex, InheritanceIndex inheritanceIndex, int api) { + super(api); + this.indexer = indexer; + this.entryIndex = entryIndex; + this.inheritanceIndex = inheritanceIndex; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + classEntry = new ClassEntry(name); + className = name; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodDefEntry entry = new MethodDefEntry(classEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access)); + return new MethodNodeWithAction(api, access, name, desc, signature, exceptions, methodNode -> { + try { + new Analyzer<>(new MethodInterpreter(entry, indexer, entryIndex, inheritanceIndex)).analyze(className, methodNode); + } catch (AnalyzerException e) { + throw new RuntimeException(e); + } + }); + } + + private static class MethodInterpreter extends InterpreterPair { + private final MethodDefEntry callerEntry; + private JarIndexer indexer; + + public MethodInterpreter(MethodDefEntry callerEntry, JarIndexer indexer, EntryIndex entryIndex, InheritanceIndex inheritanceIndex) { + super(new IndexSimpleVerifier(entryIndex, inheritanceIndex), new SourceInterpreter()); + this.callerEntry = callerEntry; + this.indexer = indexer; + } + + @Override + public PairValue newOperation(AbstractInsnNode insn) throws AnalyzerException { + if (insn.getOpcode() == Opcodes.GETSTATIC) { + FieldInsnNode field = (FieldInsnNode) insn; + indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), ReferenceTargetType.none()); + } + + return super.newOperation(insn); + } + + @Override + public PairValue unaryOperation(AbstractInsnNode insn, PairValue value) throws AnalyzerException { + if (insn.getOpcode() == Opcodes.PUTSTATIC) { + FieldInsnNode field = (FieldInsnNode) insn; + indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), ReferenceTargetType.none()); + } + + if (insn.getOpcode() == Opcodes.GETFIELD) { + FieldInsnNode field = (FieldInsnNode) insn; + indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), getReferenceTargetType(value, insn)); + } + + return super.unaryOperation(insn, value); + } + + + @Override + public PairValue binaryOperation(AbstractInsnNode insn, PairValue value1, PairValue value2) throws AnalyzerException { + if (insn.getOpcode() == Opcodes.PUTFIELD) { + FieldInsnNode field = (FieldInsnNode) insn; + FieldEntry fieldEntry = FieldEntry.parse(field.owner, field.name, field.desc); + indexer.indexFieldReference(callerEntry, fieldEntry, ReferenceTargetType.none()); + } + + return super.binaryOperation(insn, value1, value2); + } + + @Override + public PairValue naryOperation(AbstractInsnNode insn, List> values) throws AnalyzerException { + if (insn.getOpcode() == Opcodes.INVOKEINTERFACE || insn.getOpcode() == Opcodes.INVOKESPECIAL || insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { + MethodInsnNode methodInsn = (MethodInsnNode) insn; + indexer.indexMethodReference(callerEntry, MethodEntry.parse(methodInsn.owner, methodInsn.name, methodInsn.desc), getReferenceTargetType(values.get(0), insn)); + } + + if (insn.getOpcode() == Opcodes.INVOKESTATIC) { + MethodInsnNode methodInsn = (MethodInsnNode) insn; + indexer.indexMethodReference(callerEntry, MethodEntry.parse(methodInsn.owner, methodInsn.name, methodInsn.desc), ReferenceTargetType.none()); + } + + if (insn.getOpcode() == Opcodes.INVOKEDYNAMIC) { + InvokeDynamicInsnNode invokeDynamicInsn = (InvokeDynamicInsnNode) insn; + List args = values.stream().map(v -> v.right.insns.stream().findFirst().orElseThrow(AssertionError::new)).collect(Collectors.toList()); + + if ("java/lang/invoke/LambdaMetafactory".equals(invokeDynamicInsn.bsm.getOwner()) && "metafactory".equals(invokeDynamicInsn.bsm.getName())) { + Type samMethodType = (Type) invokeDynamicInsn.bsmArgs[0]; + Handle implMethod = (Handle) invokeDynamicInsn.bsmArgs[1]; + Type instantiatedMethodType = (Type) invokeDynamicInsn.bsmArgs[2]; + + ReferenceTargetType targetType; + if (implMethod.getTag() != Opcodes.H_GETSTATIC && implMethod.getTag() != Opcodes.H_PUTFIELD && implMethod.getTag() != Opcodes.H_INVOKESTATIC) { + if (instantiatedMethodType.getArgumentTypes().length < Type.getArgumentTypes(implMethod.getDesc()).length) { + targetType = getReferenceTargetType(values.get(0), insn); + } else { + targetType = ReferenceTargetType.none(); // no "this" argument + } + } else { + targetType = ReferenceTargetType.none(); + } + + indexer.indexLambda(callerEntry, new Lambda( + invokeDynamicInsn.name, + new MethodDescriptor(invokeDynamicInsn.desc), + new MethodDescriptor(samMethodType.getDescriptor()), + getHandleEntry(implMethod), + new MethodDescriptor(instantiatedMethodType.getDescriptor()) + ), targetType); + } + } + + return super.naryOperation(insn, values); + } + + private ReferenceTargetType getReferenceTargetType(PairValue target, AbstractInsnNode insn) throws AnalyzerException { + if (target.left == BasicValue.UNINITIALIZED_VALUE) { + return ReferenceTargetType.uninitialized(); + } + + if (target.left.getType().getSort() == Type.OBJECT) { + return ReferenceTargetType.classType(new ClassEntry(target.left.getType().getInternalName())); + } + + if (target.left.getType().getSort() == Type.ARRAY) { + return ReferenceTargetType.classType(new ClassEntry("java/lang/Object")); + } + + throw new AnalyzerException(insn, "called method on or accessed field of non-object type"); + } + + private static ParentedEntry getHandleEntry(Handle handle) { + switch (handle.getTag()) { + case Opcodes.H_GETFIELD: + case Opcodes.H_GETSTATIC: + case Opcodes.H_PUTFIELD: + case Opcodes.H_PUTSTATIC: + return FieldEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); + case Opcodes.H_INVOKEINTERFACE: + case Opcodes.H_INVOKESPECIAL: + case Opcodes.H_INVOKESTATIC: + case Opcodes.H_INVOKEVIRTUAL: + case Opcodes.H_NEWINVOKESPECIAL: + return MethodEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); + } + + throw new RuntimeException("Invalid handle tag " + handle.getTag()); + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java new file mode 100644 index 00000000..1ab2abdf --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.analysis.index; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import cuchaz.enigma.translation.representation.entry.ClassDefEntry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; + +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; + +public class InheritanceIndex implements JarIndexer { + private final EntryIndex entryIndex; + + private Multimap classParents = HashMultimap.create(); + private Multimap classChildren = HashMultimap.create(); + + public InheritanceIndex(EntryIndex entryIndex) { + this.entryIndex = entryIndex; + } + + @Override + public void indexClass(ClassDefEntry classEntry) { + if (classEntry.isJre()) { + return; + } + + ClassEntry superClass = classEntry.getSuperClass(); + if (superClass != null && !superClass.getName().equals("java/lang/Object")) { + indexParent(classEntry, superClass); + } + + for (ClassEntry interfaceEntry : classEntry.getInterfaces()) { + indexParent(classEntry, interfaceEntry); + } + } + + private void indexParent(ClassEntry childEntry, ClassEntry parentEntry) { + classParents.put(childEntry, parentEntry); + classChildren.put(parentEntry, childEntry); + } + + public Collection getParents(ClassEntry classEntry) { + return classParents.get(classEntry); + } + + public Collection getChildren(ClassEntry classEntry) { + return classChildren.get(classEntry); + } + + public Collection getDescendants(ClassEntry classEntry) { + Collection descendants = new HashSet<>(); + + LinkedList descendantQueue = new LinkedList<>(); + descendantQueue.push(classEntry); + + while (!descendantQueue.isEmpty()) { + ClassEntry descendant = descendantQueue.pop(); + Collection children = getChildren(descendant); + + children.forEach(descendantQueue::push); + descendants.addAll(children); + } + + return descendants; + } + + public Set getAncestors(ClassEntry classEntry) { + Set ancestors = Sets.newHashSet(); + + LinkedList ancestorQueue = new LinkedList<>(); + ancestorQueue.push(classEntry); + + while (!ancestorQueue.isEmpty()) { + ClassEntry ancestor = ancestorQueue.pop(); + Collection parents = getParents(ancestor); + + parents.forEach(ancestorQueue::push); + ancestors.addAll(parents); + } + + return ancestors; + } + + public Relation computeClassRelation(ClassEntry classEntry, ClassEntry potentialAncestor) { + if (potentialAncestor.getName().equals("java/lang/Object")) return Relation.RELATED; + if (!entryIndex.hasClass(classEntry)) return Relation.UNKNOWN; + + for (ClassEntry ancestor : getAncestors(classEntry)) { + if (potentialAncestor.equals(ancestor)) { + return Relation.RELATED; + } else if (!entryIndex.hasClass(ancestor)) { + return Relation.UNKNOWN; + } + } + + return Relation.UNRELATED; + } + + public boolean isParent(ClassEntry classEntry) { + return classChildren.containsKey(classEntry); + } + + public boolean hasParents(ClassEntry classEntry) { + Collection parents = classParents.get(classEntry); + return parents != null && !parents.isEmpty(); + } + + public enum Relation { + RELATED, + UNRELATED, + UNKNOWN + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java new file mode 100644 index 00000000..de3f5f57 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java @@ -0,0 +1,170 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.analysis.index; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import cuchaz.enigma.Enigma; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.analysis.ClassCache; +import cuchaz.enigma.analysis.ReferenceTargetType; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.IndexEntryResolver; +import cuchaz.enigma.translation.representation.Lambda; +import cuchaz.enigma.translation.representation.entry.*; +import cuchaz.enigma.utils.I18n; + +import org.objectweb.asm.ClassReader; + +import java.util.Arrays; +import java.util.Collection; + +public class JarIndex implements JarIndexer { + private final EntryIndex entryIndex; + private final InheritanceIndex inheritanceIndex; + private final ReferenceIndex referenceIndex; + private final BridgeMethodIndex bridgeMethodIndex; + private final PackageVisibilityIndex packageVisibilityIndex; + private final EntryResolver entryResolver; + + private final Collection indexers; + + private final Multimap methodImplementations = HashMultimap.create(); + + public JarIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex, PackageVisibilityIndex packageVisibilityIndex) { + this.entryIndex = entryIndex; + this.inheritanceIndex = inheritanceIndex; + this.referenceIndex = referenceIndex; + this.bridgeMethodIndex = bridgeMethodIndex; + this.packageVisibilityIndex = packageVisibilityIndex; + this.indexers = Arrays.asList(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); + this.entryResolver = new IndexEntryResolver(this); + } + + public static JarIndex empty() { + EntryIndex entryIndex = new EntryIndex(); + InheritanceIndex inheritanceIndex = new InheritanceIndex(entryIndex); + ReferenceIndex referenceIndex = new ReferenceIndex(); + BridgeMethodIndex bridgeMethodIndex = new BridgeMethodIndex(entryIndex, inheritanceIndex, referenceIndex); + PackageVisibilityIndex packageVisibilityIndex = new PackageVisibilityIndex(); + return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); + } + + public void indexJar(ClassCache classCache, ProgressListener progress) { + progress.init(4, I18n.translate("progress.jar.indexing")); + + progress.step(1, I18n.translate("progress.jar.indexing.entries")); + classCache.visit(() -> new IndexClassVisitor(this, Enigma.ASM_VERSION), ClassReader.SKIP_CODE); + + progress.step(2, I18n.translate("progress.jar.indexing.references")); + classCache.visit(() -> new IndexReferenceVisitor(this, entryIndex, inheritanceIndex, Enigma.ASM_VERSION), 0); + + progress.step(3, I18n.translate("progress.jar.indexing.methods")); + bridgeMethodIndex.findBridgeMethods(); + + progress.step(4, I18n.translate("progress.jar.indexing.process")); + processIndex(this); + } + + @Override + public void processIndex(JarIndex index) { + indexers.forEach(indexer -> indexer.processIndex(index)); + } + + @Override + public void indexClass(ClassDefEntry classEntry) { + if (classEntry.isJre()) { + return; + } + + for (ClassEntry interfaceEntry : classEntry.getInterfaces()) { + if (classEntry.equals(interfaceEntry)) { + throw new IllegalArgumentException("Class cannot be its own interface! " + classEntry); + } + } + + indexers.forEach(indexer -> indexer.indexClass(classEntry)); + } + + @Override + public void indexField(FieldDefEntry fieldEntry) { + if (fieldEntry.getParent().isJre()) { + return; + } + + indexers.forEach(indexer -> indexer.indexField(fieldEntry)); + } + + @Override + public void indexMethod(MethodDefEntry methodEntry) { + if (methodEntry.getParent().isJre()) { + return; + } + + indexers.forEach(indexer -> indexer.indexMethod(methodEntry)); + + if (!methodEntry.isConstructor()) { + methodImplementations.put(methodEntry.getParent().getFullName(), methodEntry); + } + } + + @Override + public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) { + if (callerEntry.getParent().isJre()) { + return; + } + + indexers.forEach(indexer -> indexer.indexMethodReference(callerEntry, referencedEntry, targetType)); + } + + @Override + public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) { + if (callerEntry.getParent().isJre()) { + return; + } + + indexers.forEach(indexer -> indexer.indexFieldReference(callerEntry, referencedEntry, targetType)); + } + + @Override + public void indexLambda(MethodDefEntry callerEntry, Lambda lambda, ReferenceTargetType targetType) { + if (callerEntry.getParent().isJre()) { + return; + } + + indexers.forEach(indexer -> indexer.indexLambda(callerEntry, lambda, targetType)); + } + + public EntryIndex getEntryIndex() { + return entryIndex; + } + + public InheritanceIndex getInheritanceIndex() { + return this.inheritanceIndex; + } + + public ReferenceIndex getReferenceIndex() { + return referenceIndex; + } + + public BridgeMethodIndex getBridgeMethodIndex() { + return bridgeMethodIndex; + } + + public PackageVisibilityIndex getPackageVisibilityIndex() { + return packageVisibilityIndex; + } + + public EntryResolver getEntryResolver() { + return entryResolver; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java new file mode 100644 index 00000000..f17e7c98 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java @@ -0,0 +1,28 @@ +package cuchaz.enigma.analysis.index; + +import cuchaz.enigma.analysis.ReferenceTargetType; +import cuchaz.enigma.translation.representation.Lambda; +import cuchaz.enigma.translation.representation.entry.*; + +public interface JarIndexer { + default void indexClass(ClassDefEntry classEntry) { + } + + default void indexField(FieldDefEntry fieldEntry) { + } + + default void indexMethod(MethodDefEntry methodEntry) { + } + + default void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) { + } + + default void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) { + } + + default void indexLambda(MethodDefEntry callerEntry, Lambda lambda, ReferenceTargetType targetType) { + } + + default void processIndex(JarIndex index) { + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java new file mode 100644 index 00000000..64de5f37 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java @@ -0,0 +1,147 @@ +package cuchaz.enigma.analysis.index; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.analysis.ReferenceTargetType; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.entry.*; + +import java.util.*; + +public class PackageVisibilityIndex implements JarIndexer { + private static boolean requiresSamePackage(AccessFlags entryAcc, EntryReference ref, InheritanceIndex inheritanceIndex) { + if (entryAcc.isPublic()) { + return false; + } + + if (entryAcc.isProtected()) { + ClassEntry contextClass = ref.context.getContainingClass(); + ClassEntry referencedClass = ref.entry.getContainingClass(); + + if (!inheritanceIndex.getAncestors(contextClass).contains(referencedClass)) { + return true; // access to protected member not in superclass + } + + if (ref.targetType.getKind() == ReferenceTargetType.Kind.NONE) { + return false; // access to superclass or static superclass member + } + + // access to instance member only valid if target's class assignable to context class + return !(ref.targetType.getKind() == ReferenceTargetType.Kind.UNINITIALIZED || + ((ReferenceTargetType.ClassType) ref.targetType).getEntry().equals(contextClass) || + inheritanceIndex.getAncestors(((ReferenceTargetType.ClassType) ref.targetType).getEntry()).contains(contextClass)); + } + + return true; + } + + private final HashMultimap connections = HashMultimap.create(); + private final List> partitions = Lists.newArrayList(); + private final Map> classPartitions = Maps.newHashMap(); + + private void addConnection(ClassEntry classA, ClassEntry classB) { + if (classA != classB) { + connections.put(classA, classB); + connections.put(classB, classA); + } + } + + private void buildPartition(Set unassignedClasses, Set partition, ClassEntry member) { + for (ClassEntry connected : connections.get(member)) { + if (unassignedClasses.remove(connected)) { + partition.add(connected); + buildPartition(unassignedClasses, partition, connected); + } + } + } + + private void addConnections(EntryIndex entryIndex, ReferenceIndex referenceIndex, InheritanceIndex inheritanceIndex) { + for (FieldEntry entry : entryIndex.getFields()) { + AccessFlags entryAcc = entryIndex.getFieldAccess(entry); + if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { + for (EntryReference ref : referenceIndex.getReferencesToField(entry)) { + if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { + addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); + } + } + } + } + + for (MethodEntry entry : entryIndex.getMethods()) { + AccessFlags entryAcc = entryIndex.getMethodAccess(entry); + if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { + for (EntryReference ref : referenceIndex.getReferencesToMethod(entry)) { + if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { + addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); + } + } + } + } + + for (ClassEntry entry : entryIndex.getClasses()) { + AccessFlags entryAcc = entryIndex.getClassAccess(entry); + if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { + for (EntryReference ref : referenceIndex.getFieldTypeReferencesToClass(entry)) { + if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { + addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); + } + } + + for (EntryReference ref : referenceIndex.getMethodTypeReferencesToClass(entry)) { + if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { + addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); + } + } + } + + for (ClassEntry parent : inheritanceIndex.getParents(entry)) { + AccessFlags parentAcc = entryIndex.getClassAccess(parent); + if (parentAcc != null && !parentAcc.isPublic() && !parentAcc.isPrivate()) { + addConnection(entry, parent); + } + } + + ClassEntry outerClass = entry.getOuterClass(); + if (outerClass != null) { + addConnection(entry, outerClass); + } + } + } + + private void addPartitions(EntryIndex entryIndex) { + Set unassignedClasses = Sets.newHashSet(entryIndex.getClasses()); + while (!unassignedClasses.isEmpty()) { + Iterator iterator = unassignedClasses.iterator(); + ClassEntry initialEntry = iterator.next(); + iterator.remove(); + + HashSet partition = Sets.newHashSet(); + partition.add(initialEntry); + buildPartition(unassignedClasses, partition, initialEntry); + partitions.add(partition); + for (ClassEntry entry : partition) { + classPartitions.put(entry, partition); + } + } + } + + public Collection> getPartitions() { + return partitions; + } + + public Set getPartition(ClassEntry classEntry) { + return classPartitions.get(classEntry); + } + + @Override + public void processIndex(JarIndex index) { + EntryIndex entryIndex = index.getEntryIndex(); + ReferenceIndex referenceIndex = index.getReferenceIndex(); + InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); + addConnections(entryIndex, referenceIndex, inheritanceIndex); + addPartitions(entryIndex); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java new file mode 100644 index 00000000..b6797c21 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java @@ -0,0 +1,148 @@ +package cuchaz.enigma.analysis.index; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.analysis.ReferenceTargetType; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; +import cuchaz.enigma.translation.representation.Lambda; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.*; + +import java.util.Collection; +import java.util.Map; + +public class ReferenceIndex implements JarIndexer { + private Multimap methodReferences = HashMultimap.create(); + + private Multimap> referencesToMethods = HashMultimap.create(); + private Multimap> referencesToClasses = HashMultimap.create(); + private Multimap> referencesToFields = HashMultimap.create(); + private Multimap> fieldTypeReferences = HashMultimap.create(); + private Multimap> methodTypeReferences = HashMultimap.create(); + + @Override + public void indexMethod(MethodDefEntry methodEntry) { + indexMethodDescriptor(methodEntry, methodEntry.getDesc()); + } + + private void indexMethodDescriptor(MethodDefEntry entry, MethodDescriptor descriptor) { + for (TypeDescriptor typeDescriptor : descriptor.getArgumentDescs()) { + indexMethodTypeDescriptor(entry, typeDescriptor); + } + indexMethodTypeDescriptor(entry, descriptor.getReturnDesc()); + } + + private void indexMethodTypeDescriptor(MethodDefEntry method, TypeDescriptor typeDescriptor) { + if (typeDescriptor.isType()) { + ClassEntry referencedClass = typeDescriptor.getTypeEntry(); + methodTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), method)); + } else if (typeDescriptor.isArray()) { + indexMethodTypeDescriptor(method, typeDescriptor.getArrayType()); + } + } + + @Override + public void indexField(FieldDefEntry fieldEntry) { + indexFieldTypeDescriptor(fieldEntry, fieldEntry.getDesc()); + } + + private void indexFieldTypeDescriptor(FieldDefEntry field, TypeDescriptor typeDescriptor) { + if (typeDescriptor.isType()) { + ClassEntry referencedClass = typeDescriptor.getTypeEntry(); + fieldTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), field)); + } else if (typeDescriptor.isArray()) { + indexFieldTypeDescriptor(field, typeDescriptor.getArrayType()); + } + } + + @Override + public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) { + referencesToMethods.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); + methodReferences.put(callerEntry, referencedEntry); + + if (referencedEntry.isConstructor()) { + ClassEntry referencedClass = referencedEntry.getParent(); + referencesToClasses.put(referencedClass, new EntryReference<>(referencedClass, referencedEntry.getName(), callerEntry, targetType)); + } + } + + @Override + public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) { + referencesToFields.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); + } + + @Override + public void indexLambda(MethodDefEntry callerEntry, Lambda lambda, ReferenceTargetType targetType) { + if (lambda.getImplMethod() instanceof MethodEntry) { + indexMethodReference(callerEntry, (MethodEntry) lambda.getImplMethod(), targetType); + } else { + indexFieldReference(callerEntry, (FieldEntry) lambda.getImplMethod(), targetType); + } + + indexMethodDescriptor(callerEntry, lambda.getInvokedType()); + indexMethodDescriptor(callerEntry, lambda.getSamMethodType()); + indexMethodDescriptor(callerEntry, lambda.getInstantiatedMethodType()); + } + + @Override + public void processIndex(JarIndex index) { + methodReferences = remapReferences(index, methodReferences); + referencesToMethods = remapReferencesTo(index, referencesToMethods); + referencesToClasses = remapReferencesTo(index, referencesToClasses); + referencesToFields = remapReferencesTo(index, referencesToFields); + fieldTypeReferences = remapReferencesTo(index, fieldTypeReferences); + methodTypeReferences = remapReferencesTo(index, methodTypeReferences); + } + + private , V extends Entry> Multimap remapReferences(JarIndex index, Multimap multimap) { + final int keySetSize = multimap.keySet().size(); + Multimap resolved = HashMultimap.create(multimap.keySet().size(), keySetSize == 0 ? 0 : multimap.size() / keySetSize); + for (Map.Entry entry : multimap.entries()) { + resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue())); + } + return resolved; + } + + private , C extends Entry> Multimap> remapReferencesTo(JarIndex index, Multimap> multimap) { + final int keySetSize = multimap.keySet().size(); + Multimap> resolved = HashMultimap.create(keySetSize, keySetSize == 0 ? 0 : multimap.size() / keySetSize); + for (Map.Entry> entry : multimap.entries()) { + resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue())); + } + return resolved; + } + + private > E remap(JarIndex index, E entry) { + return index.getEntryResolver().resolveFirstEntry(entry, ResolutionStrategy.RESOLVE_CLOSEST); + } + + private , C extends Entry> EntryReference remap(JarIndex index, EntryReference reference) { + return index.getEntryResolver().resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST); + } + + public Collection getMethodsReferencedBy(MethodEntry entry) { + return methodReferences.get(entry); + } + + public Collection> getReferencesToField(FieldEntry entry) { + return referencesToFields.get(entry); + } + + public Collection> getReferencesToClass(ClassEntry entry) { + return referencesToClasses.get(entry); + } + + public Collection> getReferencesToMethod(MethodEntry entry) { + return referencesToMethods.get(entry); + } + + public Collection> getFieldTypeReferencesToClass(ClassEntry entry) { + return fieldTypeReferences.get(entry); + } + + public Collection> getMethodTypeReferencesToClass(ClassEntry entry) { + return methodTypeReferences.get(entry); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java b/enigma/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java new file mode 100644 index 00000000..bdd60150 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java @@ -0,0 +1,5 @@ +package cuchaz.enigma.api; + +public interface EnigmaPlugin { + void init(EnigmaPluginContext ctx); +} diff --git a/enigma/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java b/enigma/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java new file mode 100644 index 00000000..a59051ad --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java @@ -0,0 +1,9 @@ +package cuchaz.enigma.api; + +import cuchaz.enigma.api.service.EnigmaService; +import cuchaz.enigma.api.service.EnigmaServiceFactory; +import cuchaz.enigma.api.service.EnigmaServiceType; + +public interface EnigmaPluginContext { + void registerService(String id, EnigmaServiceType serviceType, EnigmaServiceFactory factory); +} diff --git a/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaService.java b/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaService.java new file mode 100644 index 00000000..526dda77 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaService.java @@ -0,0 +1,4 @@ +package cuchaz.enigma.api.service; + +public interface EnigmaService { +} diff --git a/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java b/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java new file mode 100644 index 00000000..9e433fb0 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java @@ -0,0 +1,11 @@ +package cuchaz.enigma.api.service; + +import java.util.Optional; + +public interface EnigmaServiceContext { + static EnigmaServiceContext empty() { + return key -> Optional.empty(); + } + + Optional getArgument(String key); +} diff --git a/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java b/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java new file mode 100644 index 00000000..7c10ac26 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java @@ -0,0 +1,5 @@ +package cuchaz.enigma.api.service; + +public interface EnigmaServiceFactory { + T create(EnigmaServiceContext ctx); +} diff --git a/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java b/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java new file mode 100644 index 00000000..358828f0 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java @@ -0,0 +1,29 @@ +package cuchaz.enigma.api.service; + +public final class EnigmaServiceType { + public final String key; + + private EnigmaServiceType(String key) { + this.key = key; + } + + public static EnigmaServiceType create(String key) { + return new EnigmaServiceType<>(key); + } + + @Override + public int hashCode() { + return key.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + + if (obj instanceof EnigmaServiceType) { + return ((EnigmaServiceType) obj).key.equals(key); + } + + return false; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java b/enigma/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java new file mode 100644 index 00000000..0cda1998 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java @@ -0,0 +1,10 @@ +package cuchaz.enigma.api.service; + +import cuchaz.enigma.analysis.ClassCache; +import cuchaz.enigma.analysis.index.JarIndex; + +public interface JarIndexerService extends EnigmaService { + EnigmaServiceType TYPE = EnigmaServiceType.create("jar_indexer"); + + void acceptJar(ClassCache classCache, JarIndex jarIndex); +} diff --git a/enigma/src/main/java/cuchaz/enigma/api/service/NameProposalService.java b/enigma/src/main/java/cuchaz/enigma/api/service/NameProposalService.java new file mode 100644 index 00000000..4c357db1 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/api/service/NameProposalService.java @@ -0,0 +1,12 @@ +package cuchaz.enigma.api.service; + +import cuchaz.enigma.translation.mapping.EntryRemapper; +import cuchaz.enigma.translation.representation.entry.Entry; + +import java.util.Optional; + +public interface NameProposalService extends EnigmaService { + EnigmaServiceType TYPE = EnigmaServiceType.create("name_proposal"); + + Optional proposeName(Entry obfEntry, EntryRemapper remapper); +} diff --git a/enigma/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java b/enigma/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java new file mode 100644 index 00000000..af0cf30b --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java @@ -0,0 +1,9 @@ +package cuchaz.enigma.api.service; + +import cuchaz.enigma.translation.representation.entry.Entry; + +public interface ObfuscationTestService extends EnigmaService { + EnigmaServiceType TYPE = EnigmaServiceType.create("obfuscation_test"); + + boolean testDeobfuscated(Entry entry); +} diff --git a/enigma/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java new file mode 100644 index 00000000..1a2b47fb --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java @@ -0,0 +1,46 @@ +package cuchaz.enigma.bytecode.translators; + +import cuchaz.enigma.translation.Translator; +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.MethodEntry; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Type; + +public class AsmObjectTranslator { + public static Type translateType(Translator translator, Type type) { + String descString = type.getDescriptor(); + switch (type.getSort()) { + case Type.OBJECT: { + ClassEntry classEntry = new ClassEntry(type.getInternalName()); + return Type.getObjectType(translator.translate(classEntry).getFullName()); + } + case Type.ARRAY: { + TypeDescriptor descriptor = new TypeDescriptor(descString); + return Type.getType(translator.translate(descriptor).toString()); + } + case Type.METHOD: { + MethodDescriptor descriptor = new MethodDescriptor(descString); + return Type.getMethodType(translator.translate(descriptor).toString()); + } + } + return type; + } + + public static Handle translateHandle(Translator translator, Handle handle) { + MethodEntry entry = new MethodEntry(new ClassEntry(handle.getOwner()), handle.getName(), new MethodDescriptor(handle.getDesc())); + MethodEntry translatedMethod = translator.translate(entry); + ClassEntry ownerClass = translatedMethod.getParent(); + return new Handle(handle.getTag(), ownerClass.getFullName(), translatedMethod.getName(), translatedMethod.getDesc().toString(), handle.isInterface()); + } + + public static Object translateValue(Translator translator, Object value) { + if (value instanceof Type) { + return translateType(translator, (Type) value); + } else if (value instanceof Handle) { + return translateHandle(translator, (Handle) value); + } + return value; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java new file mode 100644 index 00000000..cfd8fbee --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java @@ -0,0 +1,126 @@ +package cuchaz.enigma.bytecode.translators; + +import com.google.common.base.CharMatcher; +import cuchaz.enigma.translation.LocalNameGenerator; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.ClassDefEntry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class LocalVariableFixVisitor extends ClassVisitor { + private ClassDefEntry ownerEntry; + + public LocalVariableFixVisitor(int api, ClassVisitor visitor) { + super(api, visitor); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + ownerEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces); + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + MethodDefEntry methodEntry = MethodDefEntry.parse(ownerEntry, access, name, descriptor, signature); + return new Method(api, methodEntry, super.visitMethod(access, name, descriptor, signature, exceptions)); + } + + private class Method extends MethodVisitor { + private final MethodDefEntry methodEntry; + private final Map parameterNames = new HashMap<>(); + private final Map parameterIndices = new HashMap<>(); + private boolean hasParameterTable; + private int parameterIndex = 0; + + Method(int api, MethodDefEntry methodEntry, MethodVisitor visitor) { + super(api, visitor); + this.methodEntry = methodEntry; + + int lvIndex = methodEntry.getAccess().isStatic() ? 0 : 1; + List parameters = methodEntry.getDesc().getArgumentDescs(); + for (int parameterIndex = 0; parameterIndex < parameters.size(); parameterIndex++) { + TypeDescriptor param = parameters.get(parameterIndex); + parameterIndices.put(lvIndex, parameterIndex); + lvIndex += param.getSize(); + } + } + + @Override + public void visitParameter(String name, int access) { + hasParameterTable = true; + super.visitParameter(fixParameterName(parameterIndex, name), fixParameterAccess(parameterIndex, access)); + parameterIndex++; + } + + @Override + public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { + if (index == 0 && !methodEntry.getAccess().isStatic()) { + name = "this"; + } else if (parameterIndices.containsKey(index)) { + name = fixParameterName(parameterIndices.get(index), name); + } else if (isInvalidName(name)) { + name = LocalNameGenerator.generateLocalVariableName(index, new TypeDescriptor(desc)); + } + + super.visitLocalVariable(name, desc, signature, start, end, index); + } + + private boolean isInvalidName(String name) { + return name == null || name.isEmpty() || !CharMatcher.ascii().matchesAllOf(name); + } + + @Override + public void visitEnd() { + if (!hasParameterTable) { + List arguments = methodEntry.getDesc().getArgumentDescs(); + for (int argumentIndex = 0; argumentIndex < arguments.size(); argumentIndex++) { + super.visitParameter(fixParameterName(argumentIndex, null), fixParameterAccess(argumentIndex, 0)); + } + } + + super.visitEnd(); + } + + private String fixParameterName(int index, String name) { + if (parameterNames.get(index) != null) { + return parameterNames.get(index); // to make sure that LVT names are consistent with parameter table names + } + + if (isInvalidName(name)) { + List arguments = methodEntry.getDesc().getArgumentDescs(); + name = LocalNameGenerator.generateArgumentName(index, arguments.get(index), arguments); + } + + if (index == 0 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("")) { + name = "name"; + } + + if (index == 1 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("")) { + name = "ordinal"; + } + + parameterNames.put(index, name); + return name; + } + + private int fixParameterAccess(int index, int access) { + if (index == 0 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("")) { + access |= Opcodes.ACC_SYNTHETIC; + } + + if (index == 1 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("")) { + access |= Opcodes.ACC_SYNTHETIC; + } + + return access; + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/bytecode/translators/SourceFixVisitor.java b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/SourceFixVisitor.java new file mode 100644 index 00000000..2b750eac --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/SourceFixVisitor.java @@ -0,0 +1,39 @@ +package cuchaz.enigma.bytecode.translators; + +import cuchaz.enigma.analysis.index.BridgeMethodIndex; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.representation.entry.ClassDefEntry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class SourceFixVisitor extends ClassVisitor { + private final JarIndex index; + private ClassDefEntry ownerEntry; + + public SourceFixVisitor(int api, ClassVisitor visitor, JarIndex index) { + super(api, visitor); + this.index = index; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + ownerEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces); + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + MethodDefEntry methodEntry = MethodDefEntry.parse(ownerEntry, access, name, descriptor, signature); + + BridgeMethodIndex bridgeIndex = index.getBridgeMethodIndex(); + if (bridgeIndex.isBridgeMethod(methodEntry)) { + access |= Opcodes.ACC_BRIDGE; + } else if (bridgeIndex.isSpecializedMethod(methodEntry)) { + name = bridgeIndex.getBridgeFromSpecialized(methodEntry).getName(); + } + + return super.visitMethod(access, name, descriptor, signature, exceptions); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java new file mode 100644 index 00000000..cb843ad4 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java @@ -0,0 +1,51 @@ +package cuchaz.enigma.bytecode.translators; + +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import org.objectweb.asm.AnnotationVisitor; + +public class TranslationAnnotationVisitor extends AnnotationVisitor { + private final Translator translator; + private final ClassEntry annotationEntry; + + public TranslationAnnotationVisitor(Translator translator, ClassEntry annotationEntry, int api, AnnotationVisitor av) { + super(api, av); + this.translator = translator; + this.annotationEntry = annotationEntry; + } + + @Override + public void visit(String name, Object value) { + super.visit(name, AsmObjectTranslator.translateValue(translator, value)); + } + + @Override + public AnnotationVisitor visitArray(String name) { + return new TranslationAnnotationVisitor(translator, annotationEntry, api, super.visitArray(name)); + } + + @Override + public AnnotationVisitor visitAnnotation(String name, String desc) { + TypeDescriptor type = new TypeDescriptor(desc); + if (name != null) { + FieldEntry annotationField = translator.translate(new FieldEntry(annotationEntry, name, type)); + return super.visitAnnotation(annotationField.getName(), annotationField.getDesc().toString()); + } else { + return super.visitAnnotation(null, translator.translate(type).toString()); + } + } + + @Override + public void visitEnum(String name, String desc, String value) { + TypeDescriptor type = new TypeDescriptor(desc); + FieldEntry enumField = translator.translate(new FieldEntry(type.getTypeEntry(), value, type)); + if (name != null) { + FieldEntry annotationField = translator.translate(new FieldEntry(annotationEntry, name, type)); + super.visitEnum(annotationField.getName(), annotationField.getDesc().toString(), enumField.getName()); + } else { + super.visitEnum(null, translator.translate(type).toString(), enumField.getName()); + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java new file mode 100644 index 00000000..e4c41d32 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.bytecode.translators; + +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.*; +import org.objectweb.asm.*; + +import java.util.Arrays; + +public class TranslationClassVisitor extends ClassVisitor { + private final Translator translator; + + private ClassDefEntry obfClassEntry; + + public TranslationClassVisitor(Translator translator, int api, ClassVisitor cv) { + super(api, cv); + this.translator = translator; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + obfClassEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces); + + ClassDefEntry translatedEntry = translator.translate(obfClassEntry); + String translatedSuper = translatedEntry.getSuperClass() != null ? translatedEntry.getSuperClass().getFullName() : null; + String[] translatedInterfaces = Arrays.stream(translatedEntry.getInterfaces()).map(ClassEntry::getFullName).toArray(String[]::new); + + super.visit(version, translatedEntry.getAccess().getFlags(), translatedEntry.getFullName(), translatedEntry.getSignature().toString(), translatedSuper, translatedInterfaces); + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + FieldDefEntry entry = FieldDefEntry.parse(obfClassEntry, access, name, desc, signature); + FieldDefEntry translatedEntry = translator.translate(entry); + FieldVisitor fv = super.visitField(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), value); + return new TranslationFieldVisitor(translator, translatedEntry, api, fv); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodDefEntry entry = MethodDefEntry.parse(obfClassEntry, access, name, desc, signature); + MethodDefEntry translatedEntry = translator.translate(entry); + String[] translatedExceptions = new String[exceptions.length]; + for (int i = 0; i < exceptions.length; i++) { + translatedExceptions[i] = translator.translate(new ClassEntry(exceptions[i])).getFullName(); + } + MethodVisitor mv = super.visitMethod(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), translatedExceptions); + return new TranslationMethodVisitor(translator, obfClassEntry, entry, api, mv); + } + + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + ClassDefEntry classEntry = ClassDefEntry.parse(access, name, obfClassEntry.getSignature().toString(), null, new String[0]); + ClassDefEntry translatedEntry = translator.translate(classEntry); + ClassEntry translatedOuterClass = translatedEntry.getOuterClass(); + if (translatedOuterClass == null) { + throw new IllegalStateException("Translated inner class did not have outer class"); + } + + // Anonymous classes do not specify an outer or inner name. As we do not translate from the given parameter, ignore if the input is null + String translatedName = translatedEntry.getFullName(); + String translatedOuterName = outerName != null ? translatedOuterClass.getFullName() : null; + String translatedInnerName = innerName != null ? translatedEntry.getName() : null; + super.visitInnerClass(translatedName, translatedOuterName, translatedInnerName, translatedEntry.getAccess().getFlags()); + } + + @Override + public void visitOuterClass(String owner, String name, String desc) { + if (desc != null) { + MethodEntry translatedEntry = translator.translate(new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc))); + super.visitOuterClass(translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString()); + } else { + super.visitOuterClass(owner, name, desc); + } + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + TypeDescriptor translatedDesc = translator.translate(new TypeDescriptor(desc)); + AnnotationVisitor av = super.visitAnnotation(translatedDesc.toString(), visible); + return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + TypeDescriptor translatedDesc = translator.translate(new TypeDescriptor(desc)); + AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, translatedDesc.toString(), visible); + return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java new file mode 100644 index 00000000..28fc199c --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java @@ -0,0 +1,33 @@ +package cuchaz.enigma.bytecode.translators; + +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.FieldDefEntry; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.TypePath; + +public class TranslationFieldVisitor extends FieldVisitor { + private final FieldDefEntry fieldEntry; + private final Translator translator; + + public TranslationFieldVisitor(Translator translator, FieldDefEntry fieldEntry, int api, FieldVisitor fv) { + super(api, fv); + this.translator = translator; + this.fieldEntry = fieldEntry; + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); + AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible); + return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); + AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible); + return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java new file mode 100644 index 00000000..a82df1b0 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java @@ -0,0 +1,145 @@ +package cuchaz.enigma.bytecode.translators; + +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.Signature; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.*; +import org.objectweb.asm.*; + +public class TranslationMethodVisitor extends MethodVisitor { + private final MethodDefEntry methodEntry; + private final Translator translator; + + private int parameterIndex = 0; + private int parameterLvIndex; + + public TranslationMethodVisitor(Translator translator, ClassDefEntry ownerEntry, MethodDefEntry methodEntry, int api, MethodVisitor mv) { + super(api, mv); + this.translator = translator; + this.methodEntry = methodEntry; + + parameterLvIndex = methodEntry.getAccess().isStatic() ? 0 : 1; + } + + @Override + public void visitParameter(String name, int access) { + name = translateVariableName(parameterLvIndex, name); + parameterLvIndex += methodEntry.getDesc().getArgumentDescs().get(parameterIndex++).getSize(); + + super.visitParameter(name, access); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + FieldEntry entry = new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc)); + FieldEntry translatedEntry = translator.translate(entry); + super.visitFieldInsn(opcode, translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString()); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + MethodEntry entry = new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc)); + MethodEntry translatedEntry = translator.translate(entry); + super.visitMethodInsn(opcode, translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString(), itf); + } + + @Override + public void visitFrame(int type, int localCount, Object[] locals, int stackCount, Object[] stack) { + Object[] translatedLocals = this.getTranslatedFrame(locals, localCount); + Object[] translatedStack = this.getTranslatedFrame(stack, stackCount); + super.visitFrame(type, localCount, translatedLocals, stackCount, translatedStack); + } + + private Object[] getTranslatedFrame(Object[] array, int count) { + if (array == null) { + return null; + } + for (int i = 0; i < count; i++) { + Object object = array[i]; + if (object instanceof String) { + String type = (String) object; + array[i] = translator.translate(new ClassEntry(type)).getFullName(); + } + } + return array; + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); + AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible); + return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); + } + + @Override + public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { + TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); + AnnotationVisitor av = super.visitParameterAnnotation(parameter, typeDesc.toString(), visible); + return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); + AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, typeDesc.toString(), visible); + return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); + } + + @Override + public void visitTypeInsn(int opcode, String type) { + ClassEntry translatedEntry = translator.translate(new ClassEntry(type)); + super.visitTypeInsn(opcode, translatedEntry.getFullName()); + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + MethodDescriptor translatedMethodDesc = translator.translate(new MethodDescriptor(desc)); + Object[] translatedBsmArgs = new Object[bsmArgs.length]; + for (int i = 0; i < bsmArgs.length; i++) { + translatedBsmArgs[i] = AsmObjectTranslator.translateValue(translator, bsmArgs[i]); + } + super.visitInvokeDynamicInsn(name, translatedMethodDesc.toString(), AsmObjectTranslator.translateHandle(translator, bsm), translatedBsmArgs); + } + + @Override + public void visitLdcInsn(Object cst) { + super.visitLdcInsn(AsmObjectTranslator.translateValue(translator, cst)); + } + + @Override + public void visitMultiANewArrayInsn(String desc, int dims) { + super.visitMultiANewArrayInsn(translator.translate(new TypeDescriptor(desc)).toString(), dims); + } + + @Override + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + if (type != null) { + ClassEntry translatedEntry = translator.translate(new ClassEntry(type)); + super.visitTryCatchBlock(start, end, handler, translatedEntry.getFullName()); + } else { + super.visitTryCatchBlock(start, end, handler, type); + } + } + + @Override + public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { + signature = translator.translate(Signature.createTypedSignature(signature)).toString(); + name = translateVariableName(index, name); + desc = translator.translate(new TypeDescriptor(desc)).toString(); + + super.visitLocalVariable(name, desc, signature, start, end, index); + } + + private String translateVariableName(int index, String name) { + LocalVariableEntry entry = new LocalVariableEntry(methodEntry, index, "", true,null); + LocalVariableEntry translatedEntry = translator.translate(entry); + String translatedName = translatedEntry.getName(); + + if (!translatedName.isEmpty()) { + return translatedName; + } + + return name; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java new file mode 100644 index 00000000..bc4a2f00 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java @@ -0,0 +1,129 @@ +package cuchaz.enigma.bytecode.translators; + +import cuchaz.enigma.Enigma; +import org.objectweb.asm.signature.SignatureVisitor; + +import java.util.Stack; +import java.util.function.Function; + +public class TranslationSignatureVisitor extends SignatureVisitor { + private final Function remapper; + + private final SignatureVisitor sv; + private final Stack classStack = new Stack<>(); + + public TranslationSignatureVisitor(Function remapper, SignatureVisitor sv) { + super(Enigma.ASM_VERSION); + this.remapper = remapper; + this.sv = sv; + } + + @Override + public void visitClassType(String name) { + classStack.push(name); + String translatedEntry = this.remapper.apply(name); + this.sv.visitClassType(translatedEntry); + } + + @Override + public void visitInnerClassType(String name) { + String lastClass = classStack.pop(); + if (!name.startsWith(lastClass+"$")){//todo see if there's a way to base this on whether there were type params or not + name = lastClass+"$"+name; + } + String translatedEntry = this.remapper.apply(name); + if (translatedEntry.contains("/")){ + translatedEntry = translatedEntry.substring(translatedEntry.lastIndexOf("/")+1); + } + if (translatedEntry.contains("$")){ + translatedEntry = translatedEntry.substring(translatedEntry.lastIndexOf("$")+1); + } + this.sv.visitInnerClassType(translatedEntry); + } + + @Override + public void visitFormalTypeParameter(String name) { + this.sv.visitFormalTypeParameter(name); + } + + @Override + public void visitTypeVariable(String name) { + this.sv.visitTypeVariable(name); + } + + @Override + public SignatureVisitor visitArrayType() { + this.sv.visitArrayType(); + return this; + } + + @Override + public void visitBaseType(char descriptor) { + this.sv.visitBaseType(descriptor); + } + + @Override + public SignatureVisitor visitClassBound() { + this.sv.visitClassBound(); + return this; + } + + @Override + public SignatureVisitor visitExceptionType() { + this.sv.visitExceptionType(); + return this; + } + + @Override + public SignatureVisitor visitInterface() { + this.sv.visitInterface(); + return this; + } + + @Override + public SignatureVisitor visitInterfaceBound() { + this.sv.visitInterfaceBound(); + return this; + } + + @Override + public SignatureVisitor visitParameterType() { + this.sv.visitParameterType(); + return this; + } + + @Override + public SignatureVisitor visitReturnType() { + this.sv.visitReturnType(); + return this; + } + + @Override + public SignatureVisitor visitSuperclass() { + this.sv.visitSuperclass(); + return this; + } + + @Override + public void visitTypeArgument() { + this.sv.visitTypeArgument(); + } + + @Override + public SignatureVisitor visitTypeArgument(char wildcard) { + this.sv.visitTypeArgument(wildcard); + return this; + } + + @Override + public void visitEnd() { + this.sv.visitEnd(); + if (!classStack.empty()) + classStack.pop(); + } + + @Override + public String toString() { + return this.sv.toString(); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/Decompiler.java b/enigma/src/main/java/cuchaz/enigma/source/Decompiler.java new file mode 100644 index 00000000..c9666d52 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/Decompiler.java @@ -0,0 +1,5 @@ +package cuchaz.enigma.source; + +public interface Decompiler { + Source getSource(String className); +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/DecompilerService.java b/enigma/src/main/java/cuchaz/enigma/source/DecompilerService.java new file mode 100644 index 00000000..377ccbc1 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/DecompilerService.java @@ -0,0 +1,11 @@ +package cuchaz.enigma.source; + +import cuchaz.enigma.ClassProvider; +import cuchaz.enigma.api.service.EnigmaService; +import cuchaz.enigma.api.service.EnigmaServiceType; + +public interface DecompilerService extends EnigmaService { + EnigmaServiceType TYPE = EnigmaServiceType.create("decompiler"); + + Decompiler create(ClassProvider classProvider, SourceSettings settings); +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/Decompilers.java b/enigma/src/main/java/cuchaz/enigma/source/Decompilers.java new file mode 100644 index 00000000..7d154a6a --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/Decompilers.java @@ -0,0 +1,9 @@ +package cuchaz.enigma.source; + +import cuchaz.enigma.source.cfr.CfrDecompiler; +import cuchaz.enigma.source.procyon.ProcyonDecompiler; + +public class Decompilers { + public static final DecompilerService PROCYON = ProcyonDecompiler::new; + public static final DecompilerService CFR = CfrDecompiler::new; +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/Source.java b/enigma/src/main/java/cuchaz/enigma/source/Source.java new file mode 100644 index 00000000..43c4de0c --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/Source.java @@ -0,0 +1,11 @@ +package cuchaz.enigma.source; + +import cuchaz.enigma.translation.mapping.EntryRemapper; + +public interface Source { + String asString(); + + Source addJavadocs(EntryRemapper remapper); + + SourceIndex index(); +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/SourceIndex.java b/enigma/src/main/java/cuchaz/enigma/source/SourceIndex.java new file mode 100644 index 00000000..178ce20e --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/SourceIndex.java @@ -0,0 +1,172 @@ +package cuchaz.enigma.source; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; +import cuchaz.enigma.translation.representation.entry.Entry; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; + +public class SourceIndex { + private String source; + private List lineOffsets; + private final TreeMap, Entry>> tokenToReference; + private final Multimap, Entry>, Token> referenceToTokens; + private final Map, Token> declarationToToken; + + public SourceIndex() { + tokenToReference = new TreeMap<>(); + referenceToTokens = HashMultimap.create(); + declarationToToken = Maps.newHashMap(); + } + + public SourceIndex(String source) { + this(); + setSource(source); + } + + public void setSource(String source) { + this.source = source; + lineOffsets = Lists.newArrayList(); + lineOffsets.add(0); + + for (int i = 0; i < this.source.length(); i++) { + if (this.source.charAt(i) == '\n') { + lineOffsets.add(i + 1); + } + } + } + + public String getSource() { + return source; + } + + public int getLineNumber(int position) { + int line = 0; + + for (int offset : lineOffsets) { + if (offset > position) { + break; + } + + line++; + } + + return line; + } + + public int getColumnNumber(int position) { + return position - lineOffsets.get(getLineNumber(position) - 1) + 1; + } + + public int getPosition(int line, int column) { + return lineOffsets.get(line - 1) + column - 1; + } + + public Iterable> declarations() { + return declarationToToken.keySet(); + } + + public Iterable declarationTokens() { + return declarationToToken.values(); + } + + public Token getDeclarationToken(Entry entry) { + return declarationToToken.get(entry); + } + + public void addDeclaration(Token token, Entry deobfEntry) { + if (token != null) { + EntryReference, Entry> reference = new EntryReference<>(deobfEntry, token.text); + tokenToReference.put(token, reference); + referenceToTokens.put(reference, token); + declarationToToken.put(deobfEntry, token); + } + } + + public Iterable, Entry>> references() { + return referenceToTokens.keySet(); + } + + public EntryReference, Entry> getReference(Token token) { + if (token == null) { + return null; + } + + return tokenToReference.get(token); + } + + public Iterable referenceTokens() { + return tokenToReference.keySet(); + } + + public Token getReferenceToken(int pos) { + Token token = tokenToReference.floorKey(new Token(pos, pos, null)); + + if (token != null && token.contains(pos)) { + return token; + } + + return null; + } + + public Collection getReferenceTokens(EntryReference, Entry> deobfReference) { + return referenceToTokens.get(deobfReference); + } + + public void addReference(Token token, Entry deobfEntry, Entry deobfContext) { + if (token != null) { + EntryReference, Entry> deobfReference = new EntryReference<>(deobfEntry, token.text, deobfContext); + tokenToReference.put(token, deobfReference); + referenceToTokens.put(deobfReference, token); + } + } + + public void resolveReferences(EntryResolver resolver) { + // resolve all the classes in the source references + for (Token token : Lists.newArrayList(referenceToTokens.values())) { + EntryReference, Entry> reference = tokenToReference.get(token); + EntryReference, Entry> resolvedReference = resolver.resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST); + + // replace the reference + tokenToReference.replace(token, resolvedReference); + + Collection tokens = referenceToTokens.removeAll(reference); + referenceToTokens.putAll(resolvedReference, tokens); + } + } + + public SourceIndex remapTo(SourceRemapper.Result result) { + SourceIndex remapped = new SourceIndex(result.getSource()); + + for (Map.Entry, Token> entry : declarationToToken.entrySet()) { + remapped.declarationToToken.put(entry.getKey(), result.getRemappedToken(entry.getValue())); + } + + for (Map.Entry, Entry>, Collection> entry : referenceToTokens.asMap().entrySet()) { + EntryReference, Entry> reference = entry.getKey(); + Collection oldTokens = entry.getValue(); + + Collection newTokens = oldTokens + .stream() + .map(result::getRemappedToken) + .collect(Collectors.toList()); + + remapped.referenceToTokens.putAll(reference, newTokens); + } + + for (Map.Entry, Entry>> entry : tokenToReference.entrySet()) { + remapped.tokenToReference.put(result.getRemappedToken(entry.getKey()), entry.getValue()); + } + + return remapped; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/SourceRemapper.java b/enigma/src/main/java/cuchaz/enigma/source/SourceRemapper.java new file mode 100644 index 00000000..b5f80063 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/SourceRemapper.java @@ -0,0 +1,62 @@ +package cuchaz.enigma.source; + +import java.util.HashMap; +import java.util.Map; + +public class SourceRemapper { + private final String source; + private final Iterable tokens; + + public SourceRemapper(String source, Iterable tokens) { + this.source = source; + this.tokens = tokens; + } + + public Result remap(Remapper remapper) { + StringBuffer remappedSource = new StringBuffer(source); + Map remappedTokens = new HashMap<>(); + + int accumulatedOffset = 0; + for (Token token : tokens) { + Token movedToken = token.move(accumulatedOffset); + + String remappedName = remapper.remap(token, movedToken); + if (remappedName != null) { + accumulatedOffset += movedToken.getRenameOffset(remappedName); + movedToken.rename(remappedSource, remappedName); + } + + if (!token.equals(movedToken)) { + remappedTokens.put(token, movedToken); + } + } + + return new Result(remappedSource.toString(), remappedTokens); + } + + public static class Result { + private final String remappedSource; + private final Map remappedTokens; + + Result(String remappedSource, Map remappedTokens) { + this.remappedSource = remappedSource; + this.remappedTokens = remappedTokens; + } + + public String getSource() { + return remappedSource; + } + + public Token getRemappedToken(Token token) { + return remappedTokens.getOrDefault(token, token); + } + + public boolean isEmpty() { + return remappedTokens.isEmpty(); + } + } + + public interface Remapper { + String remap(Token token, Token movedToken); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/SourceSettings.java b/enigma/src/main/java/cuchaz/enigma/source/SourceSettings.java new file mode 100644 index 00000000..f6c68e98 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/SourceSettings.java @@ -0,0 +1,11 @@ +package cuchaz.enigma.source; + +public class SourceSettings { + public final boolean removeImports; + public final boolean removeVariableFinal; + + public SourceSettings(boolean removeImports, boolean removeVariableFinal) { + this.removeImports = removeImports; + this.removeVariableFinal = removeVariableFinal; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/Token.java b/enigma/src/main/java/cuchaz/enigma/source/Token.java new file mode 100644 index 00000000..fad733fa --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/Token.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.source; + +public class Token implements Comparable { + + public int start; + public int end; + public String text; + + public Token(int start, int end, String text) { + this.start = start; + this.end = end; + this.text = text; + } + + public int getRenameOffset(String to) { + int length = this.end - this.start; + return to.length() - length; + } + + public void rename(StringBuffer source, String to) { + int oldEnd = this.end; + this.text = to; + this.end = this.start + to.length(); + + source.replace(start, oldEnd, to); + } + + public Token move(int offset) { + Token token = new Token(this.start + offset, this.end + offset, null); + token.text = text; + return token; + } + + public boolean contains(int pos) { + return pos >= start && pos <= end; + } + + @Override + public int compareTo(Token other) { + return start - other.start; + } + + @Override + public boolean equals(Object other) { + return other instanceof Token && equals((Token) other); + } + + @Override + public int hashCode() { + return start * 37 + end; + } + + public boolean equals(Token other) { + return start == other.start && end == other.end && text.equals(other.text); + } + + @Override + public String toString() { + return String.format("[%d,%d]", start, end); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java b/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java new file mode 100644 index 00000000..9e37f168 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java @@ -0,0 +1,108 @@ +package cuchaz.enigma.source.cfr; + +import com.google.common.io.ByteStreams; +import cuchaz.enigma.ClassProvider; +import cuchaz.enigma.source.Decompiler; +import cuchaz.enigma.source.Source; +import cuchaz.enigma.source.SourceSettings; +import org.benf.cfr.reader.apiunreleased.ClassFileSource2; +import org.benf.cfr.reader.apiunreleased.JarContent; +import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair; +import org.benf.cfr.reader.entities.ClassFile; +import org.benf.cfr.reader.mapping.MappingFactory; +import org.benf.cfr.reader.mapping.ObfuscationMapping; +import org.benf.cfr.reader.relationship.MemberNameResolver; +import org.benf.cfr.reader.state.DCCommonState; +import org.benf.cfr.reader.state.TypeUsageCollectingDumper; +import org.benf.cfr.reader.util.AnalysisType; +import org.benf.cfr.reader.util.CannotLoadClassException; +import org.benf.cfr.reader.util.collections.ListFactory; +import org.benf.cfr.reader.util.getopt.Options; +import org.benf.cfr.reader.util.getopt.OptionsImpl; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + + +public class CfrDecompiler implements Decompiler { + private final DCCommonState state; + + public CfrDecompiler(ClassProvider classProvider, SourceSettings sourceSettings) { + Map options = new HashMap<>(); + + state = new DCCommonState(OptionsImpl.getFactory().create(options), new ClassFileSource2() { + @Override + public JarContent addJarContent(String s, AnalysisType analysisType) { + return null; + } + + @Override + public void informAnalysisRelativePathDetail(String usePath, String classFilePath) { + + } + + @Override + public Collection addJar(String jarPath) { + return null; + } + + @Override + public String getPossiblyRenamedPath(String path) { + return path; + } + + @Override + public Pair getClassFileContent(String path) { + ClassNode node = classProvider.getClassNode(path.substring(0, path.lastIndexOf('.'))); + + if (node == null) { + try (InputStream classResource = CfrDecompiler.class.getClassLoader().getResourceAsStream(path)) { + if (classResource != null) { + return new Pair<>(ByteStreams.toByteArray(classResource), path); + } + } catch (IOException ignored) {} + + return null; + } + + ClassWriter cw = new ClassWriter(0); + node.accept(cw); + return new Pair<>(cw.toByteArray(), path); + } + }); + } + + @Override + public Source getSource(String className) { + DCCommonState state = this.state; + Options options = state.getOptions(); + + ObfuscationMapping mapping = MappingFactory.get(options, state); + state = new DCCommonState(state, mapping); + ClassFile tree = state.getClassFileMaybePath(className); + + state.configureWith(tree); + + // To make sure we're analysing the cached version + try { + tree = state.getClassFile(tree.getClassType()); + } catch (CannotLoadClassException ignored) {} + + if (options.getOption(OptionsImpl.DECOMPILE_INNER_CLASSES)) { + tree.loadInnerClasses(state); + } + + if (options.getOption(OptionsImpl.RENAME_DUP_MEMBERS)) { + MemberNameResolver.resolveNames(state, ListFactory.newList(state.getClassCache().getLoadedTypes())); + } + + TypeUsageCollectingDumper typeUsageCollector = new TypeUsageCollectingDumper(options, tree); + tree.analyseTop(state, typeUsageCollector); + return new CfrSource(tree, state, typeUsageCollector.getRealTypeUsageInformation()); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java b/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java new file mode 100644 index 00000000..d4f2da6a --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java @@ -0,0 +1,38 @@ +package cuchaz.enigma.source.cfr; + +import cuchaz.enigma.source.Source; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.translation.mapping.EntryRemapper; +import org.benf.cfr.reader.entities.ClassFile; +import org.benf.cfr.reader.state.DCCommonState; +import org.benf.cfr.reader.state.TypeUsageInformation; + +public class CfrSource implements Source { + private final ClassFile tree; + private final SourceIndex index; + private final String string; + + public CfrSource(ClassFile tree, DCCommonState state, TypeUsageInformation typeUsages) { + this.tree = tree; + + EnigmaDumper dumper = new EnigmaDumper(typeUsages); + tree.dump(state.getObfuscationMapping().wrap(dumper)); + index = dumper.getIndex(); + string = dumper.getString(); + } + + @Override + public String asString() { + return string; + } + + @Override + public Source addJavadocs(EntryRemapper remapper) { + return this; // TODO + } + + @Override + public SourceIndex index() { + return index; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java b/enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java new file mode 100644 index 00000000..e05a6ffa --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java @@ -0,0 +1,433 @@ +package cuchaz.enigma.source.cfr; + +import cuchaz.enigma.source.Token; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.*; +import org.benf.cfr.reader.bytecode.analysis.types.*; +import org.benf.cfr.reader.bytecode.analysis.variables.NamedVariable; +import org.benf.cfr.reader.entities.Field; +import org.benf.cfr.reader.entities.Method; +import org.benf.cfr.reader.mapping.NullMapping; +import org.benf.cfr.reader.mapping.ObfuscationMapping; +import org.benf.cfr.reader.state.TypeUsageInformation; +import org.benf.cfr.reader.util.collections.SetFactory; +import org.benf.cfr.reader.util.output.DelegatingDumper; +import org.benf.cfr.reader.util.output.Dumpable; +import org.benf.cfr.reader.util.output.Dumper; +import org.benf.cfr.reader.util.output.TypeContext; + +import java.util.Set; +import java.util.stream.Collectors; + +public class EnigmaDumper implements Dumper { + private int outputCount = 0; + private int indent; + private boolean atStart = true; + private boolean pendingCR = false; + private final StringBuilder sb = new StringBuilder(); + private final TypeUsageInformation typeUsageInformation; + private final Set emitted = SetFactory.newSet(); + private final SourceIndex index = new SourceIndex(); + private int position; + + + public EnigmaDumper(TypeUsageInformation typeUsageInformation) { + this.typeUsageInformation = typeUsageInformation; + } + + private void append(String s) { + sb.append(s); + position += s.length(); + } + + private String getDesc(JavaTypeInstance type) { + if (!type.isUsableType() && type != RawJavaType.VOID) { + throw new IllegalArgumentException(type.toString()); + } + + if (type instanceof JavaGenericBaseInstance) { + return getDesc(type.getDeGenerifiedType()); + } + + if (type instanceof JavaRefTypeInstance) { + return "L" + type.getRawName().replace('.', '/') + ";"; + } + + if (type instanceof JavaArrayTypeInstance) { + return "[" + getDesc(((JavaArrayTypeInstance) type).removeAnArrayIndirection()); + } + + if (type instanceof RawJavaType) { + switch ((RawJavaType) type) { + case BOOLEAN: + return "Z"; + case BYTE: + return "B"; + case CHAR: + return "C"; + case SHORT: + return "S"; + case INT: + return "I"; + case LONG: + return "J"; + case FLOAT: + return "F"; + case DOUBLE: + return "D"; + case VOID: + return "V"; + default: + throw new AssertionError(); + } + } + + throw new AssertionError(); + } + + private MethodEntry getMethodEntry(MethodPrototype method) { + if (method == null || method.getClassType() == null) { + return null; + } + + MethodDescriptor desc = new MethodDescriptor( + method.getArgs().stream().map(type -> new TypeDescriptor(getDesc(type))).collect(Collectors.toList()), + new TypeDescriptor(method.getName().equals("") || method.getName().equals("") ? "V" : getDesc(method.getReturnType())) + ); + + return new MethodEntry(getClassEntry(method.getClassType()), method.getName(), desc); + } + + private LocalVariableEntry getParameterEntry(MethodPrototype method, int parameterIndex, String name) { + int variableIndex = method.isInstanceMethod() ? 1 : 0; + for (int i = 0; i < parameterIndex; i++) { + variableIndex += method.getArgs().get(i).getStackType().getComputationCategory(); + } + + return new LocalVariableEntry(getMethodEntry(method), variableIndex, name, true, null); + } + + private FieldEntry getFieldEntry(JavaTypeInstance owner, String name, JavaTypeInstance type) { + return new FieldEntry(getClassEntry(owner), name, new TypeDescriptor(getDesc(type))); + } + + private ClassEntry getClassEntry(JavaTypeInstance type) { + return new ClassEntry(type.getRawName().replace('.', '/')); + } + + @Override + public Dumper beginBlockComment(boolean inline) { + print("/*").newln(); + return this; + } + + @Override + public Dumper endBlockComment() { + print(" */").newln(); + return this; + } + + @Override + public Dumper label(String s, boolean inline) { + processPendingCR(); + append(s); + append(":"); + return this; + } + + @Override + public Dumper comment(String s) { + append("// "); + append(s); + append("\n"); + return this; + } + + @Override + public void enqueuePendingCarriageReturn() { + pendingCR = true; + } + + @Override + public Dumper removePendingCarriageReturn() { + pendingCR = false; + return this; + } + + private void processPendingCR() { + if (pendingCR) { + append("\n"); + atStart = true; + pendingCR = false; + } + } + + @Override + public Dumper identifier(String s, Object ref, boolean defines) { + return print(s); + } + + @Override + public Dumper methodName(String name, MethodPrototype method, boolean special, boolean defines) { + doIndent(); + Token token = new Token(position, position + name.length(), name); + Entry entry = getMethodEntry(method); + + if (entry != null) { + if (defines) { + index.addDeclaration(token, entry); + } else { + index.addReference(token, entry, null); + } + } + + return identifier(name, null, defines); + } + + @Override + public Dumper parameterName(String name, MethodPrototype method, int index, boolean defines) { + doIndent(); + Token token = new Token(position, position + name.length(), name); + Entry entry = getParameterEntry(method, index, name); + + if (entry != null) { + if (defines) { + this.index.addDeclaration(token, entry); + } else { + this.index.addReference(token, entry, null); + } + } + + return identifier(name, null, defines); + } + + @Override + public Dumper variableName(String name, NamedVariable variable, boolean defines) { + return identifier(name, null, defines); + } + + @Override + public Dumper packageName(JavaRefTypeInstance t) { + String s = t.getPackageName(); + + if (!s.isEmpty()) { + keyword("package ").print(s).endCodeln().newln(); + } + + return this; + } + + @Override + public Dumper fieldName(String name, Field field, JavaTypeInstance owner, boolean hiddenDeclaration, boolean defines) { + doIndent(); + Token token = new Token(position, position + name.length(), name); + Entry entry = field == null ? null : getFieldEntry(owner, name, field.getJavaTypeInstance()); + + if (entry != null) { + if (defines) { + index.addDeclaration(token, entry); + } else { + index.addReference(token, entry, null); + } + } + + identifier(name, null, defines); + return this; + } + + @Override + public Dumper print(String s) { + processPendingCR(); + doIndent(); + append(s); + atStart = s.endsWith("\n"); + outputCount++; + return this; + } + + @Override + public Dumper print(char c) { + return print(String.valueOf(c)); + } + + @Override + public Dumper newln() { + append("\n"); + atStart = true; + outputCount++; + return this; + } + + @Override + public Dumper endCodeln() { + append(";\n"); + atStart = true; + outputCount++; + return this; + } + + @Override + public Dumper keyword(String s) { + print(s); + return this; + } + + @Override + public Dumper operator(String s) { + print(s); + return this; + } + + @Override + public Dumper separator(String s) { + print(s); + return this; + } + + @Override + public Dumper literal(String s, Object o) { + print(s); + return this; + } + + private void doIndent() { + if (!atStart) return; + String indents = " "; + + for (int x = 0; x < indent; ++x) { + append(indents); + } + + atStart = false; + } + + @Override + public void indent(int diff) { + indent += diff; + } + + @Override + public Dumper dump(Dumpable d) { + if (d == null) { + keyword("null"); + return this; + } + + d.dump(this); + return this; + } + + @Override + public TypeUsageInformation getTypeUsageInformation() { + return typeUsageInformation; + } + + @Override + public ObfuscationMapping getObfuscationMapping() { + return NullMapping.INSTANCE; + } + + @Override + public String toString() { + return sb.toString(); + } + + @Override + public void addSummaryError(Method method, String s) {} + + @Override + public void close() { + } + + @Override + public boolean canEmitClass(JavaTypeInstance type) { + return emitted.add(type); + } + + @Override + public int getOutputCount() { + return outputCount; + } + + @Override + public Dumper dump(JavaTypeInstance type) { + return dump(type, TypeContext.None, false); + } + + @Override + public Dumper dump(JavaTypeInstance type, boolean defines) { + return dump(type, TypeContext.None, false); + } + + @Override + public Dumper dump(JavaTypeInstance type, TypeContext context) { + return dump(type, context, false); + } + + private Dumper dump(JavaTypeInstance type, TypeContext context, boolean defines) { + doIndent(); + if (type instanceof JavaRefTypeInstance) { + int start = position; + type.dumpInto(this, typeUsageInformation, TypeContext.None); + int end = position; + Token token = new Token(start, end, sb.toString().substring(start, end)); + + if (defines) { + index.addDeclaration(token, getClassEntry(type)); + } else { + index.addReference(token, getClassEntry(type), null); + } + + return this; + } + + type.dumpInto(this, typeUsageInformation, context); + return this; + } + + @Override + public Dumper withTypeUsageInformation(TypeUsageInformation innerclassTypeUsageInformation) { + return new WithTypeUsageInformationDumper(this, innerclassTypeUsageInformation); + } + + public SourceIndex getIndex() { + index.setSource(getString()); + return index; + } + + public String getString() { + return sb.toString(); + } + + public static class WithTypeUsageInformationDumper extends DelegatingDumper { + private final TypeUsageInformation typeUsageInformation; + + WithTypeUsageInformationDumper(Dumper delegate, TypeUsageInformation typeUsageInformation) { + super(delegate); + this.typeUsageInformation = typeUsageInformation; + } + + @Override + public TypeUsageInformation getTypeUsageInformation() { + return typeUsageInformation; + } + + @Override + public Dumper dump(JavaTypeInstance javaTypeInstance) { + return dump(javaTypeInstance, TypeContext.None); + } + + @Override + public Dumper dump(JavaTypeInstance javaTypeInstance, TypeContext typeContext) { + javaTypeInstance.dumpInto(this, typeUsageInformation, typeContext); + return this; + } + + @Override + public Dumper withTypeUsageInformation(TypeUsageInformation innerclassTypeUsageInformation) { + return new WithTypeUsageInformationDumper(delegate, innerclassTypeUsageInformation); + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java new file mode 100644 index 00000000..2fae61a6 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java @@ -0,0 +1,49 @@ +package cuchaz.enigma.source.procyon; + +import com.strobel.assembler.metadata.FieldDefinition; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.Signature; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.ClassDefEntry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.FieldDefEntry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; + +public class EntryParser { + public static FieldDefEntry parse(FieldDefinition definition) { + ClassEntry owner = parse(definition.getDeclaringType()); + TypeDescriptor descriptor = new TypeDescriptor(definition.getErasedSignature()); + Signature signature = Signature.createTypedSignature(definition.getSignature()); + AccessFlags access = new AccessFlags(definition.getModifiers()); + return new FieldDefEntry(owner, definition.getName(), descriptor, signature, access, null); + } + + public static ClassDefEntry parse(TypeDefinition def) { + String name = def.getInternalName(); + Signature signature = Signature.createSignature(def.getSignature()); + AccessFlags access = new AccessFlags(def.getModifiers()); + ClassEntry superClass = def.getBaseType() != null ? parse(def.getBaseType()) : null; + ClassEntry[] interfaces = def.getExplicitInterfaces().stream().map(EntryParser::parse).toArray(ClassEntry[]::new); + return new ClassDefEntry(name, signature, access, superClass, interfaces); + } + + public static ClassEntry parse(TypeReference typeReference) { + return new ClassEntry(typeReference.getInternalName()); + } + + public static MethodDefEntry parse(MethodDefinition definition) { + ClassEntry classEntry = parse(definition.getDeclaringType()); + MethodDescriptor descriptor = new MethodDescriptor(definition.getErasedSignature()); + Signature signature = Signature.createSignature(definition.getSignature()); + AccessFlags access = new AccessFlags(definition.getModifiers()); + return new MethodDefEntry(classEntry, definition.getName(), descriptor, signature, access, null); + } + + public static TypeDescriptor parseTypeDescriptor(TypeReference type) { + return new TypeDescriptor(type.getErasedSignature()); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java new file mode 100644 index 00000000..3cbd680c --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java @@ -0,0 +1,84 @@ +package cuchaz.enigma.source.procyon; + +import com.strobel.assembler.metadata.ITypeLoader; +import com.strobel.assembler.metadata.MetadataSystem; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.decompiler.DecompilerContext; +import com.strobel.decompiler.DecompilerSettings; +import com.strobel.decompiler.languages.java.BraceStyle; +import com.strobel.decompiler.languages.java.JavaFormattingOptions; +import com.strobel.decompiler.languages.java.ast.AstBuilder; +import com.strobel.decompiler.languages.java.ast.CompilationUnit; +import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; +import cuchaz.enigma.ClassProvider; +import cuchaz.enigma.source.Source; +import cuchaz.enigma.source.Decompiler; +import cuchaz.enigma.source.SourceSettings; +import cuchaz.enigma.source.procyon.transformers.*; +import cuchaz.enigma.source.procyon.typeloader.CompiledSourceTypeLoader; +import cuchaz.enigma.source.procyon.typeloader.NoRetryMetadataSystem; +import cuchaz.enigma.source.procyon.typeloader.SynchronizedTypeLoader; + +public class ProcyonDecompiler implements Decompiler { + private final SourceSettings settings; + private final DecompilerSettings decompilerSettings; + private final MetadataSystem metadataSystem; + + public ProcyonDecompiler(ClassProvider classProvider, SourceSettings settings) { + ITypeLoader typeLoader = new SynchronizedTypeLoader(new CompiledSourceTypeLoader(classProvider)); + + metadataSystem = new NoRetryMetadataSystem(typeLoader); + metadataSystem.setEagerMethodLoadingEnabled(true); + + decompilerSettings = DecompilerSettings.javaDefaults(); + decompilerSettings.setMergeVariables(getSystemPropertyAsBoolean("enigma.mergeVariables", true)); + decompilerSettings.setForceExplicitImports(getSystemPropertyAsBoolean("enigma.forceExplicitImports", true)); + decompilerSettings.setForceExplicitTypeArguments(getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true)); + decompilerSettings.setShowDebugLineNumbers(getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false)); + decompilerSettings.setShowSyntheticMembers(getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false)); + decompilerSettings.setTypeLoader(typeLoader); + + JavaFormattingOptions formattingOptions = decompilerSettings.getJavaFormattingOptions(); + formattingOptions.ClassBraceStyle = BraceStyle.EndOfLine; + formattingOptions.InterfaceBraceStyle = BraceStyle.EndOfLine; + formattingOptions.EnumBraceStyle = BraceStyle.EndOfLine; + + this.settings = settings; + } + + @Override + public Source getSource(String className) { + TypeReference type = metadataSystem.lookupType(className); + if (type == null) { + throw new Error(String.format("Unable to find desc: %s", className)); + } + + TypeDefinition resolvedType = type.resolve(); + + DecompilerContext context = new DecompilerContext(); + context.setCurrentType(resolvedType); + context.setSettings(decompilerSettings); + + AstBuilder builder = new AstBuilder(context); + builder.addType(resolvedType); + builder.runTransformations(null); + CompilationUnit source = builder.getCompilationUnit(); + + new ObfuscatedEnumSwitchRewriterTransform(context).run(source); + new VarargsFixer(context).run(source); + new RemoveObjectCasts(context).run(source); + new Java8Generics().run(source); + new InvalidIdentifierFix().run(source); + if (settings.removeImports) DropImportAstTransform.INSTANCE.run(source); + if (settings.removeVariableFinal) DropVarModifiersAstTransform.INSTANCE.run(source); + source.acceptVisitor(new InsertParenthesesVisitor(), null); + + return new ProcyonSource(source, decompilerSettings); + } + + private static boolean getSystemPropertyAsBoolean(String property, boolean defValue) { + String value = System.getProperty(property); + return value == null ? defValue : Boolean.parseBoolean(value); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java new file mode 100644 index 00000000..53c8c70b --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java @@ -0,0 +1,49 @@ +package cuchaz.enigma.source.procyon; + +import com.strobel.decompiler.DecompilerSettings; +import com.strobel.decompiler.PlainTextOutput; +import com.strobel.decompiler.languages.java.JavaOutputVisitor; +import com.strobel.decompiler.languages.java.ast.CompilationUnit; +import cuchaz.enigma.source.Source; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.source.procyon.index.SourceIndexVisitor; +import cuchaz.enigma.source.procyon.transformers.AddJavadocsAstTransform; +import cuchaz.enigma.translation.mapping.EntryRemapper; + +import java.io.StringWriter; + +public class ProcyonSource implements Source { + private final DecompilerSettings settings; + private final CompilationUnit tree; + private String string; + + public ProcyonSource(CompilationUnit tree, DecompilerSettings settings) { + this.settings = settings; + this.tree = tree; + } + + @Override + public SourceIndex index() { + SourceIndex index = new SourceIndex(asString()); + tree.acceptVisitor(new SourceIndexVisitor(), index); + return index; + } + + @Override + public String asString() { + if (string == null) { + StringWriter writer = new StringWriter(); + tree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(writer), settings), null); + string = writer.toString(); + } + + return string; + } + + @Override + public Source addJavadocs(EntryRemapper remapper) { + CompilationUnit remappedTree = (CompilationUnit) tree.clone(); + new AddJavadocsAstTransform(remapper).run(remappedTree); + return new ProcyonSource(remappedTree, settings); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java new file mode 100644 index 00000000..f6eeb159 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.source.procyon.index; + +import com.strobel.assembler.metadata.FieldDefinition; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.decompiler.languages.TextLocation; +import com.strobel.decompiler.languages.java.ast.*; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.source.procyon.EntryParser; +import cuchaz.enigma.translation.representation.entry.*; + +public class SourceIndexClassVisitor extends SourceIndexVisitor { + private ClassDefEntry classEntry; + + public SourceIndexClassVisitor(ClassDefEntry classEntry) { + this.classEntry = classEntry; + } + + @Override + public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { + // is this this class, or a subtype? + TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); + ClassDefEntry classEntry = EntryParser.parse(def); + if (!classEntry.equals(this.classEntry)) { + // it's a subtype, recurse + index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), classEntry); + return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); + } + + return visitChildren(node, index); + } + + @Override + public Void visitSimpleType(SimpleType node, SourceIndex index) { + TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); + if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { + ClassEntry classEntry = new ClassEntry(ref.getInternalName()); + index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), classEntry, this.classEntry); + } + + return visitChildren(node, index); + } + + @Override + public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { + MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); + MethodDefEntry methodEntry = EntryParser.parse(def); + AstNode tokenNode = node.getNameToken(); + if (methodEntry.isConstructor() && methodEntry.getName().equals("")) { + // for static initializers, check elsewhere for the token node + tokenNode = node.getModifiers().firstOrNullObject(); + } + index.addDeclaration(TokenFactory.createToken(index, tokenNode), methodEntry); + return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index); + } + + @Override + public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { + MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); + MethodDefEntry methodEntry = EntryParser.parse(def); + index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), methodEntry); + return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index); + } + + @Override + public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { + FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); + FieldDefEntry fieldEntry = EntryParser.parse(def); + assert (node.getVariables().size() == 1); + VariableInitializer variable = node.getVariables().firstOrNullObject(); + index.addDeclaration(TokenFactory.createToken(index, variable.getNameToken()), fieldEntry); + return visitChildren(node, index); + } + + @Override + public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { + // treat enum declarations as field declarations + FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); + FieldDefEntry fieldEntry = EntryParser.parse(def); + index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), fieldEntry); + return visitChildren(node, index); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java new file mode 100644 index 00000000..0e8bc51a --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java @@ -0,0 +1,218 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.source.procyon.index; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.strobel.assembler.metadata.*; +import com.strobel.decompiler.ast.Variable; +import com.strobel.decompiler.languages.TextLocation; +import com.strobel.decompiler.languages.java.ast.*; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.source.procyon.EntryParser; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.*; + +import java.lang.Error; +import java.util.HashMap; +import java.util.Map; + +public class SourceIndexMethodVisitor extends SourceIndexVisitor { + private final MethodDefEntry methodEntry; + + private Multimap unmatchedIdentifier = HashMultimap.create(); + private Map> identifierEntryCache = new HashMap<>(); + + public SourceIndexMethodVisitor(MethodDefEntry methodEntry) { + this.methodEntry = methodEntry; + } + + @Override + public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + + // get the behavior entry + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + MethodEntry methodEntry = null; + if (ref instanceof MethodReference) { + methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature())); + } + if (methodEntry != null) { + // get the node for the token + AstNode tokenNode = null; + if (node.getTarget() instanceof MemberReferenceExpression) { + tokenNode = ((MemberReferenceExpression) node.getTarget()).getMemberNameToken(); + } else if (node.getTarget() instanceof SuperReferenceExpression) { + tokenNode = node.getTarget(); + } else if (node.getTarget() instanceof ThisReferenceExpression) { + tokenNode = node.getTarget(); + } + if (tokenNode != null) { + index.addReference(TokenFactory.createToken(index, tokenNode), methodEntry, this.methodEntry); + } + } + + // Check for identifier + node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression) + .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index)); + return visitChildren(node, index); + } + + @Override + public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref instanceof FieldReference) { + // make sure this is actually a field + String erasedSignature = ref.getErasedSignature(); + if (erasedSignature.indexOf('(') >= 0) { + throw new Error("Expected a field here! got " + ref); + } + + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(erasedSignature)); + index.addReference(TokenFactory.createToken(index, node.getMemberNameToken()), fieldEntry, this.methodEntry); + } + + return visitChildren(node, index); + } + + @Override + public Void visitSimpleType(SimpleType node, SourceIndex index) { + TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); + if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { + ClassEntry classEntry = new ClassEntry(ref.getInternalName()); + index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), classEntry, this.methodEntry); + } + + return visitChildren(node, index); + } + + @Override + public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { + ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); + int parameterIndex = def.getSlot(); + + if (parameterIndex >= 0) { + MethodDefEntry ownerMethod = methodEntry; + if (def.getMethod() instanceof MethodDefinition) { + ownerMethod = EntryParser.parse((MethodDefinition) def.getMethod()); + } + + TypeDescriptor parameterType = EntryParser.parseTypeDescriptor(def.getParameterType()); + LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, parameterIndex, node.getName(), true, parameterType, null); + Identifier identifier = node.getNameToken(); + // cache the argument entry and the identifier + identifierEntryCache.put(identifier.getName(), localVariableEntry); + index.addDeclaration(TokenFactory.createToken(index, identifier), localVariableEntry); + } + + return visitChildren(node, index); + } + + @Override + public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null) { + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(ref.getErasedSignature())); + index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), fieldEntry, this.methodEntry); + } else + this.checkIdentifier(node, index); + return visitChildren(node, index); + } + + private void checkIdentifier(IdentifierExpression node, SourceIndex index) { + if (identifierEntryCache.containsKey(node.getIdentifier())) // If it's in the argument cache, create a token! + index.addDeclaration(TokenFactory.createToken(index, node.getIdentifierToken()), identifierEntryCache.get(node.getIdentifier())); + else + unmatchedIdentifier.put(node.getIdentifier(), node.getIdentifierToken()); // Not matched actually, put it! + } + + private void addDeclarationToUnmatched(String key, SourceIndex index) { + Entry entry = identifierEntryCache.get(key); + + // This cannot happened in theory + if (entry == null) + return; + for (Identifier identifier : unmatchedIdentifier.get(key)) + index.addDeclaration(TokenFactory.createToken(index, identifier), entry); + unmatchedIdentifier.removeAll(key); + } + + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null && node.getType() instanceof SimpleType) { + SimpleType simpleTypeNode = (SimpleType) node.getType(); + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + MethodEntry constructorEntry = new MethodEntry(classEntry, "", new MethodDescriptor(ref.getErasedSignature())); + index.addReference(TokenFactory.createToken(index, simpleTypeNode.getIdentifierToken()), constructorEntry, this.methodEntry); + } + + return visitChildren(node, index); + } + + @Override + public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) { + AstNodeCollection variables = node.getVariables(); + + // Single assignation + if (variables.size() == 1) { + VariableInitializer initializer = variables.firstOrNullObject(); + if (initializer != null && node.getType() instanceof SimpleType) { + Identifier identifier = initializer.getNameToken(); + Variable variable = initializer.getUserData(Keys.VARIABLE); + if (variable != null) { + VariableDefinition originalVariable = variable.getOriginalVariable(); + if (originalVariable != null) { + int variableIndex = originalVariable.getSlot(); + if (variableIndex >= 0) { + MethodDefEntry ownerMethod = EntryParser.parse(originalVariable.getDeclaringMethod()); + TypeDescriptor variableType = EntryParser.parseTypeDescriptor(originalVariable.getVariableType()); + LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, variableIndex, initializer.getName(), false, variableType, null); + identifierEntryCache.put(identifier.getName(), localVariableEntry); + addDeclarationToUnmatched(identifier.getName(), index); + index.addDeclaration(TokenFactory.createToken(index, identifier), localVariableEntry); + } + } + } + } + } + return visitChildren(node, index); + } + + @Override + public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + + if (ref instanceof MethodReference) { + // get the behavior entry + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + MethodEntry methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature())); + + // get the node for the token + AstNode methodNameToken = node.getMethodNameToken(); + AstNode targetToken = node.getTarget(); + + if (methodNameToken != null) { + index.addReference(TokenFactory.createToken(index, methodNameToken), methodEntry, this.methodEntry); + } + + if (targetToken != null && !(targetToken instanceof ThisReferenceExpression)) { + index.addReference(TokenFactory.createToken(index, targetToken), methodEntry.getParent(), this.methodEntry); + } + } + + return visitChildren(node, index); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java new file mode 100644 index 00000000..dad505f7 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.source.procyon.index; + +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; +import com.strobel.decompiler.languages.java.ast.Keys; +import com.strobel.decompiler.languages.java.ast.TypeDeclaration; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.source.procyon.EntryParser; +import cuchaz.enigma.translation.representation.entry.ClassDefEntry; + +public class SourceIndexVisitor extends DepthFirstAstVisitor { + @Override + public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { + TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); + ClassDefEntry classEntry = EntryParser.parse(def); + index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), classEntry); + + return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); + } + + @Override + protected Void visitChildren(AstNode node, SourceIndex index) { + for (final AstNode child : node.getChildren()) { + child.acceptVisitor(this, index); + } + return null; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java new file mode 100644 index 00000000..dc36865a --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java @@ -0,0 +1,46 @@ +package cuchaz.enigma.source.procyon.index; + +import com.strobel.decompiler.languages.Region; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; +import com.strobel.decompiler.languages.java.ast.Identifier; +import com.strobel.decompiler.languages.java.ast.TypeDeclaration; +import cuchaz.enigma.source.Token; +import cuchaz.enigma.source.SourceIndex; + +import java.util.regex.Pattern; + +public class TokenFactory { + private static final Pattern ANONYMOUS_INNER = Pattern.compile("\\$\\d+$"); + + public static Token createToken(SourceIndex index, AstNode node) { + String name = node instanceof Identifier ? ((Identifier) node).getName() : ""; + Region region = node.getRegion(); + + if (region.getBeginLine() == 0) { + System.err.println("Got bad region from Procyon for node " + node); + return null; + } + + int start = index.getPosition(region.getBeginLine(), region.getBeginColumn()); + int end = index.getPosition(region.getEndLine(), region.getEndColumn()); + String text = index.getSource().substring(start, end); + Token token = new Token(start, end, text); + + boolean isAnonymousInner = + node instanceof Identifier && + name.indexOf('$') >= 0 && node.getParent() instanceof ConstructorDeclaration && + name.lastIndexOf('$') >= 0 && + !ANONYMOUS_INNER.matcher(name).matches(); + + if (isAnonymousInner) { + TypeDeclaration type = node.getParent().getParent() instanceof TypeDeclaration ? (TypeDeclaration) node.getParent().getParent() : null; + if (type != null) { + name = type.getName(); + token.end = token.start + name.length(); + } + } + + return token; + } +} 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 new file mode 100644 index 00000000..70fc8c6b --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java @@ -0,0 +1,134 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.google.common.base.Function; +import com.google.common.base.Strings; +import com.strobel.assembler.metadata.ParameterDefinition; +import com.strobel.decompiler.languages.java.ast.*; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; +import cuchaz.enigma.source.procyon.EntryParser; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryRemapper; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; +import cuchaz.enigma.translation.representation.entry.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +public final class AddJavadocsAstTransform implements IAstTransform { + + private final EntryRemapper remapper; + + public AddJavadocsAstTransform(EntryRemapper remapper) { + this.remapper = remapper; + } + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(remapper), null); + } + + static class Visitor extends DepthFirstAstVisitor { + + private final EntryRemapper remapper; + + Visitor(EntryRemapper remapper) { + this.remapper = remapper; + } + + private void addDoc(T node, Function> retriever) { + final Comment[] comments = getComments(node, retriever); + if (comments != null) { + node.insertChildrenBefore(node.getFirstChild(), Roles.COMMENT, comments); + } + } + + 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()); + 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)); + final Comment[] ret = getComments(node, retriever); + if (ret != null) { + final String paramPrefix = "@param " + mapping.getTargetName() + " "; + final String indent = Strings.repeat(" ", paramPrefix.length()); + ret[0].setContent(paramPrefix + ret[0].getContent()); + for (int i = 1; i < ret.length; i++) { + ret[i].setContent(indent + ret[i].getContent()); + } + } + return ret; + } + + private void visitMethod(AstNode node) { + final MethodDefEntry methodDefEntry = EntryParser.parse(node.getUserData(Keys.METHOD_DEFINITION)); + final Comment[] baseComments = getComments(node, $ -> methodDefEntry); + List comments = new ArrayList<>(); + if (baseComments != null) + Collections.addAll(comments, baseComments); + + for (ParameterDeclaration dec : node.getChildrenByRole(Roles.PARAMETER)) { + ParameterDefinition def = dec.getUserData(Keys.PARAMETER_DEFINITION); + final Comment[] paramComments = getParameterComments(dec, $ -> new LocalVariableDefEntry(methodDefEntry, def.getSlot(), def.getName(), + true, + EntryParser.parseTypeDescriptor(def.getParameterType()), null)); + if (paramComments != null) + Collections.addAll(comments, paramComments); + } + + if (!comments.isEmpty()) { + if (remapper.getObfResolver().resolveEntry(methodDefEntry, ResolutionStrategy.RESOLVE_ROOT).stream().noneMatch(e -> Objects.equals(e, methodDefEntry))) { + comments.add(0, new Comment("{@inheritDoc}", CommentType.Documentation)); + } + final AstNode oldFirst = node.getFirstChild(); + for (Comment comment : comments) { + node.insertChildBefore(oldFirst, comment, Roles.COMMENT); + } + } + } + + @Override + protected Void visitChildren(AstNode node, Void data) { + for (final AstNode child : node.getChildren()) { + child.acceptVisitor(this, data); + } + return null; + } + + @Override + public Void visitMethodDeclaration(MethodDeclaration node, Void data) { + visitMethod(node); + return super.visitMethodDeclaration(node, data); + } + + @Override + public Void visitConstructorDeclaration(ConstructorDeclaration node, Void data) { + visitMethod(node); + return super.visitConstructorDeclaration(node, data); + } + + @Override + public Void visitFieldDeclaration(FieldDeclaration node, Void data) { + addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.FIELD_DEFINITION))); + return super.visitFieldDeclaration(node, data); + } + + @Override + public Void visitTypeDeclaration(TypeDeclaration node, Void data) { + addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.TYPE_DEFINITION))); + return super.visitTypeDeclaration(node, data); + } + + @Override + public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void data) { + addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.FIELD_DEFINITION))); + return super.visitEnumValueDeclaration(node, data); + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java new file mode 100644 index 00000000..39e599d3 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java @@ -0,0 +1,33 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; +import com.strobel.decompiler.languages.java.ast.ImportDeclaration; +import com.strobel.decompiler.languages.java.ast.PackageDeclaration; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +public final class DropImportAstTransform implements IAstTransform { + public static final DropImportAstTransform INSTANCE = new DropImportAstTransform(); + + private DropImportAstTransform() { + } + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(), null); + } + + static class Visitor extends DepthFirstAstVisitor { + @Override + public Void visitPackageDeclaration(PackageDeclaration node, Void data) { + node.remove(); + return null; + } + + @Override + public Void visitImportDeclaration(ImportDeclaration node, Void data) { + node.remove(); + return null; + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java new file mode 100644 index 00000000..b8c087b9 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java @@ -0,0 +1,37 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.decompiler.languages.java.ast.*; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +import javax.lang.model.element.Modifier; + +public final class DropVarModifiersAstTransform implements IAstTransform { + public static final DropVarModifiersAstTransform INSTANCE = new DropVarModifiersAstTransform(); + + private DropVarModifiersAstTransform() { + } + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(), null); + } + + static class Visitor extends DepthFirstAstVisitor { + @Override + public Void visitParameterDeclaration(ParameterDeclaration node, Void data) { + for (JavaModifierToken modifierToken : node.getChildrenByRole(EntityDeclaration.MODIFIER_ROLE)) { + if (modifierToken.getModifier() == Modifier.FINAL) { + modifierToken.remove(); + } + } + + return null; + } + + @Override + public Void visitVariableDeclaration(VariableDeclarationStatement node, Void data) { + node.removeModifier(Modifier.FINAL); + return null; + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java new file mode 100644 index 00000000..34d95fa5 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java @@ -0,0 +1,29 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; +import com.strobel.decompiler.languages.java.ast.Identifier; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +/** + * Created by Thiakil on 13/07/2018. + */ +public class InvalidIdentifierFix implements IAstTransform { + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(), null); + } + + class Visitor extends DepthFirstAstVisitor{ + @Override + public Void visitIdentifier(Identifier node, Void data) { + super.visitIdentifier(node, data); + if (node.getName().equals("do") || node.getName().equals("if")){ + Identifier newIdentifier = Identifier.create(node.getName() + "_", node.getStartLocation()); + newIdentifier.copyUserDataFrom(node); + node.replaceWith(newIdentifier); + } + return null; + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java new file mode 100644 index 00000000..8accfc7c --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java @@ -0,0 +1,107 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.assembler.metadata.BuiltinTypes; +import com.strobel.assembler.metadata.CommonTypeReferences; +import com.strobel.assembler.metadata.Flags; +import com.strobel.assembler.metadata.IGenericInstance; +import com.strobel.assembler.metadata.IMemberDefinition; +import com.strobel.assembler.metadata.JvmType; +import com.strobel.assembler.metadata.MemberReference; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.AstNodeCollection; +import com.strobel.decompiler.languages.java.ast.AstType; +import com.strobel.decompiler.languages.java.ast.CastExpression; +import com.strobel.decompiler.languages.java.ast.ComposedType; +import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; +import com.strobel.decompiler.languages.java.ast.Expression; +import com.strobel.decompiler.languages.java.ast.Identifier; +import com.strobel.decompiler.languages.java.ast.InvocationExpression; +import com.strobel.decompiler.languages.java.ast.Keys; +import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; +import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; +import com.strobel.decompiler.languages.java.ast.Roles; +import com.strobel.decompiler.languages.java.ast.SimpleType; +import com.strobel.decompiler.languages.java.ast.WildcardType; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +/** + * Created by Thiakil on 12/07/2018. + */ +public class Java8Generics implements IAstTransform { + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(), null); + } + + static class Visitor extends DepthFirstAstVisitor{ + + @Override + public Void visitInvocationExpression(InvocationExpression node, Void data) { + super.visitInvocationExpression(node, data); + if (node.getTarget() instanceof MemberReferenceExpression){ + MemberReferenceExpression referenceExpression = (MemberReferenceExpression) node.getTarget(); + if (referenceExpression.getTypeArguments().stream().map(t->{ + TypeReference tr = t.toTypeReference(); + if (tr.getDeclaringType() != null){//ensure that inner types are resolved so we can get the TypeDefinition below + TypeReference resolved = tr.resolve(); + if (resolved != null) + return resolved; + } + return tr; + }).anyMatch(t -> t.isWildcardType() || (t instanceof TypeDefinition && ((TypeDefinition) t).isAnonymous()))) { + //these are invalid for invocations, let the compiler work it out + referenceExpression.getTypeArguments().clear(); + } else if (referenceExpression.getTypeArguments().stream().allMatch(t->t.toTypeReference().equals(CommonTypeReferences.Object))){ + //all are , thereby redundant and/or bad + referenceExpression.getTypeArguments().clear(); + } + } + return null; + } + + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, Void data) { + super.visitObjectCreationExpression(node, data); + AstType type = node.getType(); + if (type instanceof SimpleType && !((SimpleType) type).getTypeArguments().isEmpty()){ + SimpleType simpleType = (SimpleType) type; + AstNodeCollection typeArguments = simpleType.getTypeArguments(); + if (typeArguments.size() == 1 && typeArguments.firstOrNullObject().toTypeReference().equals(CommonTypeReferences.Object)){ + //all are , thereby redundant and/or bad + typeArguments.firstOrNullObject().getChildByRole(Roles.IDENTIFIER).replaceWith(Identifier.create("")); + } + } + return null; + } + + @Override + public Void visitCastExpression(CastExpression node, Void data) { + boolean doReplace = false; + TypeReference typeReference = node.getType().toTypeReference(); + if (typeReference.isArray() && typeReference.getElementType().isGenericType()){ + doReplace = true; + } else if (typeReference.isGenericType()) { + Expression target = node.getExpression(); + if (typeReference instanceof IGenericInstance && ((IGenericInstance)typeReference).getTypeArguments().stream().anyMatch(t->t.isWildcardType())){ + doReplace = true; + } else if (target instanceof InvocationExpression) { + InvocationExpression invocationExpression = (InvocationExpression)target; + if (invocationExpression.getTarget() instanceof MemberReferenceExpression && !((MemberReferenceExpression) invocationExpression.getTarget()).getTypeArguments().isEmpty()) { + ((MemberReferenceExpression) invocationExpression.getTarget()).getTypeArguments().clear(); + doReplace = true; + } + } + } + super.visitCastExpression(node, data); + if (doReplace){ + node.replaceWith(node.getExpression()); + } + return null; + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java new file mode 100644 index 00000000..32bb72f4 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java @@ -0,0 +1,414 @@ +/* + * Originally: + * EnumSwitchRewriterTransform.java + * + * Copyright (c) 2013 Mike Strobel + * + * This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain; + * and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa. + * + * This source code is subject to terms and conditions of the Apache License, Version 2.0. + * A copy of the license can be found in the License.html file at the root of this distribution. + * By using this source code in any fashion, you are agreeing to be bound by the terms of the + * Apache License, Version 2.0. + * + * You must not remove this notice, or any other, from this software. + */ + +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.assembler.metadata.BuiltinTypes; +import com.strobel.assembler.metadata.FieldDefinition; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.core.SafeCloseable; +import com.strobel.core.VerifyArgument; +import com.strobel.decompiler.DecompilerContext; +import com.strobel.decompiler.languages.java.ast.AssignmentExpression; +import com.strobel.decompiler.languages.java.ast.AstBuilder; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.CaseLabel; +import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; +import com.strobel.decompiler.languages.java.ast.Expression; +import com.strobel.decompiler.languages.java.ast.IdentifierExpression; +import com.strobel.decompiler.languages.java.ast.IndexerExpression; +import com.strobel.decompiler.languages.java.ast.InvocationExpression; +import com.strobel.decompiler.languages.java.ast.Keys; +import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; +import com.strobel.decompiler.languages.java.ast.PrimitiveExpression; +import com.strobel.decompiler.languages.java.ast.SwitchSection; +import com.strobel.decompiler.languages.java.ast.SwitchStatement; +import com.strobel.decompiler.languages.java.ast.TypeDeclaration; +import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Copy of {@link com.strobel.decompiler.languages.java.ast.transforms.EnumSwitchRewriterTransform} modified to: + * - Not rely on a field containing "$SwitchMap$" (Proguard strips it) + * - Ignore classes *with* SwitchMap$ names (so the original can handle it) + * - Ignores inner synthetics that are not package private + */ +@SuppressWarnings("Duplicates") +public class ObfuscatedEnumSwitchRewriterTransform implements IAstTransform { + private final DecompilerContext _context; + + public ObfuscatedEnumSwitchRewriterTransform(final DecompilerContext context) { + _context = VerifyArgument.notNull(context, "context"); + } + + @Override + public void run(final AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(_context), null); + } + + private final static class Visitor extends ContextTrackingVisitor { + private final static class SwitchMapInfo { + final String enclosingType; + final Map> switches = new LinkedHashMap<>(); + final Map> mappings = new LinkedHashMap<>(); + + TypeDeclaration enclosingTypeDeclaration; + + SwitchMapInfo(final String enclosingType) { + this.enclosingType = enclosingType; + } + } + + private final Map _switchMaps = new LinkedHashMap<>(); + private boolean _isSwitchMapWrapper; + + protected Visitor(final DecompilerContext context) { + super(context); + } + + @Override + public Void visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Void p) { + final boolean oldIsSwitchMapWrapper = _isSwitchMapWrapper; + final TypeDefinition typeDefinition = typeDeclaration.getUserData(Keys.TYPE_DEFINITION); + final boolean isSwitchMapWrapper = isSwitchMapWrapper(typeDefinition); + + if (isSwitchMapWrapper) { + final String internalName = typeDefinition.getInternalName(); + + SwitchMapInfo info = _switchMaps.get(internalName); + + if (info == null) { + _switchMaps.put(internalName, info = new SwitchMapInfo(internalName)); + } + + info.enclosingTypeDeclaration = typeDeclaration; + } + + _isSwitchMapWrapper = isSwitchMapWrapper; + + try { + super.visitTypeDeclaration(typeDeclaration, p); + } + finally { + _isSwitchMapWrapper = oldIsSwitchMapWrapper; + } + + rewrite(); + + return null; + } + + @Override + public Void visitSwitchStatement(final SwitchStatement node, final Void data) { + final Expression test = node.getExpression(); + + if (test instanceof IndexerExpression) { + final IndexerExpression indexer = (IndexerExpression) test; + final Expression array = indexer.getTarget(); + final Expression argument = indexer.getArgument(); + + if (!(array instanceof MemberReferenceExpression)) { + return super.visitSwitchStatement(node, data); + } + + final MemberReferenceExpression arrayAccess = (MemberReferenceExpression) array; + final Expression arrayOwner = arrayAccess.getTarget(); + final String mapName = arrayAccess.getMemberName(); + + if (mapName == null || mapName.startsWith("$SwitchMap$") || !(arrayOwner instanceof TypeReferenceExpression)) { + return super.visitSwitchStatement(node, data); + } + + final TypeReferenceExpression enclosingTypeExpression = (TypeReferenceExpression) arrayOwner; + final TypeReference enclosingType = enclosingTypeExpression.getType().getUserData(Keys.TYPE_REFERENCE); + + if (!isSwitchMapWrapper(enclosingType) || !(argument instanceof InvocationExpression)) { + return super.visitSwitchStatement(node, data); + } + + final InvocationExpression invocation = (InvocationExpression) argument; + final Expression invocationTarget = invocation.getTarget(); + + if (!(invocationTarget instanceof MemberReferenceExpression)) { + return super.visitSwitchStatement(node, data); + } + + final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget; + + if (!"ordinal".equals(memberReference.getMemberName())) { + return super.visitSwitchStatement(node, data); + } + + final String enclosingTypeName = enclosingType.getInternalName(); + + SwitchMapInfo info = _switchMaps.get(enclosingTypeName); + + if (info == null) { + _switchMaps.put(enclosingTypeName, info = new SwitchMapInfo(enclosingTypeName)); + + final TypeDefinition resolvedType = enclosingType.resolve(); + + if (resolvedType != null) { + AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER); + + if (astBuilder == null) { + astBuilder = new AstBuilder(context); + } + + try (final SafeCloseable importSuppression = astBuilder.suppressImports()) { + final TypeDeclaration declaration = astBuilder.createType(resolvedType); + + declaration.acceptVisitor(this, data); + } + } + } + + List switches = info.switches.get(mapName); + + if (switches == null) { + info.switches.put(mapName, switches = new ArrayList<>()); + } + + switches.add(node); + } + + return super.visitSwitchStatement(node, data); + } + + @Override + public Void visitAssignmentExpression(final AssignmentExpression node, final Void data) { + final TypeDefinition currentType = context.getCurrentType(); + final MethodDefinition currentMethod = context.getCurrentMethod(); + + if (_isSwitchMapWrapper && + currentType != null && + currentMethod != null && + currentMethod.isTypeInitializer()) { + + final Expression left = node.getLeft(); + final Expression right = node.getRight(); + + if (left instanceof IndexerExpression && + right instanceof PrimitiveExpression) { + + String mapName = null; + + final Expression array = ((IndexerExpression) left).getTarget(); + final Expression argument = ((IndexerExpression) left).getArgument(); + + if (array instanceof MemberReferenceExpression) { + mapName = ((MemberReferenceExpression) array).getMemberName(); + } + else if (array instanceof IdentifierExpression) { + mapName = ((IdentifierExpression) array).getIdentifier(); + } + + if (mapName == null || mapName.startsWith("$SwitchMap$")) { + return super.visitAssignmentExpression(node, data); + } + + if (!(argument instanceof InvocationExpression)) { + return super.visitAssignmentExpression(node, data); + } + + final InvocationExpression invocation = (InvocationExpression) argument; + final Expression invocationTarget = invocation.getTarget(); + + if (!(invocationTarget instanceof MemberReferenceExpression)) { + return super.visitAssignmentExpression(node, data); + } + + final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget; + final Expression memberTarget = memberReference.getTarget(); + + if (!(memberTarget instanceof MemberReferenceExpression) || !"ordinal".equals(memberReference.getMemberName())) { + return super.visitAssignmentExpression(node, data); + } + + final MemberReferenceExpression outerMemberReference = (MemberReferenceExpression) memberTarget; + final Expression outerMemberTarget = outerMemberReference.getTarget(); + + if (!(outerMemberTarget instanceof TypeReferenceExpression)) { + return super.visitAssignmentExpression(node, data); + } + + final String enclosingType = currentType.getInternalName(); + + SwitchMapInfo info = _switchMaps.get(enclosingType); + + if (info == null) { + _switchMaps.put(enclosingType, info = new SwitchMapInfo(enclosingType)); + + AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER); + + if (astBuilder == null) { + astBuilder = new AstBuilder(context); + } + + info.enclosingTypeDeclaration = astBuilder.createType(currentType); + } + + final PrimitiveExpression value = (PrimitiveExpression) right; + + assert value.getValue() instanceof Integer; + + Map mapping = info.mappings.get(mapName); + + if (mapping == null) { + info.mappings.put(mapName, mapping = new LinkedHashMap<>()); + } + + final IdentifierExpression enumValue = new IdentifierExpression( Expression.MYSTERY_OFFSET, outerMemberReference.getMemberName()); + + enumValue.putUserData(Keys.MEMBER_REFERENCE, outerMemberReference.getUserData(Keys.MEMBER_REFERENCE)); + + mapping.put(((Number) value.getValue()).intValue(), enumValue); + } + } + + return super.visitAssignmentExpression(node, data); + } + + private void rewrite() { + if (_switchMaps.isEmpty()) { + return; + } + + for (final SwitchMapInfo info : _switchMaps.values()) { + rewrite(info); + } + + // + // Remove switch map type wrappers that are no longer referenced. + // + + outer: + for (final SwitchMapInfo info : _switchMaps.values()) { + for (final String mapName : info.switches.keySet()) { + final List switches = info.switches.get(mapName); + + if (switches != null && !switches.isEmpty()) { + continue outer; + } + } + + final TypeDeclaration enclosingTypeDeclaration = info.enclosingTypeDeclaration; + + if (enclosingTypeDeclaration != null) { + enclosingTypeDeclaration.remove(); + } + } + } + + private void rewrite(final SwitchMapInfo info) { + if (info.switches.isEmpty()) { + return; + } + + for (final String mapName : info.switches.keySet()) { + final List switches = info.switches.get(mapName); + final Map mappings = info.mappings.get(mapName); + + if (switches != null && mappings != null) { + for (int i = 0; i < switches.size(); i++) { + if (rewriteSwitch(switches.get(i), mappings)) { + switches.remove(i--); + } + } + } + } + } + + private boolean rewriteSwitch(final SwitchStatement s, final Map mappings) { + final Map replacements = new IdentityHashMap<>(); + + for (final SwitchSection section : s.getSwitchSections()) { + for (final CaseLabel caseLabel : section.getCaseLabels()) { + final Expression expression = caseLabel.getExpression(); + + if (expression.isNull()) { + continue; + } + + if (expression instanceof PrimitiveExpression) { + final Object value = ((PrimitiveExpression) expression).getValue(); + + if (value instanceof Integer) { + final Expression replacement = mappings.get(value); + + if (replacement != null) { + replacements.put(expression, replacement); + continue; + } + } + } + + // + // If we can't rewrite all cases, we abort. + // + + return false; + } + } + + final IndexerExpression indexer = (IndexerExpression) s.getExpression(); + final InvocationExpression argument = (InvocationExpression) indexer.getArgument(); + final MemberReferenceExpression memberReference = (MemberReferenceExpression) argument.getTarget(); + final Expression newTest = memberReference.getTarget(); + + newTest.remove(); + indexer.replaceWith(newTest); + + for (final Map.Entry entry : replacements.entrySet()) { + entry.getKey().replaceWith(entry.getValue().clone()); + } + + return true; + } + + private static boolean isSwitchMapWrapper(final TypeReference type) { + if (type == null) { + return false; + } + + final TypeDefinition definition = type instanceof TypeDefinition ? (TypeDefinition) type + : type.resolve(); + + if (definition == null || !definition.isSynthetic() || !definition.isInnerClass() || !definition.isPackagePrivate()) { + return false; + } + + for (final FieldDefinition field : definition.getDeclaredFields()) { + if (!field.getName().startsWith("$SwitchMap$") && + BuiltinTypes.Integer.makeArrayType().equals(field.getFieldType())) { + + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java new file mode 100644 index 00000000..cf0376f3 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java @@ -0,0 +1,39 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.assembler.metadata.BuiltinTypes; +import com.strobel.decompiler.DecompilerContext; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.CastExpression; +import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +/** + * Created by Thiakil on 11/07/2018. + */ +public class RemoveObjectCasts implements IAstTransform { + private final DecompilerContext _context; + + public RemoveObjectCasts(DecompilerContext context) { + _context = context; + } + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(_context), null); + } + + private final static class Visitor extends ContextTrackingVisitor{ + + protected Visitor(DecompilerContext context) { + super(context); + } + + @Override + public Void visitCastExpression(CastExpression node, Void data) { + if (node.getType().toTypeReference().equals(BuiltinTypes.Object)){ + node.replaceWith(node.getExpression()); + } + return super.visitCastExpression(node, data); + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java new file mode 100644 index 00000000..d3ddaab6 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java @@ -0,0 +1,197 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.assembler.metadata.MemberReference; +import com.strobel.assembler.metadata.MetadataFilters; +import com.strobel.assembler.metadata.MetadataHelper; +import com.strobel.assembler.metadata.MethodBinder; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.MethodReference; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.core.StringUtilities; +import com.strobel.core.VerifyArgument; +import com.strobel.decompiler.DecompilerContext; +import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression; +import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.AstNodeCollection; +import com.strobel.decompiler.languages.java.ast.CastExpression; +import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; +import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; +import com.strobel.decompiler.languages.java.ast.Expression; +import com.strobel.decompiler.languages.java.ast.InvocationExpression; +import com.strobel.decompiler.languages.java.ast.JavaResolver; +import com.strobel.decompiler.languages.java.ast.Keys; +import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; +import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; +import com.strobel.decompiler.semantics.ResolveResult; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Thiakil on 12/07/2018. + */ +public class VarargsFixer implements IAstTransform { + private final DecompilerContext _context; + + public VarargsFixer(final DecompilerContext context) { + _context = VerifyArgument.notNull(context, "context"); + } + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(_context), null); + } + + class Visitor extends ContextTrackingVisitor { + private final JavaResolver _resolver; + protected Visitor(DecompilerContext context) { + super(context); + _resolver = new JavaResolver(context); + } + + //remove `new Object[0]` on varagrs as the normal tranformer doesnt do them + @Override + public Void visitInvocationExpression(InvocationExpression node, Void data) { + super.visitInvocationExpression(node, data); + MemberReference definition = node.getUserData(Keys.MEMBER_REFERENCE); + if (definition instanceof MethodDefinition && ((MethodDefinition) definition).isVarArgs()){ + AstNodeCollection arguments = node.getArguments(); + Expression lastParam = arguments.lastOrNullObject(); + if (!lastParam.isNull() && lastParam instanceof ArrayCreationExpression){ + ArrayCreationExpression varargArray = (ArrayCreationExpression)lastParam; + if (varargArray.getInitializer().isNull() || varargArray.getInitializer().getElements().isEmpty()){ + lastParam.remove(); + } else { + for (Expression e : varargArray.getInitializer().getElements()){ + arguments.insertBefore(varargArray, e.clone()); + } + varargArray.remove(); + } + } + } + return null; + } + + //applies the vararg transform to object creation + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, Void data) { + super.visitObjectCreationExpression(node, data); + final AstNodeCollection arguments = node.getArguments(); + final Expression lastArgument = arguments.lastOrNullObject(); + + Expression arrayArg = lastArgument; + + if (arrayArg instanceof CastExpression) + arrayArg = ((CastExpression) arrayArg).getExpression(); + + if (arrayArg == null || + arrayArg.isNull() || + !(arrayArg instanceof ArrayCreationExpression && + node.getTarget() instanceof MemberReferenceExpression)) { + + return null; + } + + final ArrayCreationExpression newArray = (ArrayCreationExpression) arrayArg; + final MemberReferenceExpression target = (MemberReferenceExpression) node.getTarget(); + + if (!newArray.getAdditionalArraySpecifiers().hasSingleElement()) { + return null; + } + + final MethodReference method = (MethodReference) node.getUserData(Keys.MEMBER_REFERENCE); + + if (method == null) { + return null; + } + + final MethodDefinition resolved = method.resolve(); + + if (resolved == null || !resolved.isVarArgs()) { + return null; + } + + final List candidates; + final Expression invocationTarget = target.getTarget(); + + if (invocationTarget == null || invocationTarget.isNull()) { + candidates = MetadataHelper.findMethods( + context.getCurrentType(), + MetadataFilters.matchName(resolved.getName()) + ); + } + else { + final ResolveResult targetResult = _resolver.apply(invocationTarget); + + if (targetResult == null || targetResult.getType() == null) { + return null; + } + + candidates = MetadataHelper.findMethods( + targetResult.getType(), + MetadataFilters.matchName(resolved.getName()) + ); + } + + final List argTypes = new ArrayList<>(); + + for (final Expression argument : arguments) { + final ResolveResult argResult = _resolver.apply(argument); + + if (argResult == null || argResult.getType() == null) { + return null; + } + + argTypes.add(argResult.getType()); + } + + final MethodBinder.BindResult c1 = MethodBinder.selectMethod(candidates, argTypes); + + if (c1.isFailure() || c1.isAmbiguous()) { + return null; + } + + argTypes.remove(argTypes.size() - 1); + + final ArrayInitializerExpression initializer = newArray.getInitializer(); + final boolean hasElements = !initializer.isNull() && !initializer.getElements().isEmpty(); + + if (hasElements) { + for (final Expression argument : initializer.getElements()) { + final ResolveResult argResult = _resolver.apply(argument); + + if (argResult == null || argResult.getType() == null) { + return null; + } + + argTypes.add(argResult.getType()); + } + } + + final MethodBinder.BindResult c2 = MethodBinder.selectMethod(candidates, argTypes); + + if (c2.isFailure() || + c2.isAmbiguous() || + !StringUtilities.equals(c2.getMethod().getErasedSignature(), c1.getMethod().getErasedSignature())) { + + return null; + } + + lastArgument.remove(); + + if (!hasElements) { + lastArgument.remove(); + return null; + } + + for (final Expression newArg : initializer.getElements()) { + newArg.remove(); + arguments.add(newArg); + } + + return null; + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java new file mode 100644 index 00000000..e702956e --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java @@ -0,0 +1,33 @@ +package cuchaz.enigma.source.procyon.typeloader; + +import com.strobel.assembler.metadata.Buffer; +import com.strobel.assembler.metadata.ClasspathTypeLoader; +import com.strobel.assembler.metadata.ITypeLoader; + +/** + * Caching version of {@link ClasspathTypeLoader} + */ +public class CachingClasspathTypeLoader extends CachingTypeLoader { + private static ITypeLoader extraClassPathLoader = null; + + public static void setExtraClassPathLoader(ITypeLoader loader){ + extraClassPathLoader = loader; + } + + private final ITypeLoader classpathLoader = new ClasspathTypeLoader(); + + @Override + protected byte[] doLoad(String className) { + Buffer parentBuf = new Buffer(); + if (classpathLoader.tryLoadType(className, parentBuf)) { + return parentBuf.array(); + } + if (extraClassPathLoader != null){ + parentBuf.reset(); + if (extraClassPathLoader.tryLoadType(className, parentBuf)){ + return parentBuf.array(); + } + } + return EMPTY_ARRAY;//need to return *something* as null means no store + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java new file mode 100644 index 00000000..5be5ddd9 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java @@ -0,0 +1,38 @@ +package cuchaz.enigma.source.procyon.typeloader; + +import com.google.common.collect.Maps; +import com.strobel.assembler.metadata.Buffer; +import com.strobel.assembler.metadata.ITypeLoader; + +import java.util.Map; + +/** + * Common cache functions + */ +public abstract class CachingTypeLoader implements ITypeLoader { + protected static final byte[] EMPTY_ARRAY = {}; + + private final Map cache = Maps.newHashMap(); + + protected abstract byte[] doLoad(String className); + + @Override + public boolean tryLoadType(String className, Buffer out) { + + // check the cache + byte[] data = this.cache.computeIfAbsent(className, this::doLoad); + + if (data == EMPTY_ARRAY) { + return false; + } + + out.reset(data.length); + System.arraycopy(data, 0, out.array(), out.position(), data.length); + out.position(0); + return true; + } + + public void clearCache() { + this.cache.clear(); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java new file mode 100644 index 00000000..e703d3b3 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.source.procyon.typeloader; + +import com.google.common.collect.Lists; +import com.strobel.assembler.metadata.Buffer; +import com.strobel.assembler.metadata.ITypeLoader; +import cuchaz.enigma.ClassProvider; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Function; + +public class CompiledSourceTypeLoader extends CachingTypeLoader { + //Store one instance as the classpath shouldn't change during load + private static final ITypeLoader CLASSPATH_TYPE_LOADER = new CachingClasspathTypeLoader(); + + private final ClassProvider compiledSource; + private final LinkedList> visitors = new LinkedList<>(); + + public CompiledSourceTypeLoader(ClassProvider compiledSource) { + this.compiledSource = compiledSource; + } + + public void addVisitor(Function visitor) { + this.visitors.addFirst(visitor); + } + + @Override + protected byte[] doLoad(String className) { + byte[] data = loadType(className); + if (data == null) { + return loadClasspath(className); + } + + return data; + } + + private byte[] loadClasspath(String name) { + Buffer parentBuf = new Buffer(); + if (CLASSPATH_TYPE_LOADER.tryLoadType(name, parentBuf)) { + return parentBuf.array(); + } + return EMPTY_ARRAY; + } + + private byte[] loadType(String className) { + ClassEntry entry = new ClassEntry(className); + + // find the class in the jar + ClassNode node = findClassNode(entry); + if (node == null) { + // couldn't find it + return null; + } + + removeRedundantClassCalls(node); + + ClassWriter writer = new ClassWriter(0); + + ClassVisitor visitor = writer; + for (Function visitorFunction : this.visitors) { + visitor = visitorFunction.apply(visitor); + } + + node.accept(visitor); + + // we have a transformed class! + return writer.toByteArray(); + } + + private void removeRedundantClassCalls(ClassNode node) { + // remove .getClass() calls that are seemingly injected + // DUP + // INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class; + // POP + for (MethodNode methodNode : node.methods) { + AbstractInsnNode insnNode = methodNode.instructions.getFirst(); + while (insnNode != null) { + if (insnNode instanceof MethodInsnNode && insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + if (methodInsnNode.name.equals("getClass") && methodInsnNode.owner.equals("java/lang/Object") && methodInsnNode.desc.equals("()Ljava/lang/Class;")) { + AbstractInsnNode previous = methodInsnNode.getPrevious(); + AbstractInsnNode next = methodInsnNode.getNext(); + if (previous.getOpcode() == Opcodes.DUP && next.getOpcode() == Opcodes.POP) { + insnNode = previous.getPrevious();//reset the iterator so it gets the new next instruction + methodNode.instructions.remove(previous); + methodNode.instructions.remove(methodInsnNode); + methodNode.instructions.remove(next); + } + } + } + insnNode = insnNode.getNext(); + } + } + } + + private ClassNode findClassNode(ClassEntry entry) { + // try to find the class in the jar + for (String className : getClassNamesToTry(entry)) { + ClassNode node = compiledSource.getClassNode(className); + if (node != null) { + return node; + } + } + + // didn't find it ;_; + return null; + } + + private Collection getClassNamesToTry(ClassEntry entry) { + List classNamesToTry = Lists.newArrayList(); + classNamesToTry.add(entry.getFullName()); + + ClassEntry outerClass = entry.getOuterClass(); + if (outerClass != null) { + classNamesToTry.addAll(getClassNamesToTry(outerClass)); + } + + return classNamesToTry; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java new file mode 100644 index 00000000..c4732b04 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java @@ -0,0 +1,38 @@ +package cuchaz.enigma.source.procyon.typeloader; + +import com.strobel.assembler.metadata.ITypeLoader; +import com.strobel.assembler.metadata.MetadataSystem; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public final class NoRetryMetadataSystem extends MetadataSystem { + private final Set failedTypes = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + public NoRetryMetadataSystem(final ITypeLoader typeLoader) { + super(typeLoader); + } + + @Override + protected synchronized TypeDefinition resolveType(final String descriptor, final boolean mightBePrimitive) { + if (failedTypes.contains(descriptor)) { + return null; + } + + final TypeDefinition result = super.resolveType(descriptor, mightBePrimitive); + + if (result == null) { + failedTypes.add(descriptor); + } + + return result; + } + + @Override + public synchronized TypeDefinition resolve(final TypeReference type) { + return super.resolve(type); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java new file mode 100644 index 00000000..86c6ecc6 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java @@ -0,0 +1,20 @@ +package cuchaz.enigma.source.procyon.typeloader; + +import com.strobel.assembler.metadata.Buffer; +import com.strobel.assembler.metadata.ITypeLoader; + +/** + * Typeloader with synchronized tryLoadType method + */ +public class SynchronizedTypeLoader implements ITypeLoader { + private final ITypeLoader delegate; + + public SynchronizedTypeLoader(ITypeLoader delegate) { + this.delegate = delegate; + } + + @Override + public synchronized boolean tryLoadType(String internalName, Buffer buffer) { + return delegate.tryLoadType(internalName, buffer); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java b/enigma/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java new file mode 100644 index 00000000..18c966cd --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java @@ -0,0 +1,44 @@ +package cuchaz.enigma.translation; + +import cuchaz.enigma.translation.mapping.NameValidator; +import cuchaz.enigma.translation.representation.TypeDescriptor; + +import java.util.Collection; +import java.util.Locale; + +public class LocalNameGenerator { + public static String generateArgumentName(int index, TypeDescriptor desc, Collection arguments) { + boolean uniqueType = arguments.stream().filter(desc::equals).count() <= 1; + String translatedName; + int nameIndex = index + 1; + StringBuilder nameBuilder = new StringBuilder(getTypeName(desc)); + if (!uniqueType || NameValidator.isReserved(nameBuilder.toString())) { + nameBuilder.append(nameIndex); + } + translatedName = nameBuilder.toString(); + return translatedName; + } + + public static String generateLocalVariableName(int index, TypeDescriptor desc) { + int nameIndex = index + 1; + return getTypeName(desc) + nameIndex; + } + + private static String getTypeName(TypeDescriptor desc) { + // Unfortunately each of these have different name getters, so they have different code paths + if (desc.isPrimitive()) { + TypeDescriptor.Primitive argCls = desc.getPrimitive(); + return argCls.name().toLowerCase(Locale.ROOT); + } else if (desc.isArray()) { + // List types would require this whole block again, so just go with aListx + return "arr"; + } else if (desc.isType()) { + String typeName = desc.getTypeEntry().getSimpleName().replace("$", ""); + typeName = typeName.substring(0, 1).toLowerCase(Locale.ROOT) + typeName.substring(1); + return typeName; + } else { + System.err.println("Encountered invalid argument type descriptor " + desc.toString()); + return "var"; + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/MappingTranslator.java b/enigma/src/main/java/cuchaz/enigma/translation/MappingTranslator.java new file mode 100644 index 00000000..529d0edb --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/MappingTranslator.java @@ -0,0 +1,24 @@ +package cuchaz.enigma.translation; + +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.EntryMap; + +public class MappingTranslator implements Translator { + private final EntryMap mappings; + private final EntryResolver resolver; + + public MappingTranslator(EntryMap mappings, EntryResolver resolver) { + this.mappings = mappings; + this.resolver = resolver; + } + + @SuppressWarnings("unchecked") + @Override + public T translate(T translatable) { + if (translatable == null) { + return null; + } + return (T) translatable.translate(this, resolver, mappings); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/ProposingTranslator.java b/enigma/src/main/java/cuchaz/enigma/translation/ProposingTranslator.java new file mode 100644 index 00000000..97866e92 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/ProposingTranslator.java @@ -0,0 +1,51 @@ +package cuchaz.enigma.translation; + +import cuchaz.enigma.api.service.NameProposalService; +import cuchaz.enigma.translation.mapping.EntryRemapper; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; +import cuchaz.enigma.translation.representation.entry.Entry; + +import java.util.Arrays; +import java.util.Optional; + +public class ProposingTranslator implements Translator { + private final EntryRemapper mapper; + private final NameProposalService[] nameProposalServices; + + public ProposingTranslator(EntryRemapper mapper, NameProposalService[] nameProposalServices) { + this.mapper = mapper; + this.nameProposalServices = nameProposalServices; + } + + @Override + @SuppressWarnings("unchecked") + public T translate(T translatable) { + if (translatable == null) { + return null; + } + + T deobfuscated = mapper.deobfuscate(translatable); + + if (translatable instanceof Entry && ((Entry) deobfuscated).getName().equals(((Entry) translatable).getName())) { + return mapper.getObfResolver() + .resolveEntry((Entry) translatable, ResolutionStrategy.RESOLVE_ROOT) + .stream() + .map(this::proposeName) + .filter(Optional::isPresent) + .map(Optional::get) + .findFirst() + .map(newName -> (T) ((Entry) deobfuscated).withName(newName)) + .orElse(deobfuscated); + } + + return deobfuscated; + } + + private Optional proposeName(Entry entry) { + return Arrays.stream(nameProposalServices) + .map(service -> service.proposeName(entry, mapper)) + .filter(Optional::isPresent) + .map(Optional::get) + .findFirst(); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java b/enigma/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java new file mode 100644 index 00000000..37830535 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.translation; + +import com.google.common.collect.Lists; + +import java.io.IOException; +import java.io.StringReader; +import java.util.List; + +public class SignatureUpdater { + + public static String update(String signature, ClassNameUpdater updater) { + try { + StringBuilder buf = new StringBuilder(); + + // read the signature character-by-character + StringReader reader = new StringReader(signature); + int i; + while ((i = reader.read()) != -1) { + char c = (char) i; + + // does this character start a class name? + if (c == 'L') { + // update the class name and add it to the buffer + buf.append('L'); + String className = readClass(reader); + if (className == null) { + throw new IllegalArgumentException("Malformed signature: " + signature); + } + buf.append(updater.update(className)); + buf.append(';'); + } else { + // copy the character into the buffer + buf.append(c); + } + } + + return buf.toString(); + } catch (IOException ex) { + // I'm pretty sure a StringReader will never throw one of these + throw new Error(ex); + } + } + + private static String readClass(StringReader reader) throws IOException { + // read all the characters in the buffer until we hit a ';' + // remember to treat generics correctly + StringBuilder buf = new StringBuilder(); + int depth = 0; + int i; + while ((i = reader.read()) != -1) { + char c = (char) i; + + if (c == '<') { + depth++; + } else if (c == '>') { + depth--; + } else if (depth == 0) { + if (c == ';') { + return buf.toString(); + } else { + buf.append(c); + } + } + } + + return null; + } + + public static List getClasses(String signature) { + final List classNames = Lists.newArrayList(); + update(signature, className -> { + classNames.add(className); + return className; + }); + return classNames; + } + + public interface ClassNameUpdater { + String update(String className); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/Translatable.java b/enigma/src/main/java/cuchaz/enigma/translation/Translatable.java new file mode 100644 index 00000000..0370ef13 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/Translatable.java @@ -0,0 +1,9 @@ +package cuchaz.enigma.translation; + +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.EntryMap; + +public interface Translatable { + Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings); +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/TranslationDirection.java b/enigma/src/main/java/cuchaz/enigma/translation/TranslationDirection.java new file mode 100644 index 00000000..2ecb30be --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/TranslationDirection.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.translation; + +public enum TranslationDirection { + + DEOBFUSCATING { + @Override + public T choose(T deobfChoice, T obfChoice) { + if (deobfChoice == null) { + return obfChoice; + } + return deobfChoice; + } + }, + OBFUSCATING { + @Override + public T choose(T deobfChoice, T obfChoice) { + if (obfChoice == null) { + return deobfChoice; + } + return obfChoice; + } + }; + + public abstract T choose(T deobfChoice, T obfChoice); +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/Translator.java b/enigma/src/main/java/cuchaz/enigma/translation/Translator.java new file mode 100644 index 00000000..c70141f2 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/Translator.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.translation; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public interface Translator { + T translate(T translatable); + + default Collection translate(Collection translatable) { + return translatable.stream() + .map(this::translate) + .collect(Collectors.toList()); + } + + default Set translate(Set translatable) { + return translatable.stream() + .map(this::translate) + .collect(Collectors.toSet()); + } + + default Map translateKeys(Map translatable) { + Map result = new HashMap<>(translatable.size()); + for (Map.Entry entry : translatable.entrySet()) { + result.put(translate(entry.getKey()), entry.getValue()); + } + return result; + } + + default Map translate(Map translatable) { + Map result = new HashMap<>(translatable.size()); + for (Map.Entry entry : translatable.entrySet()) { + result.put(translate(entry.getKey()), translate(entry.getValue())); + } + return result; + } + + default Multimap translate(Multimap translatable) { + Multimap result = HashMultimap.create(translatable.size(), 1); + for (Map.Entry> entry : translatable.asMap().entrySet()) { + result.putAll(translate(entry.getKey()), translate(entry.getValue())); + } + return result; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/VoidTranslator.java b/enigma/src/main/java/cuchaz/enigma/translation/VoidTranslator.java new file mode 100644 index 00000000..c010833b --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/VoidTranslator.java @@ -0,0 +1,10 @@ +package cuchaz.enigma.translation; + +public enum VoidTranslator implements Translator { + INSTANCE; + + @Override + public T translate(T translatable) { + return translatable; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java new file mode 100644 index 00000000..5b79b794 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java @@ -0,0 +1,25 @@ +package cuchaz.enigma.translation.mapping; + +import cuchaz.enigma.translation.representation.AccessFlags; + +public enum AccessModifier { + UNCHANGED, PUBLIC, PROTECTED, PRIVATE; + + public String getFormattedName() { + return "ACC:" + super.toString(); + } + + public AccessFlags transform(AccessFlags access) { + switch (this) { + case PUBLIC: + return access.setPublic(); + case PROTECTED: + return access.setProtected(); + case PRIVATE: + return access.setPrivate(); + case UNCHANGED: + default: + return access; + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java new file mode 100644 index 00000000..e1a32533 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java @@ -0,0 +1,24 @@ +package cuchaz.enigma.translation.mapping; + +import cuchaz.enigma.translation.representation.entry.Entry; + +import javax.annotation.Nullable; +import java.util.stream.Stream; + +public interface EntryMap { + void insert(Entry entry, T value); + + @Nullable + T remove(Entry entry); + + @Nullable + T get(Entry entry); + + default boolean contains(Entry entry) { + return get(entry) != null; + } + + Stream> getAllEntries(); + + boolean isEmpty(); +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java new file mode 100644 index 00000000..c607817c --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java @@ -0,0 +1,75 @@ +package cuchaz.enigma.translation.mapping; + +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 EntryMapping(@Nonnull String targetName) { + this(targetName, AccessModifier.UNCHANGED); + } + + public EntryMapping(@Nonnull String targetName, @Nullable String javadoc) { + this(targetName, AccessModifier.UNCHANGED, javadoc); + } + + public EntryMapping(@Nonnull 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); + } + + public EntryMapping withModifier(AccessModifier newModifier) { + return new EntryMapping(targetName, newModifier, javadoc); + } + + 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 new file mode 100644 index 00000000..1dd7eacc --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java @@ -0,0 +1,104 @@ +package cuchaz.enigma.translation.mapping; + +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.MappingTranslator; +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.mapping.tree.HashEntryTree; +import cuchaz.enigma.translation.representation.entry.Entry; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.stream.Stream; + +public class EntryRemapper { + private final DeltaTrackingTree obfToDeobf; + + private final EntryResolver obfResolver; + private final Translator deobfuscator; + + private final MappingValidator validator; + + private EntryRemapper(JarIndex jarIndex, EntryTree obfToDeobf) { + this.obfToDeobf = new DeltaTrackingTree<>(obfToDeobf); + + this.obfResolver = jarIndex.getEntryResolver(); + + this.deobfuscator = new MappingTranslator(obfToDeobf, obfResolver); + + this.validator = new MappingValidator(obfToDeobf, deobfuscator, jarIndex); + } + + public static EntryRemapper mapped(JarIndex index, EntryTree obfToDeobf) { + return new EntryRemapper(index, obfToDeobf); + } + + public static EntryRemapper empty(JarIndex index) { + return new EntryRemapper(index, new HashEntryTree<>()); + } + + public > void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping) { + mapFromObf(obfuscatedEntry, deobfMapping, true); + } + + public > void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping, boolean renaming) { + Collection resolvedEntries = obfResolver.resolveEntry(obfuscatedEntry, renaming ? ResolutionStrategy.RESOLVE_ROOT : ResolutionStrategy.RESOLVE_CLOSEST); + + if (renaming && deobfMapping != null) { + for (E resolvedEntry : resolvedEntries) { + validator.validateRename(resolvedEntry, deobfMapping.getTargetName()); + } + } + + for (E resolvedEntry : resolvedEntries) { + obfToDeobf.insert(resolvedEntry, deobfMapping); + } + } + + public void removeByObf(Entry obfuscatedEntry) { + mapFromObf(obfuscatedEntry, null); + } + + @Nullable + public EntryMapping getDeobfMapping(Entry entry) { + return obfToDeobf.get(entry); + } + + public boolean hasDeobfMapping(Entry obfEntry) { + return obfToDeobf.contains(obfEntry); + } + + public T deobfuscate(T translatable) { + return deobfuscator.translate(translatable); + } + + public Translator getDeobfuscator() { + return deobfuscator; + } + + public Stream> getObfEntries() { + return obfToDeobf.getAllEntries(); + } + + public Collection> getObfChildren(Entry obfuscatedEntry) { + return obfToDeobf.getChildren(obfuscatedEntry); + } + + public DeltaTrackingTree getObfToDeobf() { + return obfToDeobf; + } + + public MappingDelta takeMappingDelta() { + return obfToDeobf.takeDelta(); + } + + public boolean isDirty() { + return obfToDeobf.isDirty(); + } + + public EntryResolver getObfResolver() { + return obfResolver; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java new file mode 100644 index 00000000..521f72d0 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java @@ -0,0 +1,41 @@ +package cuchaz.enigma.translation.mapping; + +import com.google.common.collect.Streams; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import java.util.Collection; +import java.util.Set; +import java.util.stream.Collectors; + +public interface EntryResolver { + > Collection resolveEntry(E entry, ResolutionStrategy strategy); + + default > E resolveFirstEntry(E entry, ResolutionStrategy strategy) { + return resolveEntry(entry, strategy).stream().findFirst().orElse(entry); + } + + default , C extends Entry> Collection> resolveReference(EntryReference reference, ResolutionStrategy strategy) { + Collection entry = resolveEntry(reference.entry, strategy); + if (reference.context != null) { + Collection context = resolveEntry(reference.context, strategy); + return Streams.zip(entry.stream(), context.stream(), (e, c) -> new EntryReference<>(e, c, reference)) + .collect(Collectors.toList()); + } else { + return entry.stream() + .map(e -> new EntryReference<>(e, null, reference)) + .collect(Collectors.toList()); + } + } + + default , C extends Entry> EntryReference resolveFirstReference(EntryReference reference, ResolutionStrategy strategy) { + E entry = resolveFirstEntry(reference.entry, strategy); + C context = resolveFirstEntry(reference.context, strategy); + return new EntryReference<>(entry, context, reference); + } + + Set> resolveEquivalentEntries(Entry entry); + + Set resolveEquivalentMethods(MethodEntry methodEntry); +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/IllegalNameException.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/IllegalNameException.java new file mode 100644 index 00000000..a7f83cd7 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/IllegalNameException.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.translation.mapping; + +public class IllegalNameException extends RuntimeException { + + private String name; + private String reason; + + public IllegalNameException(String name, String reason) { + this.name = name; + this.reason = reason; + } + + public String getReason() { + return this.reason; + } + + @Override + public String getMessage() { + StringBuilder buf = new StringBuilder(); + buf.append("Illegal name: "); + buf.append(this.name); + if (this.reason != null) { + buf.append(" because "); + buf.append(this.reason); + } + return buf.toString(); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java new file mode 100644 index 00000000..78231ddd --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java @@ -0,0 +1,227 @@ +package cuchaz.enigma.translation.mapping; + +import com.google.common.collect.Sets; +import cuchaz.enigma.analysis.IndexTreeBuilder; +import cuchaz.enigma.analysis.MethodImplementationsTreeNode; +import cuchaz.enigma.analysis.MethodInheritanceTreeNode; +import cuchaz.enigma.analysis.index.BridgeMethodIndex; +import cuchaz.enigma.analysis.index.EntryIndex; +import cuchaz.enigma.analysis.index.InheritanceIndex; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.VoidTranslator; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.stream.Collectors; + +public class IndexEntryResolver implements EntryResolver { + private final EntryIndex entryIndex; + private final InheritanceIndex inheritanceIndex; + private final BridgeMethodIndex bridgeMethodIndex; + + private final IndexTreeBuilder treeBuilder; + + public IndexEntryResolver(JarIndex index) { + this.entryIndex = index.getEntryIndex(); + this.inheritanceIndex = index.getInheritanceIndex(); + this.bridgeMethodIndex = index.getBridgeMethodIndex(); + + this.treeBuilder = new IndexTreeBuilder(index); + } + + @Override + @SuppressWarnings("unchecked") + public > Collection resolveEntry(E entry, ResolutionStrategy strategy) { + if (entry == null) { + return Collections.emptySet(); + } + + Entry classChild = getClassChild(entry); + if (classChild != null && !(classChild instanceof ClassEntry)) { + AccessFlags access = entryIndex.getEntryAccess(classChild); + + // If we're looking for the closest and this entry exists, we're done looking + if (strategy == ResolutionStrategy.RESOLVE_CLOSEST && access != null) { + return Collections.singleton(entry); + } + + if (access == null || !access.isPrivate()) { + Collection> resolvedChildren = resolveChildEntry(classChild, strategy); + if (!resolvedChildren.isEmpty()) { + return resolvedChildren.stream() + .map(resolvedChild -> (E) entry.replaceAncestor(classChild, resolvedChild)) + .collect(Collectors.toList()); + } + } + } + + return Collections.singleton(entry); + } + + @Nullable + private Entry getClassChild(Entry entry) { + if (entry instanceof ClassEntry) { + return null; + } + + // get the entry in the hierarchy that is the child of a class + List> ancestry = entry.getAncestry(); + for (int i = ancestry.size() - 1; i > 0; i--) { + Entry child = ancestry.get(i); + Entry cast = child.castParent(ClassEntry.class); + if (cast != null && !(cast instanceof ClassEntry)) { + // we found the entry which is a child of a class, we are now able to resolve the owner of this entry + return cast; + } + } + + return null; + } + + private Set> resolveChildEntry(Entry entry, ResolutionStrategy strategy) { + ClassEntry ownerClass = entry.getParent(); + + if (entry instanceof MethodEntry) { + MethodEntry bridgeMethod = bridgeMethodIndex.getBridgeFromSpecialized((MethodEntry) entry); + if (bridgeMethod != null && ownerClass.equals(bridgeMethod.getParent())) { + Set> resolvedBridge = resolveChildEntry(bridgeMethod, strategy); + if (!resolvedBridge.isEmpty()) { + return resolvedBridge; + } else { + return Collections.singleton(bridgeMethod); + } + } + } + + Set> resolvedEntries = new HashSet<>(); + + for (ClassEntry parentClass : inheritanceIndex.getParents(ownerClass)) { + Entry parentEntry = entry.withParent(parentClass); + + if (strategy == ResolutionStrategy.RESOLVE_ROOT) { + resolvedEntries.addAll(resolveRoot(parentEntry, strategy)); + } else { + resolvedEntries.addAll(resolveClosest(parentEntry, strategy)); + } + } + + return resolvedEntries; + } + + private Collection> resolveRoot(Entry entry, ResolutionStrategy strategy) { + // When resolving root, we want to first look for the lowest entry before returning ourselves + Set> parentResolution = resolveChildEntry(entry, strategy); + + if (parentResolution.isEmpty()) { + AccessFlags parentAccess = entryIndex.getEntryAccess(entry); + if (parentAccess != null && !parentAccess.isPrivate()) { + return Collections.singleton(entry); + } + } + + return parentResolution; + } + + private Collection> resolveClosest(Entry entry, ResolutionStrategy strategy) { + // When resolving closest, we want to first check if we exist before looking further down + AccessFlags parentAccess = entryIndex.getEntryAccess(entry); + if (parentAccess != null && !parentAccess.isPrivate()) { + return Collections.singleton(entry); + } else { + return resolveChildEntry(entry, strategy); + } + } + + @Override + public Set> resolveEquivalentEntries(Entry entry) { + MethodEntry relevantMethod = entry.findAncestor(MethodEntry.class); + if (relevantMethod == null || !entryIndex.hasMethod(relevantMethod)) { + return Collections.singleton(entry); + } + + Set equivalentMethods = resolveEquivalentMethods(relevantMethod); + Set> equivalentEntries = new HashSet<>(equivalentMethods.size()); + + for (MethodEntry equivalentMethod : equivalentMethods) { + Entry equivalentEntry = entry.replaceAncestor(relevantMethod, equivalentMethod); + equivalentEntries.add(equivalentEntry); + } + + return equivalentEntries; + } + + @Override + public Set resolveEquivalentMethods(MethodEntry methodEntry) { + AccessFlags access = entryIndex.getMethodAccess(methodEntry); + if (access == null) { + throw new IllegalArgumentException("Could not find method " + methodEntry); + } + + if (!canInherit(methodEntry, access)) { + return Collections.singleton(methodEntry); + } + + Set methodEntries = Sets.newHashSet(); + resolveEquivalentMethods(methodEntries, treeBuilder.buildMethodInheritance(VoidTranslator.INSTANCE, methodEntry)); + return methodEntries; + } + + private void resolveEquivalentMethods(Set methodEntries, MethodInheritanceTreeNode node) { + MethodEntry methodEntry = node.getMethodEntry(); + if (methodEntries.contains(methodEntry)) { + return; + } + + AccessFlags flags = entryIndex.getMethodAccess(methodEntry); + if (flags != null && canInherit(methodEntry, flags)) { + // collect the entry + methodEntries.add(methodEntry); + } + + // look at bridge methods! + MethodEntry bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(methodEntry); + while (bridgedMethod != null) { + methodEntries.addAll(resolveEquivalentMethods(bridgedMethod)); + bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(bridgedMethod); + } + + // look at interface methods too + for (MethodImplementationsTreeNode implementationsNode : treeBuilder.buildMethodImplementations(VoidTranslator.INSTANCE, methodEntry)) { + resolveEquivalentMethods(methodEntries, implementationsNode); + } + + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + resolveEquivalentMethods(methodEntries, (MethodInheritanceTreeNode) node.getChildAt(i)); + } + } + + private void resolveEquivalentMethods(Set methodEntries, MethodImplementationsTreeNode node) { + MethodEntry methodEntry = node.getMethodEntry(); + AccessFlags flags = entryIndex.getMethodAccess(methodEntry); + if (flags != null && !flags.isPrivate() && !flags.isStatic()) { + // collect the entry + methodEntries.add(methodEntry); + } + + // look at bridge methods! + MethodEntry bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(methodEntry); + while (bridgedMethod != null) { + methodEntries.addAll(resolveEquivalentMethods(bridgedMethod)); + bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(bridgedMethod); + } + + // recurse + for (int i = 0; i < node.getChildCount(); i++) { + resolveEquivalentMethods(methodEntries, (MethodImplementationsTreeNode) node.getChildAt(i)); + } + } + + private boolean canInherit(MethodEntry entry, AccessFlags access) { + return !entry.isConstructor() && !access.isPrivate() && !access.isStatic() && !access.isFinal(); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java new file mode 100644 index 00000000..1407bb60 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java @@ -0,0 +1,54 @@ +package cuchaz.enigma.translation.mapping; + +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +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; + +import java.util.stream.Stream; + +public class MappingDelta implements Translatable { + public static final Object PLACEHOLDER = new Object(); + + private final EntryTree baseMappings; + + private final EntryTree changes; + + public MappingDelta(EntryTree baseMappings, EntryTree changes) { + this.baseMappings = baseMappings; + this.changes = changes; + } + + public MappingDelta(EntryTree baseMappings) { + this(baseMappings, new HashEntryTree<>()); + } + + public static MappingDelta added(EntryTree mappings) { + EntryTree changes = new HashEntryTree<>(); + mappings.getAllEntries().forEach(entry -> changes.insert(entry, PLACEHOLDER)); + + return new MappingDelta<>(new HashEntryTree<>(), changes); + } + + public EntryTree getBaseMappings() { + return baseMappings; + } + + public EntryTree getChanges() { + return changes; + } + + public Stream> getChangedRoots() { + return changes.getRootNodes().map(EntryTreeNode::getEntry); + } + + @Override + public MappingDelta translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + return new MappingDelta<>( + translator.translate(baseMappings), + translator.translate(changes) + ); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingOperations.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingOperations.java new file mode 100644 index 00000000..2c037484 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingOperations.java @@ -0,0 +1,71 @@ +package cuchaz.enigma.translation.mapping; + +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.MappingTranslator; +import cuchaz.enigma.translation.Translator; +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.MethodEntry; + +import java.util.HashSet; +import java.util.Set; + +public class MappingOperations { + public static EntryTree invert(EntryTree mappings) { + Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE); + EntryTree result = new HashEntryTree<>(); + + for (EntryTreeNode node : mappings) { + Entry leftEntry = node.getEntry(); + EntryMapping leftMapping = node.getValue(); + + if (!(leftEntry instanceof ClassEntry || leftEntry instanceof MethodEntry || leftEntry instanceof FieldEntry)) { + result.insert(translator.translate(leftEntry), leftMapping); + continue; + } + + Entry rightEntry = translator.translate(leftEntry); + + result.insert(rightEntry, leftMapping == null ? null : leftMapping.withName(leftEntry.getName())); + } + + return result; + } + + public static EntryTree compose(EntryTree left, EntryTree right, boolean keepLeftOnly, boolean keepRightOnly) { + Translator leftTranslator = new MappingTranslator(left, VoidEntryResolver.INSTANCE); + EntryTree result = new HashEntryTree<>(); + Set> addedMappings = new HashSet<>(); + + for (EntryTreeNode node : left) { + Entry leftEntry = node.getEntry(); + EntryMapping leftMapping = node.getValue(); + + Entry rightEntry = leftTranslator.translate(leftEntry); + + EntryMapping rightMapping = right.get(rightEntry); + if (rightMapping != null) { + result.insert(leftEntry, rightMapping); + addedMappings.add(rightEntry); + } else if (keepLeftOnly) { + result.insert(leftEntry, leftMapping); + } + } + + if (keepRightOnly) { + Translator leftInverseTranslator = new MappingTranslator(invert(left), VoidEntryResolver.INSTANCE); + for (EntryTreeNode node : right) { + Entry rightEntry = node.getEntry(); + EntryMapping rightMapping = node.getValue(); + + if (!addedMappings.contains(rightEntry)) { + result.insert(leftInverseTranslator.translate(rightEntry), rightMapping); + } + } + } + return result; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java new file mode 100644 index 00000000..5d39e3d2 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java @@ -0,0 +1,32 @@ +package cuchaz.enigma.translation.mapping; + +import cuchaz.enigma.translation.representation.entry.Entry; + +import javax.annotation.Nullable; + +public class MappingPair, M> { + private final E entry; + private M mapping; + + public MappingPair(E entry, @Nullable M mapping) { + this.entry = entry; + this.mapping = mapping; + } + + public MappingPair(E entry) { + this(entry, null); + } + + public E getEntry() { + return entry; + } + + @Nullable + public M getMapping() { + return mapping; + } + + public void setMapping(M mapping) { + this.mapping = mapping; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java new file mode 100644 index 00000000..ae615da4 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java @@ -0,0 +1,75 @@ +package cuchaz.enigma.translation.mapping; + +import cuchaz.enigma.analysis.index.InheritanceIndex; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.Entry; + +import java.util.Collection; +import java.util.HashSet; +import java.util.stream.Collectors; + +public class MappingValidator { + private final EntryTree obfToDeobf; + private final Translator deobfuscator; + private final JarIndex index; + + public MappingValidator(EntryTree obfToDeobf, Translator deobfuscator, JarIndex index) { + this.obfToDeobf = obfToDeobf; + this.deobfuscator = deobfuscator; + this.index = index; + } + + public void validateRename(Entry entry, String name) throws IllegalNameException { + Collection> equivalentEntries = index.getEntryResolver().resolveEquivalentEntries(entry); + for (Entry equivalentEntry : equivalentEntries) { + equivalentEntry.validateName(name); + validateUnique(equivalentEntry, name); + } + } + + private void validateUnique(Entry entry, String name) { + ClassEntry containingClass = entry.getContainingClass(); + Collection relatedClasses = getRelatedClasses(containingClass); + + for (ClassEntry relatedClass : relatedClasses) { + Entry relatedEntry = entry.replaceAncestor(containingClass, relatedClass); + Entry translatedEntry = deobfuscator.translate(relatedEntry); + + Collection> translatedSiblings = obfToDeobf.getSiblings(relatedEntry).stream() + .map(deobfuscator::translate) + .collect(Collectors.toList()); + + if (!isUnique(translatedEntry, translatedSiblings, name)) { + Entry parent = translatedEntry.getParent(); + if (parent != null) { + throw new IllegalNameException(name, "Name is not unique in " + parent + "!"); + } else { + throw new IllegalNameException(name, "Name is not unique!"); + } + } + } + } + + private Collection getRelatedClasses(ClassEntry classEntry) { + InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); + + Collection relatedClasses = new HashSet<>(); + relatedClasses.add(classEntry); + relatedClasses.addAll(inheritanceIndex.getChildren(classEntry)); + relatedClasses.addAll(inheritanceIndex.getAncestors(classEntry)); + + return relatedClasses; + } + + private boolean isUnique(Entry entry, Collection> siblings, String name) { + for (Entry sibling : siblings) { + if (entry.canConflictWith(sibling) && sibling.getName().equals(name)) { + return false; + } + } + return true; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java new file mode 100644 index 00000000..5d9794fb --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.translation.mapping; + +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.analysis.index.JarIndex; +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 java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public class MappingsChecker { + private final JarIndex index; + private final EntryTree mappings; + + public MappingsChecker(JarIndex index, EntryTree mappings) { + this.index = index; + this.mappings = mappings; + } + + public Dropped dropBrokenMappings(ProgressListener progress) { + Dropped dropped = new Dropped(); + + Collection> obfEntries = mappings.getAllEntries() + .filter(e -> e instanceof ClassEntry || e instanceof MethodEntry || e instanceof FieldEntry || e instanceof LocalVariableEntry) + .collect(Collectors.toList()); + + progress.init(obfEntries.size(), "Checking for dropped mappings"); + + int steps = 0; + for (Entry entry : obfEntries) { + progress.step(steps++, entry.toString()); + tryDropEntry(dropped, entry); + } + + dropped.apply(mappings); + + return dropped; + } + + private void tryDropEntry(Dropped dropped, Entry entry) { + if (shouldDropEntry(entry)) { + EntryMapping mapping = mappings.get(entry); + if (mapping != null) { + dropped.drop(entry, mapping); + } + } + } + + private boolean shouldDropEntry(Entry entry) { + if (!index.getEntryIndex().hasEntry(entry)) { + return true; + } + Collection> resolvedEntries = index.getEntryResolver().resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT); + return !resolvedEntries.contains(entry); + } + + public static class Dropped { + private final Map, String> droppedMappings = new HashMap<>(); + + public void drop(Entry entry, EntryMapping mapping) { + droppedMappings.put(entry, mapping.getTargetName()); + } + + void apply(EntryTree mappings) { + for (Entry entry : droppedMappings.keySet()) { + EntryTreeNode node = mappings.findNode(entry); + if (node == null) { + continue; + } + + for (Entry childEntry : node.getChildrenRecursively()) { + mappings.remove(childEntry); + } + } + } + + public Map, String> getDroppedMappings() { + return droppedMappings; + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java new file mode 100644 index 00000000..74ba633d --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.translation.mapping; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +public class NameValidator { + private static final Pattern IDENTIFIER_PATTERN; + private static final Pattern CLASS_PATTERN; + private static final List ILLEGAL_IDENTIFIERS = Arrays.asList( + "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", + "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", + "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", + "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally", + "long", "strictfp", "volatile", "const", "float", "native", "super", "while", "_" + ); + + static { + String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*"; + IDENTIFIER_PATTERN = Pattern.compile(identifierRegex); + CLASS_PATTERN = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex)); + } + + public static void validateClassName(String name) { + if (!CLASS_PATTERN.matcher(name).matches() || ILLEGAL_IDENTIFIERS.contains(name)) { + throw new IllegalNameException(name, "This doesn't look like a legal class name"); + } + } + + public static void validateIdentifier(String name) { + if (!IDENTIFIER_PATTERN.matcher(name).matches() || ILLEGAL_IDENTIFIERS.contains(name)) { + throw new IllegalNameException(name, "This doesn't look like a legal identifier"); + } + } + + public static boolean isReserved(String name) { + return ILLEGAL_IDENTIFIERS.contains(name); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java new file mode 100644 index 00000000..1c28e028 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java @@ -0,0 +1,6 @@ +package cuchaz.enigma.translation.mapping; + +public enum ResolutionStrategy { + RESOLVE_ROOT, + RESOLVE_CLOSEST +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java new file mode 100644 index 00000000..2eab55fd --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java @@ -0,0 +1,27 @@ +package cuchaz.enigma.translation.mapping; + +import cuchaz.enigma.translation.representation.entry.Entry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +public enum VoidEntryResolver implements EntryResolver { + INSTANCE; + + @Override + public > Collection resolveEntry(E entry, ResolutionStrategy strategy) { + return Collections.singleton(entry); + } + + @Override + public Set> resolveEquivalentEntries(Entry entry) { + return Collections.singleton(entry); + } + + @Override + public Set resolveEquivalentMethods(MethodEntry methodEntry) { + return Collections.singleton(methodEntry); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/LfPrintWriter.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/LfPrintWriter.java new file mode 100644 index 00000000..441949c6 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/LfPrintWriter.java @@ -0,0 +1,16 @@ +package cuchaz.enigma.translation.mapping.serde; + +import java.io.PrintWriter; +import java.io.Writer; + +public class LfPrintWriter extends PrintWriter { + public LfPrintWriter(Writer out) { + super(out); + } + + @Override + public void println() { + // https://stackoverflow.com/a/14749004 + write('\n'); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFileNameFormat.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFileNameFormat.java new file mode 100644 index 00000000..9bc013c4 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFileNameFormat.java @@ -0,0 +1,10 @@ +package cuchaz.enigma.translation.mapping.serde; + +import com.google.gson.annotations.SerializedName; + +public enum MappingFileNameFormat { + @SerializedName("by_obf") + BY_OBF, + @SerializedName("by_deobf") + BY_DEOBF +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java new file mode 100644 index 00000000..ca275ebd --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java @@ -0,0 +1,65 @@ +package cuchaz.enigma.translation.mapping.serde; + +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.MappingDelta; +import cuchaz.enigma.translation.mapping.serde.enigma.EnigmaMappingsReader; +import cuchaz.enigma.translation.mapping.serde.enigma.EnigmaMappingsWriter; +import cuchaz.enigma.translation.mapping.serde.proguard.ProguardMappingsReader; +import cuchaz.enigma.translation.mapping.serde.srg.SrgMappingsWriter; +import cuchaz.enigma.translation.mapping.serde.tiny.TinyMappingsReader; +import cuchaz.enigma.translation.mapping.serde.tiny.TinyMappingsWriter; +import cuchaz.enigma.translation.mapping.serde.tinyv2.TinyV2Reader; +import cuchaz.enigma.translation.mapping.serde.tinyv2.TinyV2Writer; +import cuchaz.enigma.translation.mapping.tree.EntryTree; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.nio.file.Path; + +public enum MappingFormat { + ENIGMA_FILE(EnigmaMappingsWriter.FILE, EnigmaMappingsReader.FILE), + ENIGMA_DIRECTORY(EnigmaMappingsWriter.DIRECTORY, EnigmaMappingsReader.DIRECTORY), + ENIGMA_ZIP(EnigmaMappingsWriter.ZIP, EnigmaMappingsReader.ZIP), + TINY_V2(new TinyV2Writer("intermediary", "named"), new TinyV2Reader()), + TINY_FILE(TinyMappingsWriter.INSTANCE, TinyMappingsReader.INSTANCE), + SRG_FILE(SrgMappingsWriter.INSTANCE, null), + PROGUARD(null, ProguardMappingsReader.INSTANCE); + + + private final MappingsWriter writer; + private final MappingsReader reader; + + MappingFormat(MappingsWriter writer, MappingsReader reader) { + this.writer = writer; + this.reader = reader; + } + + public void write(EntryTree mappings, Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) { + write(mappings, MappingDelta.added(mappings), path, progressListener, saveParameters); + } + + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) { + if (writer == null) { + throw new IllegalStateException(name() + " does not support writing"); + } + writer.write(mappings, delta, path, progressListener, saveParameters); + } + + public EntryTree read(Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) throws IOException, MappingParseException { + if (reader == null) { + throw new IllegalStateException(name() + " does not support reading"); + } + return reader.read(path, progressListener, saveParameters); + } + + @Nullable + public MappingsWriter getWriter() { + return writer; + } + + @Nullable + public MappingsReader getReader() { + return reader; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java new file mode 100644 index 00000000..7c8f6cc6 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java @@ -0,0 +1,51 @@ +package cuchaz.enigma.translation.mapping.serde; + +public final class MappingHelper { + private static final String TO_ESCAPE = "\\\n\r\0\t"; + private static final String ESCAPED = "\\nr0t"; + + public static String escape(String raw) { + StringBuilder builder = new StringBuilder(raw.length() + 1); + for (int i = 0; i < raw.length(); i++) { + final char c = raw.charAt(i); + final int r = TO_ESCAPE.indexOf(c); + if (r < 0) { + builder.append(c); + } else { + builder.append('\\').append(ESCAPED.charAt(r)); + } + } + return builder.toString(); + } + + public static String unescape(String str) { + int pos = str.indexOf('\\'); + if (pos < 0) return str; + + StringBuilder ret = new StringBuilder(str.length() - 1); + int start = 0; + + do { + ret.append(str, start, pos); + pos++; + int type; + + if (pos >= str.length()) { + throw new RuntimeException("incomplete escape sequence at the end"); + } else if ((type = ESCAPED.indexOf(str.charAt(pos))) < 0) { + throw new RuntimeException("invalid escape character: \\" + str.charAt(pos)); + } else { + ret.append(TO_ESCAPE.charAt(type)); + } + + start = pos + 1; + } while ((pos = str.indexOf('\\', start)) >= 0); + + ret.append(str, start, str.length()); + + return ret.toString(); + } + + private MappingHelper() { + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingParseException.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingParseException.java new file mode 100644 index 00000000..9d04b976 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingParseException.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.translation.mapping.serde; + +import java.io.File; +import java.util.function.Supplier; + +public class MappingParseException extends Exception { + + private int line; + private String message; + private String filePath; + + public MappingParseException(File file, int line, String message) { + this.line = line; + this.message = message; + filePath = file.getAbsolutePath(); + } + + public MappingParseException(Supplier filenameProvider, int line, String message) { + this.line = line; + this.message = message; + filePath = filenameProvider.get(); + } + + @Override + public String getMessage() { + return "Line " + line + ": " + message + " in file " + filePath; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingSaveParameters.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingSaveParameters.java new file mode 100644 index 00000000..e78fd956 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingSaveParameters.java @@ -0,0 +1,16 @@ +package cuchaz.enigma.translation.mapping.serde; + +import com.google.gson.annotations.SerializedName; + +public class MappingSaveParameters { + @SerializedName("file_name_format") + private final MappingFileNameFormat fileNameFormat; + + public MappingSaveParameters(MappingFileNameFormat fileNameFormat) { + this.fileNameFormat = fileNameFormat; + } + + public MappingFileNameFormat getFileNameFormat() { + return fileNameFormat; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java new file mode 100644 index 00000000..2f01375a --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java @@ -0,0 +1,12 @@ +package cuchaz.enigma.translation.mapping.serde; + +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.tree.EntryTree; + +import java.io.IOException; +import java.nio.file.Path; + +public interface MappingsReader { + EntryTree read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws MappingParseException, IOException; +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java new file mode 100644 index 00000000..68a8dbb2 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java @@ -0,0 +1,16 @@ +package cuchaz.enigma.translation.mapping.serde; + +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.MappingDelta; +import cuchaz.enigma.translation.mapping.tree.EntryTree; + +import java.nio.file.Path; + +public interface MappingsWriter { + void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters); + + default void write(EntryTree mappings, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + write(mappings, MappingDelta.added(mappings), path, progress, saveParameters); + } +} 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 new file mode 100644 index 00000000..79587a0b --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java @@ -0,0 +1,30 @@ +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; + +public final class RawEntryMapping { + private final String targetName; + private final AccessModifier access; + private List javadocs = new ArrayList<>(); + + public RawEntryMapping(String targetName) { + this(targetName, null); + } + + public RawEntryMapping(String targetName, AccessModifier access) { + this.access = access; + this.targetName = targetName; + } + + public void addJavadocLine(String line) { + javadocs.add(line); + } + + public EntryMapping bake() { + return new EntryMapping(targetName, access, javadocs.isEmpty() ? null : String.join("\n", javadocs)); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaFormat.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaFormat.java new file mode 100644 index 00000000..210d328e --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaFormat.java @@ -0,0 +1,9 @@ +package cuchaz.enigma.translation.mapping.serde.enigma; + +public class EnigmaFormat { + public static final String COMMENT = "COMMENT"; + public static final String CLASS = "CLASS"; + public static final String FIELD = "FIELD"; + public static final String METHOD = "METHOD"; + public static final String PARAMETER = "ARG"; +} 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 new file mode 100644 index 00000000..30de85f0 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsReader.java @@ -0,0 +1,322 @@ +package cuchaz.enigma.translation.mapping.serde.enigma; + +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.tree.EntryTree; +import cuchaz.enigma.translation.mapping.tree.HashEntryTree; +import cuchaz.enigma.translation.representation.MethodDescriptor; +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; +import java.util.stream.Collectors; + +public enum EnigmaMappingsReader implements MappingsReader { + FILE { + @Override + public EntryTree read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException { + progress.init(1, I18n.translate("progress.mappings.enigma_file.loading")); + + EntryTree mappings = new HashEntryTree<>(); + readFile(path, mappings); + + progress.step(1, I18n.translate("progress.mappings.enigma_file.done")); + + return mappings; + } + }, + DIRECTORY { + @Override + public EntryTree read(Path root, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException { + EntryTree mappings = new HashEntryTree<>(); + + List files = Files.walk(root) + .filter(f -> !Files.isDirectory(f)) + .filter(f -> f.toString().endsWith(".mapping")) + .collect(Collectors.toList()); + + progress.init(files.size(), I18n.translate("progress.mappings.enigma_directory.loading")); + int step = 0; + + for (Path file : files) { + progress.step(step++, root.relativize(file).toString()); + if (Files.isHidden(file)) { + continue; + } + readFile(file, mappings); + } + + return mappings; + } + }, + ZIP { + @Override + public EntryTree read(Path zip, ProgressListener progress, MappingSaveParameters saveParameters) throws MappingParseException, IOException { + try (FileSystem fs = FileSystems.newFileSystem(zip, (ClassLoader) null)) { + return DIRECTORY.read(fs.getPath("/"), progress, saveParameters); + } + } + }; + + protected void readFile(Path path, EntryTree mappings) throws IOException, MappingParseException { + List lines = Files.readAllLines(path, Charsets.UTF_8); + Deque> mappingStack = new ArrayDeque<>(); + + for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) { + String line = lines.get(lineNumber); + int indentation = countIndentation(line); + + line = formatLine(line); + if (line == null) { + continue; + } + + cleanMappingStack(indentation, mappingStack, mappings); + + try { + MappingPair pair = parseLine(mappingStack.peek(), line); + if (pair != null) { + mappingStack.push(pair); + if (pair.getMapping() != null) { + + } + } + } catch (Throwable t) { + t.printStackTrace(); + throw new MappingParseException(path::toString, lineNumber, t.toString()); + } + } + + // Clean up rest + cleanMappingStack(0, mappingStack, mappings); + } + + private void cleanMappingStack(int indentation, Deque> mappingStack, EntryTree mappings) { + while (indentation < mappingStack.size()) { + MappingPair pair = mappingStack.pop(); + if (pair.getMapping() != null) { + mappings.insert(pair.getEntry(), pair.getMapping().bake()); + } + } + } + + @Nullable + private String formatLine(String line) { + line = stripComment(line); + line = line.trim(); + + if (line.isEmpty()) { + return null; + } + + return line; + } + + private String stripComment(String line) { + //Dont support comments on javadoc lines + if (line.trim().startsWith(EnigmaFormat.COMMENT)) { + return line; + } + + int commentPos = line.indexOf('#'); + if (commentPos >= 0) { + return line.substring(0, commentPos); + } + return line; + } + + private int countIndentation(String line) { + int indent = 0; + for (int i = 0; i < line.length(); i++) { + if (line.charAt(i) != '\t') { + break; + } + indent++; + } + return indent; + } + + private MappingPair parseLine(@Nullable MappingPair parent, String line) { + String[] tokens = line.trim().split("\\s"); + String keyToken = tokens[0].toUpperCase(Locale.ROOT); + Entry parentEntry = parent == null ? null : parent.getEntry(); + + switch (keyToken) { + case EnigmaFormat.CLASS: + return parseClass(parentEntry, tokens); + case EnigmaFormat.FIELD: + return parseField(parentEntry, tokens); + case EnigmaFormat.METHOD: + return parseMethod(parentEntry, tokens); + case EnigmaFormat.PARAMETER: + return parseArgument(parentEntry, tokens); + case EnigmaFormat.COMMENT: + readJavadoc(parent, tokens); + return null; + default: + throw new RuntimeException("Unknown token '" + keyToken + "'"); + } + } + + private 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)) : ""; + if (parent.getMapping() == null) { + parent.setMapping(new RawEntryMapping(parent.getEntry().getName(), AccessModifier.UNCHANGED)); + } + parent.getMapping().addJavadocLine(MappingHelper.unescape(jdLine)); + } + + private MappingPair parseClass(@Nullable Entry parent, String[] tokens) { + String obfuscatedName = ClassEntry.getInnerName(tokens[1]); + ClassEntry obfuscatedEntry; + if (parent instanceof ClassEntry) { + obfuscatedEntry = new ClassEntry((ClassEntry) parent, obfuscatedName); + } else { + obfuscatedEntry = new ClassEntry(obfuscatedName); + } + + String mapping = null; + AccessModifier modifier = AccessModifier.UNCHANGED; + + if (tokens.length == 3) { + AccessModifier parsedModifier = parseModifier(tokens[2]); + if (parsedModifier != null) { + modifier = parsedModifier; + mapping = obfuscatedName; + } else { + mapping = tokens[2]; + } + } else if (tokens.length == 4) { + mapping = tokens[2]; + modifier = parseModifier(tokens[3]); + } + + if (mapping != null) { + return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier)); + } else { + return new MappingPair<>(obfuscatedEntry); + } + } + + private MappingPair parseField(@Nullable Entry parent, String[] tokens) { + if (!(parent instanceof ClassEntry)) { + throw new RuntimeException("Field must be a child of a class!"); + } + + ClassEntry ownerEntry = (ClassEntry) parent; + + String obfuscatedName = tokens[1]; + String mapping = obfuscatedName; + AccessModifier modifier = AccessModifier.UNCHANGED; + TypeDescriptor descriptor; + + if (tokens.length == 3) { + mapping = tokens[1]; + descriptor = new TypeDescriptor(tokens[2]); + } else if (tokens.length == 4) { + AccessModifier parsedModifier = parseModifier(tokens[3]); + if (parsedModifier != null) { + descriptor = new TypeDescriptor(tokens[2]); + modifier = parsedModifier; + } else { + mapping = tokens[2]; + descriptor = new TypeDescriptor(tokens[3]); + } + } else if (tokens.length == 5) { + descriptor = new TypeDescriptor(tokens[3]); + mapping = tokens[2]; + modifier = parseModifier(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); + } + } + + private MappingPair parseMethod(@Nullable Entry parent, String[] tokens) { + if (!(parent instanceof ClassEntry)) { + throw new RuntimeException("Method must be a child of a class!"); + } + + ClassEntry ownerEntry = (ClassEntry) parent; + + String obfuscatedName = tokens[1]; + String mapping = null; + AccessModifier modifier = AccessModifier.UNCHANGED; + MethodDescriptor descriptor; + + if (tokens.length == 3) { + descriptor = new MethodDescriptor(tokens[2]); + } else if (tokens.length == 4) { + AccessModifier parsedModifier = parseModifier(tokens[3]); + if (parsedModifier != null) { + modifier = parsedModifier; + mapping = obfuscatedName; + descriptor = new MethodDescriptor(tokens[2]); + } else { + mapping = tokens[2]; + descriptor = new MethodDescriptor(tokens[3]); + } + } else if (tokens.length == 5) { + mapping = tokens[2]; + modifier = parseModifier(tokens[4]); + descriptor = new MethodDescriptor(tokens[3]); + } else { + throw new RuntimeException("Invalid method declaration"); + } + + MethodEntry obfuscatedEntry = new MethodEntry(ownerEntry, obfuscatedName, descriptor); + if (mapping != null) { + return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier)); + } else { + return new MappingPair<>(obfuscatedEntry); + } + } + + private MappingPair parseArgument(@Nullable Entry parent, String[] tokens) { + if (!(parent instanceof MethodEntry)) { + throw new RuntimeException("Method arg must be a child of a method!"); + } + + MethodEntry ownerEntry = (MethodEntry) parent; + LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerEntry, Integer.parseInt(tokens[1]), "", true, null); + String mapping = tokens[2]; + + return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping)); + } + + @Nullable + private AccessModifier parseModifier(String token) { + if (token.startsWith("ACC:")) { + return AccessModifier.valueOf(token.substring(4)); + } + return null; + } +} 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 new file mode 100644 index 00000000..7570d4b6 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsWriter.java @@ -0,0 +1,318 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.translation.mapping.serde.enigma; + +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.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +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.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.utils.I18n; + +public enum EnigmaMappingsWriter implements MappingsWriter { + FILE { + @Override + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + Collection classes = mappings.getRootNodes() + .filter(entry -> entry instanceof ClassEntry) + .map(entry -> (ClassEntry) entry) + .collect(Collectors.toList()); + + progress.init(classes.size(), I18n.translate("progress.mappings.enigma_file.writing")); + + int steps = 0; + try (PrintWriter writer = new LfPrintWriter(Files.newBufferedWriter(path))) { + for (ClassEntry classEntry : classes) { + progress.step(steps++, classEntry.getFullName()); + writeRoot(writer, mappings, classEntry); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + }, + DIRECTORY { + @Override + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + Collection changedClasses = delta.getChangedRoots() + .filter(entry -> entry instanceof ClassEntry) + .map(entry -> (ClassEntry) entry) + .collect(Collectors.toList()); + + applyDeletions(path, changedClasses, mappings, delta.getBaseMappings(), saveParameters.getFileNameFormat()); + + progress.init(changedClasses.size(), I18n.translate("progress.mappings.enigma_directory.writing")); + + AtomicInteger steps = new AtomicInteger(); + + Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE); + changedClasses.parallelStream().forEach(classEntry -> { + progress.step(steps.getAndIncrement(), classEntry.getFullName()); + + try { + ClassEntry fileEntry = classEntry; + if (saveParameters.getFileNameFormat() == MappingFileNameFormat.BY_DEOBF) { + fileEntry = translator.translate(fileEntry); + } + + Path classPath = resolve(path, fileEntry); + Files.createDirectories(classPath.getParent()); + Files.deleteIfExists(classPath); + + try (PrintWriter writer = new LfPrintWriter(Files.newBufferedWriter(classPath))) { + writeRoot(writer, mappings, classEntry); + } + } catch (Throwable t) { + System.err.println("Failed to write class '" + classEntry.getFullName() + "'"); + t.printStackTrace(); + } + }); + } + + private void applyDeletions(Path root, Collection changedClasses, EntryTree mappings, EntryTree oldMappings, MappingFileNameFormat fileNameFormat) { + Translator oldMappingTranslator = new MappingTranslator(oldMappings, VoidEntryResolver.INSTANCE); + + Stream deletedClassStream = changedClasses.stream() + .filter(e -> !Objects.equals(oldMappings.get(e), mappings.get(e))); + + if (fileNameFormat == MappingFileNameFormat.BY_DEOBF) { + deletedClassStream = deletedClassStream.map(oldMappingTranslator::translate); + } + + Collection deletedClasses = deletedClassStream.collect(Collectors.toList()); + + for (ClassEntry classEntry : deletedClasses) { + try { + Files.deleteIfExists(resolve(root, classEntry)); + } catch (IOException e) { + System.err.println("Failed to delete deleted class '" + classEntry + "'"); + e.printStackTrace(); + } + } + + for (ClassEntry classEntry : deletedClasses) { + String packageName = classEntry.getPackageName(); + if (packageName != null) { + Path packagePath = Paths.get(packageName); + try { + deleteDeadPackages(root, packagePath); + } catch (IOException e) { + System.err.println("Failed to delete dead package '" + packageName + "'"); + e.printStackTrace(); + } + } + } + } + + private void deleteDeadPackages(Path root, Path packagePath) throws IOException { + for (int i = packagePath.getNameCount() - 1; i >= 0; i--) { + Path subPath = packagePath.subpath(0, i + 1); + Path packagePart = root.resolve(subPath); + if (isEmpty(packagePart)) { + Files.deleteIfExists(packagePart); + } + } + } + + private boolean isEmpty(Path path) { + try (DirectoryStream stream = Files.newDirectoryStream(path)) { + return !stream.iterator().hasNext(); + } catch (IOException e) { + return false; + } + } + + private Path resolve(Path root, ClassEntry classEntry) { + return root.resolve(classEntry.getFullName() + ".mapping"); + } + }, + ZIP { + @Override + public void write(EntryTree mappings, MappingDelta delta, Path zip, ProgressListener progress, MappingSaveParameters saveParameters) { + try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:file", null, zip.toUri().getPath(), ""), Collections.singletonMap("create", "true"))) { + DIRECTORY.write(mappings, delta, fs.getPath("/"), progress, saveParameters); + } catch (IOException e) { + e.printStackTrace(); + } catch (URISyntaxException e) { + throw new RuntimeException("Unexpected error creating URI for " + zip, e); + } + } + }; + + protected void writeRoot(PrintWriter writer, EntryTree mappings, ClassEntry classEntry) { + Collection> children = groupChildren(mappings.getChildren(classEntry)); + + EntryMapping classEntryMapping = mappings.get(classEntry); + + writer.println(writeClass(classEntry, classEntryMapping).trim()); + if (classEntryMapping != null && classEntryMapping.getJavadoc() != 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(); + if (jd != null) { + for (String line : jd.split("\\R")) { + writer.println(indent(EnigmaFormat.COMMENT + " " + MappingHelper.escape(line), depth + 1)); + } + } + } + + protected void writeEntry(PrintWriter writer, EntryTree mappings, Entry entry, int depth) { + EntryTreeNode node = mappings.findNode(entry); + if (node == null) { + return; + } + + 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); + writer.println(indent(line, depth)); + } + if (mapping != null && mapping.getJavadoc() != null) { + writeDocs(writer, mapping, depth); + } + + Collection> children = groupChildren(node.getChildren()); + for (Entry child : children) { + writeEntry(writer, mappings, child, depth + 1); + } + } + + private Collection> groupChildren(Collection> children) { + Collection> result = new ArrayList<>(children.size()); + + children.stream().filter(e -> e instanceof FieldEntry) + .map(e -> (FieldEntry) e) + .sorted() + .forEach(result::add); + + children.stream().filter(e -> e instanceof MethodEntry) + .map(e -> (MethodEntry) e) + .sorted() + .forEach(result::add); + + children.stream().filter(e -> e instanceof LocalVariableEntry) + .map(e -> (LocalVariableEntry) e) + .sorted() + .forEach(result::add); + + children.stream().filter(e -> e instanceof ClassEntry) + .map(e -> (ClassEntry) e) + .sorted() + .forEach(result::add); + + return result; + } + + protected String writeClass(ClassEntry entry, 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) { + StringBuilder builder = new StringBuilder(EnigmaFormat.METHOD + " "); + builder.append(entry.getName()).append(' '); + if (mapping != null && !mapping.getTargetName().equals(entry.getName())) { + writeMapping(builder, mapping); + } + + builder.append(entry.getDesc().toString()); + + return builder.toString(); + } + + protected String writeField(FieldEntry entry, EntryMapping mapping) { + StringBuilder builder = new StringBuilder(EnigmaFormat.FIELD + " "); + builder.append(entry.getName()).append(' '); + if (mapping != null && !mapping.getTargetName().equals(entry.getName())) { + 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(); + } + + 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(' '); + } + } + } + + private String indent(String line, int depth) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < depth; i++) { + builder.append("\t"); + } + builder.append(line.trim()); + return builder.toString(); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/proguard/ProguardMappingsReader.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/proguard/ProguardMappingsReader.java new file mode 100644 index 00000000..034fb416 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/proguard/ProguardMappingsReader.java @@ -0,0 +1,135 @@ +package cuchaz.enigma.translation.mapping.serde.proguard; + +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.mapping.MappingOperations; +import cuchaz.enigma.translation.mapping.serde.MappingParseException; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; +import cuchaz.enigma.translation.mapping.serde.MappingsReader; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.mapping.tree.HashEntryTree; +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.FieldEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ProguardMappingsReader implements MappingsReader { + public static final ProguardMappingsReader INSTANCE = new ProguardMappingsReader(); + private static final String NAME = "[a-zA-Z0-9_\\-.$<>]+"; + private static final String TYPE = NAME + "(?:\\[])*"; + private static final String TYPE_LIST = "|(?:(?:" + TYPE + ",)*" + TYPE + ")"; + private static final Pattern CLASS = Pattern.compile("(" + NAME + ") -> (" + NAME + "):"); + private static final Pattern FIELD = Pattern.compile(" {4}(" + TYPE + ") (" + NAME + ") -> (" + NAME + ")"); + private static final Pattern METHOD = Pattern.compile(" {4}(?:[0-9]+:[0-9]+:)?(" + TYPE + ") (" + NAME + ")\\((" + TYPE_LIST + ")\\) -> (" + NAME + ")"); + + public ProguardMappingsReader() {} + + @Override + public EntryTree read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws MappingParseException, IOException { + EntryTree mappings = new HashEntryTree<>(); + + int lineNumber = 0; + ClassEntry currentClass = null; + for (String line : Files.readAllLines(path, StandardCharsets.UTF_8)) { + lineNumber++; + + if (line.startsWith("#") || line.isEmpty()) { + continue; + } + + Matcher classMatcher = CLASS.matcher(line); + Matcher fieldMatcher = FIELD.matcher(line); + Matcher methodMatcher = METHOD.matcher(line); + + if (classMatcher.matches()) { + String name = classMatcher.group(1); + String targetName = classMatcher.group(2); + + mappings.insert(currentClass = new ClassEntry(name.replace('.', '/')), new EntryMapping(ClassEntry.getInnerName(targetName.replace('.', '/')))); + } else if (fieldMatcher.matches()) { + String type = fieldMatcher.group(1); + String name = fieldMatcher.group(2); + String targetName = fieldMatcher.group(3); + + if (currentClass == null) { + throw new MappingParseException(path::toString, lineNumber, "field mapping not inside class: " + line); + } + + mappings.insert(new FieldEntry(currentClass, name, new TypeDescriptor(getDescriptor(type))), new EntryMapping(targetName)); + } else if (methodMatcher.matches()) { + String returnType = methodMatcher.group(1); + String name = methodMatcher.group(2); + String[] parameterTypes = methodMatcher.group(3).isEmpty() ? new String[0] : methodMatcher.group(3).split(","); + String targetName = methodMatcher.group(4); + + if (currentClass == null) { + throw new MappingParseException(path::toString, lineNumber, "method mapping not inside class: " + line); + } + + mappings.insert(new MethodEntry(currentClass, name, new MethodDescriptor(getDescriptor(returnType, parameterTypes))), new EntryMapping(targetName)); + } else { + throw new MappingParseException(path::toString, lineNumber, "invalid mapping line: " + line); + } + } + + return MappingOperations.invert(mappings); + } + + private String getDescriptor(String type) { + StringBuilder descriptor = new StringBuilder(); + + while (type.endsWith("[]")) { + descriptor.append("["); + type = type.substring(0, type.length() - 2); + } + + switch (type) { + case "byte": + return descriptor + "B"; + case "char": + return descriptor + "C"; + case "short": + return descriptor + "S"; + case "int": + return descriptor + "I"; + case "long": + return descriptor + "J"; + case "float": + return descriptor + "F"; + case "double": + return descriptor + "D"; + case "boolean": + return descriptor + "Z"; + case "void": + return descriptor + "V"; + } + + descriptor.append("L"); + descriptor.append(type.replace('.', '/')); + descriptor.append(";"); + + return descriptor.toString(); + } + + private String getDescriptor(String returnType, String[] parameterTypes) { + StringBuilder descriptor = new StringBuilder(); + descriptor.append('('); + + for (String parameterType : parameterTypes) { + descriptor.append(getDescriptor(parameterType)); + } + + descriptor.append(')'); + descriptor.append(getDescriptor(returnType)); + + return descriptor.toString(); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/srg/SrgMappingsWriter.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/srg/SrgMappingsWriter.java new file mode 100644 index 00000000..9c9f103d --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/srg/SrgMappingsWriter.java @@ -0,0 +1,119 @@ +package cuchaz.enigma.translation.mapping.serde.srg; + +import com.google.common.collect.Lists; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.MappingTranslator; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.MappingDelta; +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.MappingsWriter; +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.MethodEntry; +import cuchaz.enigma.utils.I18n; + +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public enum SrgMappingsWriter implements MappingsWriter { + INSTANCE; + + @Override + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + try { + Files.deleteIfExists(path); + Files.createFile(path); + } catch (IOException e) { + e.printStackTrace(); + } + + List classLines = new ArrayList<>(); + List fieldLines = new ArrayList<>(); + List methodLines = new ArrayList<>(); + + Collection> rootEntries = Lists.newArrayList(mappings).stream() + .map(EntryTreeNode::getEntry) + .collect(Collectors.toList()); + progress.init(rootEntries.size(), I18n.translate("progress.mappings.srg_file.generating")); + + int steps = 0; + for (Entry entry : sorted(rootEntries)) { + progress.step(steps++, entry.getName()); + writeEntry(classLines, fieldLines, methodLines, mappings, entry); + } + + progress.init(3, I18n.translate("progress.mappings.srg_file.writing")); + try (PrintWriter writer = new LfPrintWriter(Files.newBufferedWriter(path))) { + progress.step(0, I18n.translate("type.classes")); + classLines.forEach(writer::println); + progress.step(1, I18n.translate("type.fields")); + fieldLines.forEach(writer::println); + progress.step(2, I18n.translate("type.methods")); + methodLines.forEach(writer::println); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void writeEntry(List classes, List fields, List methods, EntryTree mappings, Entry entry) { + EntryTreeNode node = mappings.findNode(entry); + if (node == null) { + return; + } + + Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE); + if (entry instanceof ClassEntry) { + classes.add(generateClassLine((ClassEntry) entry, translator)); + } else if (entry instanceof FieldEntry) { + fields.add(generateFieldLine((FieldEntry) entry, translator)); + } else if (entry instanceof MethodEntry) { + methods.add(generateMethodLine((MethodEntry) entry, translator)); + } + + for (Entry child : sorted(node.getChildren())) { + writeEntry(classes, fields, methods, mappings, child); + } + } + + private String generateClassLine(ClassEntry sourceEntry, Translator translator) { + ClassEntry targetEntry = translator.translate(sourceEntry); + return "CL: " + sourceEntry.getFullName() + " " + targetEntry.getFullName(); + } + + private String generateMethodLine(MethodEntry sourceEntry, Translator translator) { + MethodEntry targetEntry = translator.translate(sourceEntry); + return "MD: " + describeMethod(sourceEntry) + " " + describeMethod(targetEntry); + } + + private String describeMethod(MethodEntry entry) { + return entry.getParent().getFullName() + "/" + entry.getName() + " " + entry.getDesc(); + } + + private String generateFieldLine(FieldEntry sourceEntry, Translator translator) { + FieldEntry targetEntry = translator.translate(sourceEntry); + return "FD: " + describeField(sourceEntry) + " " + describeField(targetEntry); + } + + private String describeField(FieldEntry entry) { + return entry.getParent().getFullName() + "/" + entry.getName(); + } + + private Collection> sorted(Iterable> iterable) { + ArrayList> sorted = Lists.newArrayList(iterable); + sorted.sort(Comparator.comparing(Entry::getName)); + return sorted; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsReader.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsReader.java new file mode 100644 index 00000000..f3c66dfb --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsReader.java @@ -0,0 +1,116 @@ +package cuchaz.enigma.translation.mapping.serde.tiny; + +import com.google.common.base.Charsets; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.mapping.serde.MappingParseException; +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.MappingsReader; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.mapping.tree.HashEntryTree; +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.FieldEntry; +import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; +import cuchaz.enigma.utils.I18n; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +public enum TinyMappingsReader implements MappingsReader { + INSTANCE; + + @Override + public EntryTree read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException { + return read(path, Files.readAllLines(path, Charsets.UTF_8), progress); + } + + private EntryTree read(Path path, List lines, ProgressListener progress) throws MappingParseException { + EntryTree mappings = new HashEntryTree<>(); + lines.remove(0); + + progress.init(lines.size(), I18n.translate("progress.mappings.tiny_file.loading")); + + for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) { + progress.step(lineNumber, ""); + + String line = lines.get(lineNumber); + + if (line.trim().startsWith("#")) { + continue; + } + + try { + MappingPair mapping = parseLine(line); + mappings.insert(mapping.getEntry(), mapping.getMapping()); + } catch (Throwable t) { + t.printStackTrace(); + throw new MappingParseException(path::toString, lineNumber, t.toString()); + } + } + + return mappings; + } + + private MappingPair parseLine(String line) { + String[] tokens = line.split("\t"); + + String key = tokens[0]; + switch (key) { + case "CLASS": + return parseClass(tokens); + case "FIELD": + return parseField(tokens); + case "METHOD": + return parseMethod(tokens); + case "MTH-ARG": + return parseArgument(tokens); + default: + throw new RuntimeException("Unknown token '" + key + "'!"); + } + } + + private MappingPair parseClass(String[] tokens) { + ClassEntry obfuscatedEntry = new ClassEntry(tokens[1]); + String mapping = tokens[2]; + if (mapping.indexOf('$') > 0) { + // inner classes should map to only the final part + mapping = mapping.substring(mapping.lastIndexOf('$') + 1); + } + return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping)); + } + + private MappingPair parseField(String[] tokens) { + ClassEntry ownerClass = new ClassEntry(tokens[1]); + TypeDescriptor descriptor = new TypeDescriptor(tokens[2]); + + FieldEntry obfuscatedEntry = new FieldEntry(ownerClass, tokens[3], descriptor); + String mapping = tokens[4]; + return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping)); + } + + private MappingPair parseMethod(String[] tokens) { + ClassEntry ownerClass = new ClassEntry(tokens[1]); + MethodDescriptor descriptor = new MethodDescriptor(tokens[2]); + + MethodEntry obfuscatedEntry = new MethodEntry(ownerClass, tokens[3], descriptor); + String mapping = tokens[4]; + return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping)); + } + + private MappingPair parseArgument(String[] tokens) { + ClassEntry ownerClass = new ClassEntry(tokens[1]); + MethodDescriptor ownerDescriptor = new MethodDescriptor(tokens[2]); + MethodEntry ownerMethod = new MethodEntry(ownerClass, tokens[3], ownerDescriptor); + int variableIndex = Integer.parseInt(tokens[4]); + + String mapping = tokens[5]; + LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerMethod, variableIndex, "", true, null); + return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping)); + } +} 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 new file mode 100644 index 00000000..4f78e6f3 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsWriter.java @@ -0,0 +1,149 @@ +package cuchaz.enigma.translation.mapping.serde.tiny; + +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.MappingTranslator; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.MappingDelta; +import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; +import cuchaz.enigma.translation.mapping.VoidEntryResolver; +import cuchaz.enigma.translation.mapping.serde.MappingsWriter; +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.MethodEntry; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; + +public class TinyMappingsWriter implements MappingsWriter { + private static final String VERSION_CONSTANT = "v1"; + private static final Joiner TAB_JOINER = Joiner.on('\t'); + + //Possibly add a gui or a way to select the namespaces when exporting from the gui + public static final TinyMappingsWriter INSTANCE = new TinyMappingsWriter("intermediary", "named"); + + // HACK: as of enigma 0.13.1, some fields seem to appear duplicated? + private final Set writtenLines = new HashSet<>(); + private final String nameObf; + private final String nameDeobf; + + public TinyMappingsWriter(String nameObf, String nameDeobf) { + this.nameObf = nameObf; + this.nameDeobf = nameDeobf; + } + + @Override + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { + try { + Files.deleteIfExists(path); + Files.createFile(path); + } catch (IOException e) { + e.printStackTrace(); + } + + try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) { + writeLine(writer, new String[]{VERSION_CONSTANT, nameObf, nameDeobf}); + + Lists.newArrayList(mappings).stream() + .map(EntryTreeNode::getEntry).sorted(Comparator.comparing(Object::toString)) + .forEach(entry -> writeEntry(writer, mappings, entry)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void writeEntry(Writer writer, EntryTree mappings, Entry entry) { + EntryTreeNode node = mappings.findNode(entry); + if (node == null) { + return; + } + + Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE); + + EntryMapping mapping = mappings.get(entry); + if (mapping != null && !entry.getName().equals(mapping.getTargetName())) { + if (entry instanceof ClassEntry) { + writeClass(writer, (ClassEntry) entry, translator); + } else if (entry instanceof FieldEntry) { + writeLine(writer, serializeEntry(entry, mapping.getTargetName())); + } else if (entry instanceof MethodEntry) { + writeLine(writer, serializeEntry(entry, mapping.getTargetName())); + } + } + + writeChildren(writer, mappings, node); + } + + private void writeChildren(Writer writer, EntryTree mappings, EntryTreeNode node) { + node.getChildren().stream() + .filter(e -> e instanceof FieldEntry).sorted() + .forEach(child -> writeEntry(writer, mappings, child)); + + node.getChildren().stream() + .filter(e -> e instanceof MethodEntry).sorted() + .forEach(child -> writeEntry(writer, mappings, child)); + + node.getChildren().stream() + .filter(e -> e instanceof ClassEntry).sorted() + .forEach(child -> writeEntry(writer, mappings, child)); + } + + private void writeClass(Writer writer, ClassEntry entry, Translator translator) { + ClassEntry translatedEntry = translator.translate(entry); + + String obfClassName = entry.getFullName(); + String deobfClassName = translatedEntry.getFullName(); + writeLine(writer, new String[]{"CLASS", obfClassName, deobfClassName}); + } + + private void writeLine(Writer writer, String[] data) { + try { + String line = TAB_JOINER.join(data) + "\n"; + if (writtenLines.add(line)) { + writer.write(line); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private String[] serializeEntry(Entry entry, String... extraFields) { + String[] data = null; + + if (entry instanceof FieldEntry) { + data = new String[4 + extraFields.length]; + data[0] = "FIELD"; + data[1] = entry.getContainingClass().getFullName(); + data[2] = ((FieldEntry) entry).getDesc().toString(); + data[3] = entry.getName(); + } else if (entry instanceof MethodEntry) { + data = new String[4 + extraFields.length]; + data[0] = "METHOD"; + data[1] = entry.getContainingClass().getFullName(); + data[2] = ((MethodEntry) entry).getDesc().toString(); + data[3] = entry.getName(); + } else if (entry instanceof ClassEntry) { + data = new String[2 + extraFields.length]; + data[0] = "CLASS"; + data[1] = ((ClassEntry) entry).getFullName(); + } + + if (data != null) { + System.arraycopy(extraFields, 0, data, data.length - extraFields.length, extraFields.length); + } + + return data; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Reader.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Reader.java new file mode 100644 index 00000000..dc255693 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Reader.java @@ -0,0 +1,296 @@ +package cuchaz.enigma.translation.mapping.serde.tinyv2; + +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.mapping.serde.MappingParseException; +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.MappingsReader; +import cuchaz.enigma.translation.mapping.serde.RawEntryMapping; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.mapping.tree.HashEntryTree; +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.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.BitSet; +import java.util.List; + +public final class TinyV2Reader implements MappingsReader { + + private static final String MINOR_VERSION = "0"; + // 0 indent + private static final int IN_HEADER = 0; + private static final int IN_CLASS = IN_HEADER + 1; + // 1 indent + private static final int IN_METHOD = IN_CLASS + 1; + private static final int IN_FIELD = IN_METHOD + 1; + // 2 indent + private static final int IN_PARAMETER = IN_FIELD + 1; + // general properties + private static final int STATE_SIZE = IN_PARAMETER + 1; + private static final int[] INDENT_CLEAR_START = {IN_HEADER, IN_METHOD, IN_PARAMETER, STATE_SIZE}; + + @Override + public EntryTree read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException { + return read(path, Files.readAllLines(path, StandardCharsets.UTF_8), progress); + } + + private EntryTree read(Path path, List lines, ProgressListener progress) throws MappingParseException { + EntryTree mappings = new HashEntryTree<>(); + + progress.init(lines.size(), "progress.mappings.tiny_v2.loading"); + + BitSet state = new BitSet(STATE_SIZE); + @SuppressWarnings({"unchecked", "rawtypes"}) + MappingPair, RawEntryMapping>[] holds = new MappingPair[STATE_SIZE]; + boolean escapeNames = false; + + for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) { + try { + progress.step(lineNumber, ""); + String line = lines.get(lineNumber); + + int indent = 0; + while (line.charAt(indent) == '\t') + indent++; + + String[] parts = line.substring(indent).split("\t", -1); + if (parts.length == 0 || indent >= INDENT_CLEAR_START.length) + throw new IllegalArgumentException("Invalid format"); + + // clean and register stuff in stack + for (int i = INDENT_CLEAR_START[indent]; i < STATE_SIZE; i++) { + state.clear(i); + if (holds[i] != null) { + RawEntryMapping mapping = holds[i].getMapping(); + if (mapping != null) { + EntryMapping baked = mapping.bake(); + if (baked != null) { + mappings.insert(holds[i].getEntry(), baked); + } + } + holds[i] = null; + } + } + + switch (indent) { + case 0: + switch (parts[0]) { + case "tiny": // header + if (lineNumber != 0) { + throw new IllegalArgumentException("Header can only be on the first line"); + } + if (parts.length < 5) { + throw new IllegalArgumentException("Not enough header columns, needs at least 5"); + } + if (!"2".equals(parts[1]) || !MINOR_VERSION.equals(parts[2])) { + throw new IllegalArgumentException("Unsupported TinyV2 version, requires major " + "2" + " and minor " + MINOR_VERSION + ""); + } + state.set(IN_HEADER); + break; + case "c": // class + state.set(IN_CLASS); + holds[IN_CLASS] = parseClass(parts, escapeNames); + break; + default: + unsupportKey(parts); + } + + break; + case 1: + if (state.get(IN_HEADER)) { + if (parts[0].equals("esacpe-names")) { + escapeNames = true; + } + + break; + } + + if (state.get(IN_CLASS)) { + switch (parts[0]) { + case "m": // method + state.set(IN_METHOD); + holds[IN_METHOD] = parseMethod(holds[IN_CLASS], parts, escapeNames); + break; + case "f": // field + state.set(IN_FIELD); + holds[IN_FIELD] = parseField(holds[IN_CLASS], parts, escapeNames); + break; + case "c": // class javadoc + addJavadoc(holds[IN_CLASS], parts); + break; + default: + unsupportKey(parts); + } + break; + } + + unsupportKey(parts); + case 2: + if (state.get(IN_METHOD)) { + switch (parts[0]) { + case "p": // parameter + state.set(IN_PARAMETER); + holds[IN_PARAMETER] = parseArgument(holds[IN_METHOD], parts, escapeNames); + break; + case "v": // local variable + // TODO add local var mapping + break; + case "c": // method javadoc + addJavadoc(holds[IN_METHOD], parts); + break; + default: + unsupportKey(parts); + } + break; + } + + if (state.get(IN_FIELD)) { + switch (parts[0]) { + case "c": // field javadoc + addJavadoc(holds[IN_FIELD], parts); + break; + default: + unsupportKey(parts); + } + break; + } + unsupportKey(parts); + case 3: + if (state.get(IN_PARAMETER)) { + switch (parts[0]) { + case "c": + addJavadoc(holds[IN_PARAMETER], parts); + break; + default: + unsupportKey(parts); + } + break; + } + unsupportKey(parts); + default: + unsupportKey(parts); + } + + } catch (Throwable t) { + t.printStackTrace(); + throw new MappingParseException(path::toString, lineNumber + 1, t.toString()); + } + } + + return mappings; + } + + private void unsupportKey(String[] parts) { + throw new IllegalArgumentException("Unsupported key " + parts[0]); + } + + private void addJavadoc(MappingPair pair, String[] parts) { + if (parts.length != 2) { + throw new IllegalArgumentException("Invalid javadoc declaration"); + } + + addJavadoc(pair, parts[1]); + } + + private MappingPair parseClass(String[] tokens, boolean escapeNames) { + ClassEntry obfuscatedEntry = new ClassEntry(unescapeOpt(tokens[1], escapeNames)); + if (tokens.length <= 2) + return new MappingPair<>(obfuscatedEntry); + String token2 = unescapeOpt(tokens[2], escapeNames); + String mapping = token2.substring(token2.lastIndexOf('$') + 1); + return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping)); + } + + private MappingPair parseField(MappingPair parent, String[] tokens, boolean escapeNames) { + ClassEntry ownerClass = (ClassEntry) parent.getEntry(); + TypeDescriptor descriptor = new TypeDescriptor(unescapeOpt(tokens[1], escapeNames)); + + FieldEntry obfuscatedEntry = new FieldEntry(ownerClass, unescapeOpt(tokens[2], escapeNames), descriptor); + if (tokens.length <= 3) + return new MappingPair<>(obfuscatedEntry); + String mapping = unescapeOpt(tokens[3], escapeNames); + return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping)); + } + + private MappingPair parseMethod(MappingPair parent, String[] tokens, boolean escapeNames) { + ClassEntry ownerClass = (ClassEntry) parent.getEntry(); + MethodDescriptor descriptor = new MethodDescriptor(unescapeOpt(tokens[1], escapeNames)); + + MethodEntry obfuscatedEntry = new MethodEntry(ownerClass, unescapeOpt(tokens[2], escapeNames), descriptor); + if (tokens.length <= 3) + return new MappingPair<>(obfuscatedEntry); + String mapping = unescapeOpt(tokens[3], escapeNames); + return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping)); + } + + + + private void addJavadoc(MappingPair pair, String javadoc) { + RawEntryMapping mapping = pair.getMapping(); + if (mapping == null) { + throw new IllegalArgumentException("Javadoc requires a mapping in enigma!"); + } + mapping.addJavadocLine(unescape(javadoc)); + } + + + + private MappingPair parseArgument(MappingPair parent, String[] tokens, boolean escapeNames) { + MethodEntry ownerMethod = (MethodEntry) parent.getEntry(); + int variableIndex = Integer.parseInt(tokens[1]); + + // tokens[2] is the useless obf name + + LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerMethod, variableIndex, "", true, null); + if (tokens.length <= 3) + return new MappingPair<>(obfuscatedEntry); + String mapping = unescapeOpt(tokens[3], escapeNames); + return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping)); + } + + private static final String TO_ESCAPE = "\\\n\r\0\t"; + private static final String ESCAPED = "\\nr0t"; + + private static String unescapeOpt(String raw, boolean escapedStrings) { + return escapedStrings ? unescape(raw) : raw; + } + + private static String unescape(String str) { + // copied from matcher, lazy! + int pos = str.indexOf('\\'); + if (pos < 0) return str; + + StringBuilder ret = new StringBuilder(str.length() - 1); + int start = 0; + + do { + ret.append(str, start, pos); + pos++; + int type; + + if (pos >= str.length()) { + throw new RuntimeException("incomplete escape sequence at the end"); + } else if ((type = ESCAPED.indexOf(str.charAt(pos))) < 0) { + throw new RuntimeException("invalid escape character: \\" + str.charAt(pos)); + } else { + ret.append(TO_ESCAPE.charAt(type)); + } + + start = pos + 1; + } while ((pos = str.indexOf('\\', start)) >= 0); + + ret.append(str, start, str.length()); + + return ret.toString(); + } +} 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 new file mode 100644 index 00000000..5546b2fe --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Writer.java @@ -0,0 +1,172 @@ +package cuchaz.enigma.translation.mapping.serde.tinyv2; + +import com.google.common.base.Strings; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.MappingDelta; +import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; +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.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 java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public final class TinyV2Writer implements MappingsWriter { + + private static final String MINOR_VERSION = "0"; + private final String obfHeader; + private final String deobfHeader; + + public TinyV2Writer(String obfHeader, String deobfHeader) { + this.obfHeader = obfHeader; + this.deobfHeader = deobfHeader; + } + + @Override + public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters parameters) { + List> classes = StreamSupport.stream(mappings.spliterator(), false).filter(node -> node.getEntry() instanceof ClassEntry).collect(Collectors.toList()); + + try (PrintWriter writer = new LfPrintWriter(Files.newBufferedWriter(path))) { + writer.println("tiny\t2\t" + MINOR_VERSION + "\t" + obfHeader + "\t" + deobfHeader); + + // no escape names + + for (EntryTreeNode node : classes) { + writeClass(writer, node, mappings); + } + } catch (IOException ex) { + ex.printStackTrace(); // TODO add some better logging system + } + } + + private void writeClass(PrintWriter writer, EntryTreeNode node, EntryMap tree) { + writer.print("c\t"); + ClassEntry classEntry = (ClassEntry) node.getEntry(); + String fullName = classEntry.getFullName(); + writer.print(fullName); + Deque parts = new LinkedList<>(); + do { + EntryMapping mapping = tree.get(classEntry); + if (mapping != null) { + parts.addFirst(mapping.getTargetName()); + } else { + parts.addFirst(classEntry.getName()); + } + classEntry = classEntry.getOuterClass(); + } while (classEntry != null); + + String mappedName = String.join("$", parts); + + writer.print("\t"); + + writer.print(mappedName); // todo escaping when we have v2 fixed later + + writer.println(); + + writeComment(writer, node.getValue(), 1); + + for (EntryTreeNode child : node.getChildNodes()) { + Entry entry = child.getEntry(); + if (entry instanceof FieldEntry) { + writeField(writer, child); + } else if (entry instanceof MethodEntry) { + writeMethod(writer, child); + } + } + } + + private void writeMethod(PrintWriter writer, EntryTreeNode node) { + writer.print(indent(1)); + writer.print("m\t"); + writer.print(((MethodEntry) node.getEntry()).getDesc().toString()); + writer.print("\t"); + 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()); + + writeComment(writer, mapping, 2); + } + + for (EntryTreeNode child : node.getChildNodes()) { + Entry entry = child.getEntry(); + if (entry instanceof LocalVariableEntry) { + writeParameter(writer, child); + } + // TODO write actual local variables + } + } + + private void writeField(PrintWriter writer, EntryTreeNode node) { + if (node.getValue() == null) + return; // Shortcut + + writer.print(indent(1)); + writer.print("f\t"); + writer.print(((FieldEntry) node.getEntry()).getDesc().toString()); + writer.print("\t"); + 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()); + + writeComment(writer, mapping, 2); + } + } + + private void writeParameter(PrintWriter writer, EntryTreeNode node) { + if (node.getValue() == null) + return; // Shortcut + + writer.print(indent(2)); + writer.print("p\t"); + writer.print(((LocalVariableEntry) node.getEntry()).getIndex()); + writer.print("\t"); + writer.print(node.getEntry().getName()); + writer.print("\t"); + EntryMapping mapping = node.getValue(); + if (mapping == null) { + writer.println(); // todo ??? + } else { + writer.println(mapping.getTargetName()); + + writeComment(writer, mapping, 3); + } + } + + private void writeComment(PrintWriter writer, EntryMapping mapping, int indent) { + if (mapping != null && mapping.getJavadoc() != null) { + writer.print(indent(indent)); + writer.print("c\t"); + writer.print(MappingHelper.escape(mapping.getJavadoc())); + writer.println(); + } + } + + private String indent(int level) { + return Strings.repeat("\t", level); + } + +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java new file mode 100644 index 00000000..255fa5fb --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java @@ -0,0 +1,110 @@ +package cuchaz.enigma.translation.mapping.tree; + +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.MappingDelta; +import cuchaz.enigma.translation.representation.entry.Entry; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Iterator; +import java.util.stream.Stream; + +public class DeltaTrackingTree implements EntryTree { + private final EntryTree delegate; + + private EntryTree deltaReference; + private EntryTree changes = new HashEntryTree<>(); + + public DeltaTrackingTree(EntryTree delegate) { + this.delegate = delegate; + this.deltaReference = new HashEntryTree<>(delegate); + } + + public DeltaTrackingTree() { + this(new HashEntryTree<>()); + } + + @Override + public void insert(Entry entry, T value) { + trackChange(entry); + delegate.insert(entry, value); + } + + @Nullable + @Override + public T remove(Entry entry) { + trackChange(entry); + return delegate.remove(entry); + } + + public void trackChange(Entry entry) { + changes.insert(entry, MappingDelta.PLACEHOLDER); + } + + @Nullable + @Override + public T get(Entry entry) { + return delegate.get(entry); + } + + @Override + public Collection> getChildren(Entry entry) { + return delegate.getChildren(entry); + } + + @Override + public Collection> getSiblings(Entry entry) { + return delegate.getSiblings(entry); + } + + @Nullable + @Override + public EntryTreeNode findNode(Entry entry) { + return delegate.findNode(entry); + } + + @Override + public Stream> getRootNodes() { + return delegate.getRootNodes(); + } + + @Override + public DeltaTrackingTree translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + DeltaTrackingTree translatedTree = new DeltaTrackingTree<>(delegate.translate(translator, resolver, mappings)); + translatedTree.changes = changes.translate(translator, resolver, mappings); + return translatedTree; + } + + @Override + public Stream> getAllEntries() { + return delegate.getAllEntries(); + } + + @Override + public boolean isEmpty() { + return delegate.isEmpty(); + } + + @Override + public Iterator> iterator() { + return delegate.iterator(); + } + + public MappingDelta takeDelta() { + MappingDelta delta = new MappingDelta<>(deltaReference, changes); + resetDelta(); + return delta; + } + + private void resetDelta() { + deltaReference = new HashEntryTree<>(delegate); + changes = new HashEntryTree<>(); + } + + public boolean isDirty() { + return !changes.isEmpty(); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java new file mode 100644 index 00000000..daaefcc1 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java @@ -0,0 +1,26 @@ +package cuchaz.enigma.translation.mapping.tree; + +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.representation.entry.Entry; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.stream.Stream; + +public interface EntryTree extends EntryMap, Iterable>, Translatable { + Collection> getChildren(Entry entry); + + Collection> getSiblings(Entry entry); + + @Nullable + EntryTreeNode findNode(Entry entry); + + Stream> getRootNodes(); + + @Override + EntryTree translate(Translator translator, EntryResolver resolver, EntryMap mappings); +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java new file mode 100644 index 00000000..affcd504 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java @@ -0,0 +1,40 @@ +package cuchaz.enigma.translation.mapping.tree; + +import cuchaz.enigma.translation.representation.entry.Entry; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.stream.Collectors; + +public interface EntryTreeNode { + @Nullable + T getValue(); + + Entry getEntry(); + + boolean isEmpty(); + + Collection> getChildren(); + + Collection> getChildNodes(); + + default Collection> getNodesRecursively() { + Collection> nodes = new ArrayList<>(); + nodes.add(this); + for (EntryTreeNode node : getChildNodes()) { + nodes.addAll(node.getNodesRecursively()); + } + return nodes; + } + + default Collection> getChildrenRecursively() { + return getNodesRecursively().stream() + .map(EntryTreeNode::getEntry) + .collect(Collectors.toList()); + } + + default boolean hasValue() { + return getValue() != null; + } +} 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 new file mode 100644 index 00000000..570941cd --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java @@ -0,0 +1,188 @@ +package cuchaz.enigma.translation.mapping.tree; + +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.representation.entry.Entry; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +public class HashEntryTree implements EntryTree { + private final Map, HashTreeNode> root = new HashMap<>(); + + public HashEntryTree() { + } + + public HashEntryTree(EntryTree tree) { + for (EntryTreeNode node : tree) { + insert(node.getEntry(), node.getValue()); + } + } + + @Override + public void insert(Entry entry, T value) { + List> path = computePath(entry, true); + path.get(path.size() - 1).putValue(value); + if (value == null) { + removeDeadAlong(path); + } + } + + @Override + @Nullable + public T remove(Entry entry) { + List> path = computePath(entry, false); + if (path.isEmpty()) { + return null; + } + + T value = path.get(path.size() - 1).removeValue(); + + removeDeadAlong(path); + + return value; + } + + @Override + @Nullable + public T get(Entry entry) { + HashTreeNode node = findNode(entry); + if (node == null) { + return null; + } + return node.getValue(); + } + + @Override + public boolean contains(Entry entry) { + return get(entry) != null; + } + + @Override + public Collection> getChildren(Entry entry) { + HashTreeNode leaf = findNode(entry); + if (leaf == null) { + return Collections.emptyList(); + } + return leaf.getChildren(); + } + + @Override + public Collection> getSiblings(Entry entry) { + Entry parent = entry.getParent(); + if (parent == null) { + return getSiblings(entry, root.keySet()); + } + return getSiblings(entry, getChildren(parent)); + } + + private Collection> getSiblings(Entry entry, Collection> generation) { + Set> siblings = new HashSet<>(generation); + siblings.remove(entry); + return siblings; + } + + @Override + @Nullable + public HashTreeNode findNode(Entry target) { + List> parentChain = target.getAncestry(); + if (parentChain.isEmpty()) { + return null; + } + + HashTreeNode node = root.get(parentChain.get(0)); + for (int i = 1; i < parentChain.size(); i++) { + if (node == null) { + return null; + } + node = node.getChild(parentChain.get(i)); + } + + return node; + } + + private List> computePath(Entry target, boolean make) { + List> ancestry = target.getAncestry(); + if (ancestry.isEmpty()) { + return Collections.emptyList(); + } + + List> path = new ArrayList<>(ancestry.size()); + + Entry rootEntry = ancestry.get(0); + HashTreeNode node = make ? root.computeIfAbsent(rootEntry, HashTreeNode::new) : root.get(rootEntry); + if (node == null) { + return Collections.emptyList(); + } + + path.add(node); + + for (int i = 1; i < ancestry.size(); i++) { + Entry ancestor = ancestry.get(i); + node = make ? node.computeChild(ancestor) : node.getChild(ancestor); + if (node == null) { + return Collections.emptyList(); + } + + path.add(node); + } + + return path; + } + + private void removeDeadAlong(List> path) { + for (int i = path.size() - 1; i >= 0; i--) { + HashTreeNode node = path.get(i); + if (node.isEmpty()) { + if (i > 0) { + HashTreeNode parentNode = path.get(i - 1); + parentNode.remove(node.getEntry()); + } else { + root.remove(node.getEntry()); + } + } else { + break; + } + } + } + + @Override + public Iterator> iterator() { + Collection> nodes = new ArrayList<>(); + for (EntryTreeNode node : root.values()) { + nodes.addAll(node.getNodesRecursively()); + } + return nodes.iterator(); + } + + @Override + public Stream> getAllEntries() { + return StreamSupport.stream(spliterator(), false) + .filter(EntryTreeNode::hasValue) + .map(EntryTreeNode::getEntry); + } + + @Override + public Stream> getRootNodes() { + return root.values().stream().map(Function.identity()); + } + + @Override + public boolean isEmpty() { + return root.isEmpty(); + } + + @Override + public HashEntryTree translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + HashEntryTree translatedTree = new HashEntryTree<>(); + for (EntryTreeNode node : this) { + translatedTree.insert(translator.translate(node.getEntry()), node.getValue()); + } + return translatedTree; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java new file mode 100644 index 00000000..0a990bd5 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java @@ -0,0 +1,75 @@ +package cuchaz.enigma.translation.mapping.tree; + +import cuchaz.enigma.translation.representation.entry.Entry; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +public class HashTreeNode implements EntryTreeNode, Iterable> { + private final Entry entry; + private final Map, HashTreeNode> children = new HashMap<>(); + private T value; + + HashTreeNode(Entry entry) { + this.entry = entry; + } + + void putValue(T value) { + this.value = value; + } + + T removeValue() { + T value = this.value; + this.value = null; + return value; + } + + @Nullable + HashTreeNode getChild(Entry entry) { + return children.get(entry); + } + + @Nonnull + HashTreeNode computeChild(Entry entry) { + return children.computeIfAbsent(entry, HashTreeNode::new); + } + + void remove(Entry entry) { + children.remove(entry); + } + + @Override + @Nullable + public T getValue() { + return value; + } + + @Override + public Entry getEntry() { + return entry; + } + + @Override + public boolean isEmpty() { + return children.isEmpty() && value == null; + } + + @Override + public Collection> getChildren() { + return children.keySet(); + } + + @Override + public Collection> getChildNodes() { + return children.values(); + } + + @Override + public Iterator> iterator() { + return children.values().iterator(); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java new file mode 100644 index 00000000..b280eef2 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java @@ -0,0 +1,116 @@ +package cuchaz.enigma.translation.representation; + +import cuchaz.enigma.analysis.Access; +import org.objectweb.asm.Opcodes; + +import java.lang.reflect.Modifier; + +public class AccessFlags { + public static final AccessFlags PRIVATE = new AccessFlags(Opcodes.ACC_PRIVATE); + public static final AccessFlags PUBLIC = new AccessFlags(Opcodes.ACC_PUBLIC); + + private int flags; + + public AccessFlags(int flags) { + this.flags = flags; + } + + public boolean isPrivate() { + return Modifier.isPrivate(this.flags); + } + + public boolean isProtected() { + return Modifier.isProtected(this.flags); + } + + public boolean isPublic() { + return Modifier.isPublic(this.flags); + } + + public boolean isSynthetic() { + return (this.flags & Opcodes.ACC_SYNTHETIC) != 0; + } + + public boolean isStatic() { + return Modifier.isStatic(this.flags); + } + + public boolean isEnum() { + return (flags & Opcodes.ACC_ENUM) != 0; + } + + public boolean isBridge() { + return (flags & Opcodes.ACC_BRIDGE) != 0; + } + + public boolean isFinal() { + return (flags & Opcodes.ACC_FINAL) != 0; + } + + public boolean isInterface() { + return (flags & Opcodes.ACC_INTERFACE) != 0; + } + + public AccessFlags setPrivate() { + this.setVisibility(Opcodes.ACC_PRIVATE); + return this; + } + + public AccessFlags setProtected() { + this.setVisibility(Opcodes.ACC_PROTECTED); + return this; + } + + public AccessFlags setPublic() { + this.setVisibility(Opcodes.ACC_PUBLIC); + return this; + } + + public AccessFlags setBridge() { + flags |= Opcodes.ACC_BRIDGE; + return this; + } + + @Deprecated + public AccessFlags setBridged() { + return setBridge(); + } + + public void setVisibility(int visibility) { + this.resetVisibility(); + this.flags |= visibility; + } + + private void resetVisibility() { + this.flags &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC); + } + + public int getFlags() { + return this.flags; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof AccessFlags && ((AccessFlags) obj).flags == flags; + } + + @Override + public int hashCode() { + return flags; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(Access.get(this).toString().toLowerCase()); + if (isStatic()) { + builder.append(" static"); + } + if (isSynthetic()) { + builder.append(" synthetic"); + } + if (isBridge()) { + builder.append(" bridge"); + } + return builder.toString(); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/Lambda.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/Lambda.java new file mode 100644 index 00000000..ad9389c1 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/Lambda.java @@ -0,0 +1,105 @@ +package cuchaz.enigma.translation.representation; + +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; +import cuchaz.enigma.translation.representation.entry.ParentedEntry; + +import java.util.Objects; + +public class Lambda implements Translatable { + private final String invokedName; + private final MethodDescriptor invokedType; + private final MethodDescriptor samMethodType; + private final ParentedEntry implMethod; + private final MethodDescriptor instantiatedMethodType; + + public Lambda(String invokedName, MethodDescriptor invokedType, MethodDescriptor samMethodType, ParentedEntry implMethod, MethodDescriptor instantiatedMethodType) { + this.invokedName = invokedName; + this.invokedType = invokedType; + this.samMethodType = samMethodType; + this.implMethod = implMethod; + this.instantiatedMethodType = instantiatedMethodType; + } + + @Override + public Lambda translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + MethodEntry samMethod = new MethodEntry(getInterface(), invokedName, samMethodType); + EntryMapping samMethodMapping = resolveMapping(resolver, mappings, samMethod); + + return new Lambda( + samMethodMapping != null ? samMethodMapping.getTargetName() : invokedName, + invokedType.translate(translator, resolver, mappings), + samMethodType.translate(translator, resolver, mappings), + implMethod.translate(translator, resolver, mappings), + instantiatedMethodType.translate(translator, resolver, mappings) + ); + } + + private EntryMapping resolveMapping(EntryResolver resolver, EntryMap mappings, MethodEntry methodEntry) { + for (MethodEntry entry : resolver.resolveEntry(methodEntry, ResolutionStrategy.RESOLVE_ROOT)) { + EntryMapping mapping = mappings.get(entry); + if (mapping != null) { + return mapping; + } + } + return null; + } + + public ClassEntry getInterface() { + return invokedType.getReturnDesc().getTypeEntry(); + } + + public String getInvokedName() { + return invokedName; + } + + public MethodDescriptor getInvokedType() { + return invokedType; + } + + public MethodDescriptor getSamMethodType() { + return samMethodType; + } + + public ParentedEntry getImplMethod() { + return implMethod; + } + + public MethodDescriptor getInstantiatedMethodType() { + return instantiatedMethodType; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Lambda lambda = (Lambda) o; + return Objects.equals(invokedName, lambda.invokedName) && + Objects.equals(invokedType, lambda.invokedType) && + Objects.equals(samMethodType, lambda.samMethodType) && + Objects.equals(implMethod, lambda.implMethod) && + Objects.equals(instantiatedMethodType, lambda.instantiatedMethodType); + } + + @Override + public int hashCode() { + return Objects.hash(invokedName, invokedType, samMethodType, implMethod, instantiatedMethodType); + } + + @Override + public String toString() { + return "Lambda{" + + "invokedName='" + invokedName + '\'' + + ", invokedType=" + invokedType + + ", samMethodType=" + samMethodType + + ", implMethod=" + implMethod + + ", instantiatedMethodType=" + instantiatedMethodType + + '}'; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java new file mode 100644 index 00000000..03c8686a --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.translation.representation; + +import com.google.common.collect.Lists; +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.representation.entry.ClassEntry; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +public class MethodDescriptor implements Translatable { + + private List argumentDescs; + private TypeDescriptor returnDesc; + + public MethodDescriptor(String desc) { + try { + this.argumentDescs = Lists.newArrayList(); + int i = 0; + while (i < desc.length()) { + char c = desc.charAt(i); + if (c == '(') { + assert (this.argumentDescs.isEmpty()); + assert (this.returnDesc == null); + i++; + } else if (c == ')') { + i++; + break; + } else { + String type = TypeDescriptor.parseFirst(desc.substring(i)); + this.argumentDescs.add(new TypeDescriptor(type)); + i += type.length(); + } + } + this.returnDesc = new TypeDescriptor(TypeDescriptor.parseFirst(desc.substring(i))); + } catch (Exception ex) { + throw new IllegalArgumentException("Unable to parse method descriptor: " + desc, ex); + } + } + + public MethodDescriptor(List argumentDescs, TypeDescriptor returnDesc) { + this.argumentDescs = argumentDescs; + this.returnDesc = returnDesc; + } + + public List getArgumentDescs() { + return this.argumentDescs; + } + + public TypeDescriptor getReturnDesc() { + return this.returnDesc; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("("); + for (TypeDescriptor desc : this.argumentDescs) { + buf.append(desc); + } + buf.append(")"); + buf.append(this.returnDesc); + return buf.toString(); + } + + public Iterable types() { + List descs = Lists.newArrayList(); + descs.addAll(this.argumentDescs); + descs.add(this.returnDesc); + return descs; + } + + @Override + public boolean equals(Object other) { + return other instanceof MethodDescriptor && equals((MethodDescriptor) other); + } + + public boolean equals(MethodDescriptor other) { + return this.argumentDescs.equals(other.argumentDescs) && this.returnDesc.equals(other.returnDesc); + } + + @Override + public int hashCode() { + return Objects.hash(this.argumentDescs.hashCode(), this.returnDesc.hashCode()); + } + + public boolean hasClass(ClassEntry classEntry) { + for (TypeDescriptor desc : types()) { + if (desc.containsType() && desc.getTypeEntry().equals(classEntry)) { + return true; + } + } + return false; + } + + public MethodDescriptor remap(Function remapper) { + List argumentDescs = new ArrayList<>(this.argumentDescs.size()); + for (TypeDescriptor desc : this.argumentDescs) { + argumentDescs.add(desc.remap(remapper)); + } + return new MethodDescriptor(argumentDescs, returnDesc.remap(remapper)); + } + + @Override + public MethodDescriptor translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + List translatedArguments = new ArrayList<>(argumentDescs.size()); + for (TypeDescriptor argument : argumentDescs) { + translatedArguments.add(translator.translate(argument)); + } + return new MethodDescriptor(translatedArguments, translator.translate(returnDesc)); + } + + public boolean canConflictWith(MethodDescriptor descriptor) { + return descriptor.argumentDescs.equals(argumentDescs); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/Signature.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/Signature.java new file mode 100644 index 00000000..424088ab --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/Signature.java @@ -0,0 +1,98 @@ +package cuchaz.enigma.translation.representation; + +import cuchaz.enigma.bytecode.translators.TranslationSignatureVisitor; +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import org.objectweb.asm.signature.SignatureReader; +import org.objectweb.asm.signature.SignatureVisitor; +import org.objectweb.asm.signature.SignatureWriter; + +import java.util.function.Function; +import java.util.regex.Pattern; + +public class Signature implements Translatable { + private static final Pattern OBJECT_PATTERN = Pattern.compile(".*:Ljava/lang/Object;:.*"); + + private final String signature; + private final boolean isType; + + private Signature(String signature, boolean isType) { + if (signature != null && OBJECT_PATTERN.matcher(signature).matches()) { + signature = signature.replaceAll(":Ljava/lang/Object;:", "::"); + } + + this.signature = signature; + this.isType = isType; + } + + public static Signature createTypedSignature(String signature) { + if (signature != null && !signature.isEmpty()) { + return new Signature(signature, true); + } + return new Signature(null, true); + } + + public static Signature createSignature(String signature) { + if (signature != null && !signature.isEmpty()) { + return new Signature(signature, false); + } + return new Signature(null, false); + } + + public String getSignature() { + return signature; + } + + public boolean isType() { + return isType; + } + + public Signature remap(Function remapper) { + if (signature == null) { + return this; + } + SignatureWriter writer = new SignatureWriter(); + SignatureVisitor visitor = new TranslationSignatureVisitor(remapper, writer); + if (isType) { + new SignatureReader(signature).acceptType(visitor); + } else { + new SignatureReader(signature).accept(visitor); + } + return new Signature(writer.toString(), isType); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Signature) { + Signature other = (Signature) obj; + return (other.signature == null && signature == null || other.signature != null + && signature != null && other.signature.equals(signature)) + && other.isType == this.isType; + } + return false; + } + + @Override + public int hashCode() { + int hash = (isType ? 1 : 0) << 16; + if (signature != null) { + hash |= signature.hashCode(); + } + + return hash; + } + + @Override + public String toString() { + return signature; + } + + @Override + public Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + return remap(name -> translator.translate(new ClassEntry(name)).getFullName()); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java new file mode 100644 index 00000000..f7ba849e --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java @@ -0,0 +1,268 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.translation.representation; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.representation.entry.ClassEntry; + +import java.util.Map; +import java.util.function.Function; + +public class TypeDescriptor implements Translatable { + + protected final String desc; + + public TypeDescriptor(String desc) { + Preconditions.checkNotNull(desc, "Desc cannot be null"); + + // don't deal with generics + // this is just for raw jvm types + if (desc.charAt(0) == 'T' || desc.indexOf('<') >= 0 || desc.indexOf('>') >= 0) { + throw new IllegalArgumentException("don't use with generic types or templates: " + desc); + } + + this.desc = desc; + } + + public static String parseFirst(String in) { + + if (in == null || in.length() <= 0) { + throw new IllegalArgumentException("No desc to parse, input is empty!"); + } + + // read one desc from the input + + char c = in.charAt(0); + + // first check for void + if (c == 'V') { + return "V"; + } + + // then check for primitives + Primitive primitive = Primitive.get(c); + if (primitive != null) { + return in.substring(0, 1); + } + + // then check for classes + if (c == 'L') { + return readClass(in); + } + + // then check for templates + if (c == 'T') { + return readClass(in); + } + + // then check for arrays + int dim = countArrayDimension(in); + if (dim > 0) { + String arrayType = TypeDescriptor.parseFirst(in.substring(dim)); + return in.substring(0, dim + arrayType.length()); + } + + throw new IllegalArgumentException("don't know how to parse: " + in); + } + + private static int countArrayDimension(String in) { + int i = 0; + while (i < in.length() && in.charAt(i) == '[') + i++; + return i; + } + + private static String readClass(String in) { + // read all the characters in the buffer until we hit a ';' + // include the parameters too + StringBuilder buf = new StringBuilder(); + int depth = 0; + for (int i = 0; i < in.length(); i++) { + char c = in.charAt(i); + buf.append(c); + + if (c == '<') { + depth++; + } else if (c == '>') { + depth--; + } else if (depth == 0 && c == ';') { + return buf.toString(); + } + } + return null; + } + + public static TypeDescriptor of(String name) { + return new TypeDescriptor("L" + name + ";"); + } + + @Override + public String toString() { + return this.desc; + } + + public boolean isVoid() { + return this.desc.length() == 1 && this.desc.charAt(0) == 'V'; + } + + public boolean isPrimitive() { + return this.desc.length() == 1 && Primitive.get(this.desc.charAt(0)) != null; + } + + public Primitive getPrimitive() { + if (!isPrimitive()) { + throw new IllegalStateException("not a primitive"); + } + return Primitive.get(this.desc.charAt(0)); + } + + public boolean isType() { + return this.desc.charAt(0) == 'L' && this.desc.charAt(this.desc.length() - 1) == ';'; + } + + public ClassEntry getTypeEntry() { + if (isType()) { + String name = this.desc.substring(1, this.desc.length() - 1); + + int pos = name.indexOf('<'); + if (pos >= 0) { + // remove the parameters from the class name + name = name.substring(0, pos); + } + + return new ClassEntry(name); + + } else if (isArray() && getArrayType().isType()) { + return getArrayType().getTypeEntry(); + } else { + throw new IllegalStateException("desc doesn't have a class"); + } + } + + public boolean isArray() { + return this.desc.charAt(0) == '['; + } + + public int getArrayDimension() { + if (!isArray()) { + throw new IllegalStateException("not an array"); + } + return countArrayDimension(this.desc); + } + + public TypeDescriptor getArrayType() { + if (!isArray()) { + throw new IllegalStateException("not an array"); + } + return new TypeDescriptor(this.desc.substring(getArrayDimension())); + } + + public boolean containsType() { + return isType() || (isArray() && getArrayType().containsType()); + } + + @Override + public boolean equals(Object other) { + return other instanceof TypeDescriptor && equals((TypeDescriptor) other); + } + + public boolean equals(TypeDescriptor other) { + return this.desc.equals(other.desc); + } + + @Override + public int hashCode() { + return this.desc.hashCode(); + } + + public TypeDescriptor remap(Function remapper) { + String desc = this.desc; + if (isType() || (isArray() && containsType())) { + String replacedName = remapper.apply(this.getTypeEntry().getFullName()); + if (replacedName != null) { + if (this.isType()) { + desc = "L" + replacedName + ";"; + } else { + desc = getArrayPrefix(this.getArrayDimension()) + "L" + replacedName + ";"; + } + } + } + return new TypeDescriptor(desc); + } + + private static String getArrayPrefix(int dimension) { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < dimension; i++) { + buf.append("["); + } + return buf.toString(); + } + + public int getSize() { + switch (desc.charAt(0)) { + case 'J': + case 'D': + if (desc.length() == 1) { + return 2; + } else { + return 1; + } + default: + return 1; + } + } + + @Override + public Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + return remap(name -> translator.translate(new ClassEntry(name)).getFullName()); + } + + public enum Primitive { + BYTE('B'), + CHARACTER('C'), + SHORT('S'), + INTEGER('I'), + LONG('J'), + FLOAT('F'), + DOUBLE('D'), + BOOLEAN('Z'); + + private static final Map lookup; + + static { + lookup = Maps.newTreeMap(); + for (Primitive val : values()) { + lookup.put(val.getCode(), val); + } + } + + private char code; + + Primitive(char code) { + this.code = code; + } + + public static Primitive get(char code) { + return lookup.get(code); + } + + public char getCode() { + return this.code; + } + } +} 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 new file mode 100644 index 00000000..69307651 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.Signature; + +import javax.annotation.Nullable; +import java.util.Arrays; + +public class ClassDefEntry extends ClassEntry implements DefEntry { + private final AccessFlags access; + private final Signature signature; + private final ClassEntry superClass; + private final ClassEntry[] interfaces; + + public ClassDefEntry(String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, ClassEntry[] interfaces) { + this(getOuterClass(className), getInnerName(className), signature, access, superClass, interfaces, null); + } + + public ClassDefEntry(ClassEntry parent, String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, ClassEntry[] interfaces) { + this(parent, className, signature, access, superClass, interfaces, null); + } + + public ClassDefEntry(ClassEntry parent, String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, + ClassEntry[] interfaces, String javadocs) { + super(parent, className, javadocs); + Preconditions.checkNotNull(signature, "Class signature cannot be null"); + Preconditions.checkNotNull(access, "Class access cannot be null"); + + this.signature = signature; + this.access = access; + this.superClass = superClass; + this.interfaces = interfaces != null ? interfaces : new ClassEntry[0]; + } + + public static ClassDefEntry parse(int access, String name, String signature, String superName, String[] interfaces) { + ClassEntry superClass = superName != null ? new ClassEntry(superName) : null; + ClassEntry[] interfaceClasses = Arrays.stream(interfaces).map(ClassEntry::new).toArray(ClassEntry[]::new); + return new ClassDefEntry(name, Signature.createSignature(signature), new AccessFlags(access), superClass, interfaceClasses); + } + + public Signature getSignature() { + return signature; + } + + @Override + public AccessFlags getAccess() { + return access; + } + + @Nullable + public ClassEntry getSuperClass() { + return superClass; + } + + public ClassEntry[] getInterfaces() { + return interfaces; + } + + @Override + public ClassDefEntry translate(Translator translator, @Nullable EntryMapping mapping) { + Signature translatedSignature = translator.translate(signature); + String translatedName = mapping != null ? mapping.getTargetName() : name; + AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access; + ClassEntry translatedSuper = translator.translate(superClass); + ClassEntry[] translatedInterfaces = Arrays.stream(interfaces).map(translator::translate).toArray(ClassEntry[]::new); + String docs = mapping != null ? mapping.getJavadoc() : null; + return new ClassDefEntry(parent, translatedName, translatedSignature, translatedAccess, translatedSuper, translatedInterfaces, docs); + } + + @Override + public ClassDefEntry withName(String name) { + return new ClassDefEntry(parent, name, signature, access, superClass, interfaces, javadocs); + } + + @Override + public ClassDefEntry withParent(ClassEntry parent) { + return new ClassDefEntry(parent, name, signature, access, superClass, interfaces, javadocs); + } +} 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 new file mode 100644 index 00000000..7d4b2ba4 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java @@ -0,0 +1,214 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.translation.representation.entry; + +import cuchaz.enigma.translation.mapping.IllegalNameException; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.NameValidator; +import cuchaz.enigma.translation.representation.TypeDescriptor; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.Objects; + +public class ClassEntry extends ParentedEntry implements Comparable { + private final String fullName; + + public ClassEntry(String className) { + this(getOuterClass(className), getInnerName(className), null); + } + + public ClassEntry(@Nullable ClassEntry parent, String className) { + this(parent, className, null); + } + + public ClassEntry(@Nullable ClassEntry parent, String className, @Nullable String javadocs) { + super(parent, className, javadocs); + if (parent != null) { + fullName = parent.getFullName() + "$" + name; + } else { + fullName = name; + } + + if (parent == null && className.indexOf('.') >= 0) { + throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className); + } + } + + @Override + public Class getParentType() { + return ClassEntry.class; + } + + @Override + public String getName() { + return this.name; + } + + public String getFullName() { + return fullName; + } + + @Override + public ClassEntry translate(Translator translator, @Nullable EntryMapping mapping) { + if (name.charAt(0) == '[') { + String translatedName = translator.translate(new TypeDescriptor(name)).toString(); + return new ClassEntry(parent, translatedName); + } + + String translatedName = mapping != null ? mapping.getTargetName() : name; + String docs = mapping != null ? mapping.getJavadoc() : null; + return new ClassEntry(parent, translatedName, docs); + } + + @Override + public ClassEntry getContainingClass() { + return this; + } + + @Override + public int hashCode() { + return fullName.hashCode(); + } + + @Override + public boolean equals(Object other) { + return other instanceof ClassEntry && equals((ClassEntry) other); + } + + public boolean equals(ClassEntry other) { + return other != null && Objects.equals(parent, other.parent) && this.name.equals(other.name); + } + + @Override + public boolean canConflictWith(Entry entry) { + return true; + } + + @Override + public void validateName(String name) throws IllegalNameException { + NameValidator.validateClassName(name); + } + + @Override + public ClassEntry withName(String name) { + return new ClassEntry(parent, name, javadocs); + } + + @Override + public ClassEntry withParent(ClassEntry parent) { + return new ClassEntry(parent, name, javadocs); + } + + @Override + public String toString() { + return getFullName(); + } + + public String getPackageName() { + return getPackageName(fullName); + } + + public String getSimpleName() { + int packagePos = name.lastIndexOf('/'); + if (packagePos > 0) { + return name.substring(packagePos + 1); + } + return name; + } + + public boolean isInnerClass() { + return parent != null; + } + + @Nullable + public ClassEntry getOuterClass() { + return parent; + } + + @Nonnull + public ClassEntry getOutermostClass() { + if (parent == null) { + return this; + } + return parent.getOutermostClass(); + } + + public ClassEntry buildClassEntry(List classChain) { + assert (classChain.contains(this)); + StringBuilder buf = new StringBuilder(); + for (ClassEntry chainEntry : classChain) { + if (buf.length() == 0) { + buf.append(chainEntry.getFullName()); + } else { + buf.append("$"); + buf.append(chainEntry.getSimpleName()); + } + + if (chainEntry == this) { + break; + } + } + return new ClassEntry(buf.toString()); + } + + public boolean isJre() { + String packageName = getPackageName(); + return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax")); + } + + public static String getPackageName(String name) { + int pos = name.lastIndexOf('/'); + if (pos > 0) { + return name.substring(0, pos); + } + return null; + } + + @Nullable + public static ClassEntry getOuterClass(String name) { + int index = name.lastIndexOf('$'); + if (index >= 0) { + return new ClassEntry(name.substring(0, index)); + } + return null; + } + + public static String getInnerName(String name) { + int innerClassPos = name.lastIndexOf('$'); + if (innerClassPos > 0) { + return name.substring(innerClassPos + 1); + } + return name; + } + + @Override + public String getSourceRemapName() { + ClassEntry outerClass = getOuterClass(); + if (outerClass != null) { + return outerClass.getSourceRemapName() + "." + name; + } + return getSimpleName(); + } + + @Override + public int compareTo(ClassEntry entry) { + String fullName = getFullName(); + String otherFullName = entry.getFullName(); + if (fullName.length() != otherFullName.length()) { + return fullName.length() - otherFullName.length(); + } + return fullName.compareTo(otherFullName); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java new file mode 100644 index 00000000..82536c73 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java @@ -0,0 +1,7 @@ +package cuchaz.enigma.translation.representation.entry; + +import cuchaz.enigma.translation.representation.AccessFlags; + +public interface DefEntry

> extends Entry

{ + AccessFlags getAccess(); +} 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 new file mode 100644 index 00000000..40bff31d --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.translation.representation.entry; + +import cuchaz.enigma.translation.mapping.IllegalNameException; +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.mapping.NameValidator; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +public interface Entry

> extends Translatable { + String getName(); + + String getJavadocs(); + + default String getSourceRemapName() { + return getName(); + } + + @Nullable + P getParent(); + + Class

getParentType(); + + Entry

withName(String name); + + Entry

withParent(P parent); + + boolean canConflictWith(Entry entry); + + @Nullable + default ClassEntry getContainingClass() { + P parent = getParent(); + if (parent == null) { + return null; + } + if (parent instanceof ClassEntry) { + return (ClassEntry) parent; + } + return parent.getContainingClass(); + } + + default List> getAncestry() { + P parent = getParent(); + List> entries = new ArrayList<>(); + if (parent != null) { + entries.addAll(parent.getAncestry()); + } + entries.add(this); + return entries; + } + + @Nullable + @SuppressWarnings("unchecked") + default > E findAncestor(Class type) { + List> ancestry = getAncestry(); + for (int i = ancestry.size() - 1; i >= 0; i--) { + Entry ancestor = ancestry.get(i); + if (type.isAssignableFrom(ancestor.getClass())) { + return (E) ancestor; + } + } + return null; + } + + @SuppressWarnings("unchecked") + default > Entry

replaceAncestor(E target, E replacement) { + if (replacement.equals(target)) { + return this; + } + + if (equals(target)) { + return (Entry

) replacement; + } + + P parent = getParent(); + if (parent == null) { + return this; + } + + return withParent((P) parent.replaceAncestor(target, replacement)); + } + + default void validateName(String name) throws IllegalNameException { + NameValidator.validateIdentifier(name); + } + + @SuppressWarnings("unchecked") + @Nullable + default > Entry castParent(Class parentType) { + if (parentType.equals(getParentType())) { + return (Entry) this; + } + return null; + } +} 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 new file mode 100644 index 00000000..f9282b28 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.Signature; +import cuchaz.enigma.translation.representation.TypeDescriptor; + +import javax.annotation.Nullable; + +public class FieldDefEntry extends FieldEntry implements DefEntry { + private final AccessFlags access; + private final Signature signature; + + public FieldDefEntry(ClassEntry owner, String name, TypeDescriptor desc, Signature signature, AccessFlags access) { + this(owner, name, desc, signature, access, null); + } + + public FieldDefEntry(ClassEntry owner, String name, TypeDescriptor desc, Signature signature, AccessFlags access, String javadocs) { + super(owner, name, desc, javadocs); + Preconditions.checkNotNull(access, "Field access cannot be null"); + Preconditions.checkNotNull(signature, "Field signature cannot be null"); + this.access = access; + this.signature = signature; + } + + public static FieldDefEntry parse(ClassEntry owner, int access, String name, String desc, String signature) { + return new FieldDefEntry(owner, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access), null); + } + + @Override + public AccessFlags getAccess() { + return access; + } + + public Signature getSignature() { + return signature; + } + + @Override + public FieldDefEntry translate(Translator translator, @Nullable 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; + return new FieldDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess, docs); + } + + @Override + public FieldDefEntry withName(String name) { + return new FieldDefEntry(parent, name, desc, signature, access, javadocs); + } + + @Override + public FieldDefEntry withParent(ClassEntry owner) { + return new FieldDefEntry(owner, this.name, this.desc, signature, access, javadocs); + } +} 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 new file mode 100644 index 00000000..0e478783 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.TypeDescriptor; + +import javax.annotation.Nullable; +import java.util.Objects; + +public class FieldEntry extends ParentedEntry implements Comparable { + protected final TypeDescriptor desc; + + public FieldEntry(ClassEntry parent, String name, TypeDescriptor desc) { + this(parent, name, desc, null); + } + + public FieldEntry(ClassEntry parent, String name, TypeDescriptor desc, String javadocs) { + super(parent, name, javadocs); + + Preconditions.checkNotNull(parent, "Owner cannot be null"); + Preconditions.checkNotNull(desc, "Field descriptor cannot be null"); + + this.desc = desc; + } + + public static FieldEntry parse(String owner, String name, String desc) { + return new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc), null); + } + + @Override + public Class getParentType() { + return ClassEntry.class; + } + + public TypeDescriptor getDesc() { + return this.desc; + } + + @Override + public FieldEntry withName(String name) { + return new FieldEntry(parent, name, desc, null); + } + + @Override + public FieldEntry withParent(ClassEntry parent) { + return new FieldEntry(parent, this.name, this.desc, null); + } + + @Override + protected FieldEntry translate(Translator translator, @Nullable EntryMapping mapping) { + String translatedName = mapping != null ? mapping.getTargetName() : name; + String docs = mapping != null ? mapping.getJavadoc() : null; + return new FieldEntry(parent, translatedName, translator.translate(desc), docs); + } + + @Override + public int hashCode() { + return Objects.hash(this.parent, this.name, this.desc); + } + + @Override + public boolean equals(Object other) { + return other instanceof FieldEntry && equals((FieldEntry) other); + } + + public boolean equals(FieldEntry other) { + return this.parent.equals(other.parent) && name.equals(other.name) && desc.equals(other.desc); + } + + @Override + public boolean canConflictWith(Entry entry) { + return entry instanceof FieldEntry && ((FieldEntry) entry).parent.equals(parent); + } + + @Override + public String toString() { + return this.parent.getFullName() + "." + this.name + ":" + this.desc; + } + + @Override + public int compareTo(FieldEntry entry) { + return (name + desc.toString()).compareTo(entry.name + entry.desc.toString()); + } +} 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 new file mode 100644 index 00000000..aad4236d --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java @@ -0,0 +1,51 @@ +package cuchaz.enigma.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.TypeDescriptor; + +import javax.annotation.Nullable; + +/** + * TypeDescriptor... + * Created by Thog + * 19/10/2016 + */ +public class LocalVariableDefEntry extends LocalVariableEntry { + protected final TypeDescriptor desc; + + public LocalVariableDefEntry(MethodEntry ownerEntry, int index, String name, boolean parameter, TypeDescriptor desc, String javadoc) { + super(ownerEntry, index, name, parameter, javadoc); + Preconditions.checkNotNull(desc, "Variable desc cannot be null"); + + this.desc = desc; + } + + public TypeDescriptor getDesc() { + return desc; + } + + @Override + public LocalVariableDefEntry translate(Translator translator, @Nullable EntryMapping mapping) { + TypeDescriptor translatedDesc = translator.translate(desc); + String translatedName = mapping != null ? mapping.getTargetName() : name; + String javadoc = mapping != null ? mapping.getJavadoc() : javadocs; + return new LocalVariableDefEntry(parent, index, translatedName, parameter, translatedDesc, javadoc); + } + + @Override + public LocalVariableDefEntry withName(String name) { + return new LocalVariableDefEntry(parent, index, name, parameter, desc, javadocs); + } + + @Override + public LocalVariableDefEntry withParent(MethodEntry entry) { + return new LocalVariableDefEntry(entry, index, name, parameter, desc, javadocs); + } + + @Override + public String toString() { + return this.parent + "(" + this.index + ":" + this.name + ":" + this.desc + ")"; + } +} 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 new file mode 100644 index 00000000..3f98f030 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java @@ -0,0 +1,93 @@ +package cuchaz.enigma.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; + +import javax.annotation.Nullable; +import java.util.Objects; + +/** + * TypeDescriptor... + * Created by Thog + * 19/10/2016 + */ +public class LocalVariableEntry extends ParentedEntry implements Comparable { + + protected final int index; + protected final boolean parameter; + + public LocalVariableEntry(MethodEntry parent, int index, String name, boolean parameter, String javadoc) { + super(parent, name, javadoc); + + Preconditions.checkNotNull(parent, "Variable owner cannot be null"); + Preconditions.checkArgument(index >= 0, "Index must be positive"); + + this.index = index; + this.parameter = parameter; + } + + @Override + public Class getParentType() { + return MethodEntry.class; + } + + public boolean isArgument() { + return this.parameter; + } + + public int getIndex() { + return index; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public LocalVariableEntry translate(Translator translator, @Nullable EntryMapping mapping) { + String translatedName = mapping != null ? mapping.getTargetName() : name; + String javadoc = mapping != null ? mapping.getJavadoc() : null; + return new LocalVariableEntry(parent, index, translatedName, parameter, javadoc); + } + + @Override + public LocalVariableEntry withName(String name) { + return new LocalVariableEntry(parent, index, name, parameter, javadocs); + } + + @Override + public LocalVariableEntry withParent(MethodEntry parent) { + return new LocalVariableEntry(parent, index, name, parameter, javadocs); + } + + @Override + public int hashCode() { + return Objects.hash(this.parent, this.index); + } + + @Override + public boolean equals(Object other) { + return other instanceof LocalVariableEntry && equals((LocalVariableEntry) other); + } + + public boolean equals(LocalVariableEntry other) { + return this.parent.equals(other.parent) && this.index == other.index; + } + + @Override + public boolean canConflictWith(Entry entry) { + return entry instanceof LocalVariableEntry && ((LocalVariableEntry) entry).parent.equals(parent); + } + + @Override + public String toString() { + return this.parent + "(" + this.index + ":" + this.name + ")"; + } + + @Override + public int compareTo(LocalVariableEntry entry) { + return Integer.compare(index, entry.index); + } +} 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 new file mode 100644 index 00000000..4e75a5c8 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.Signature; + +import javax.annotation.Nullable; + +public class MethodDefEntry extends MethodEntry implements DefEntry { + private final AccessFlags access; + private final Signature signature; + + public MethodDefEntry(ClassEntry owner, String name, MethodDescriptor descriptor, Signature signature, AccessFlags access) { + this(owner, name, descriptor, signature, access, null); + } + + public MethodDefEntry(ClassEntry owner, String name, MethodDescriptor descriptor, Signature signature, AccessFlags access, String docs) { + super(owner, name, descriptor, docs); + Preconditions.checkNotNull(access, "Method access cannot be null"); + Preconditions.checkNotNull(signature, "Method signature cannot be null"); + this.access = access; + this.signature = signature; + } + + public static MethodDefEntry parse(ClassEntry owner, int access, String name, String desc, String signature) { + return new MethodDefEntry(owner, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access), null); + } + + @Override + public AccessFlags getAccess() { + return access; + } + + public Signature getSignature() { + return signature; + } + + @Override + public MethodDefEntry translate(Translator translator, @Nullable 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; + return new MethodDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess, docs); + } + + @Override + public MethodDefEntry withName(String name) { + return new MethodDefEntry(parent, name, descriptor, signature, access, javadocs); + } + + @Override + public MethodDefEntry withParent(ClassEntry parent) { + return new MethodDefEntry(new ClassEntry(parent.getFullName()), name, descriptor, signature, access, javadocs); + } +} 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 new file mode 100644 index 00000000..086a5c1f --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.MethodDescriptor; + +import javax.annotation.Nullable; +import java.util.Objects; + +public class MethodEntry extends ParentedEntry implements Comparable { + + protected final MethodDescriptor descriptor; + + public MethodEntry(ClassEntry parent, String name, MethodDescriptor descriptor) { + this(parent, name, descriptor, null); + } + + public MethodEntry(ClassEntry parent, String name, MethodDescriptor descriptor, String javadocs) { + super(parent, name, javadocs); + + Preconditions.checkNotNull(parent, "Parent cannot be null"); + Preconditions.checkNotNull(descriptor, "Method descriptor cannot be null"); + + this.descriptor = descriptor; + } + + public static MethodEntry parse(String owner, String name, String desc) { + return new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc), null); + } + + @Override + public Class getParentType() { + return ClassEntry.class; + } + + public MethodDescriptor getDesc() { + return this.descriptor; + } + + public boolean isConstructor() { + return name.equals("") || name.equals(""); + } + + @Override + public MethodEntry translate(Translator translator, @Nullable EntryMapping mapping) { + String translatedName = mapping != null ? mapping.getTargetName() : name; + String docs = mapping != null ? mapping.getJavadoc() : null; + return new MethodEntry(parent, translatedName, translator.translate(descriptor), docs); + } + + @Override + public MethodEntry withName(String name) { + return new MethodEntry(parent, name, descriptor, javadocs); + } + + @Override + public MethodEntry withParent(ClassEntry parent) { + return new MethodEntry(new ClassEntry(parent.getFullName()), name, descriptor, javadocs); + } + + @Override + public int hashCode() { + return Objects.hash(this.parent, this.name, this.descriptor); + } + + @Override + public boolean equals(Object other) { + return other instanceof MethodEntry && equals((MethodEntry) other); + } + + public boolean equals(MethodEntry other) { + return this.parent.equals(other.getParent()) && this.name.equals(other.getName()) && this.descriptor.equals(other.getDesc()); + } + + @Override + public boolean canConflictWith(Entry entry) { + if (entry instanceof MethodEntry) { + MethodEntry methodEntry = (MethodEntry) entry; + return methodEntry.parent.equals(parent) && methodEntry.descriptor.canConflictWith(descriptor); + } + return false; + } + + @Override + public String toString() { + return this.parent.getFullName() + "." + this.name + this.descriptor; + } + + @Override + public int compareTo(MethodEntry entry) { + return (name + descriptor.toString()).compareTo(entry.name + entry.descriptor.toString()); + } +} 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 new file mode 100644 index 00000000..95be22ce --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; + +import javax.annotation.Nullable; + +public abstract class ParentedEntry

> implements Entry

{ + protected final P parent; + protected final String name; + protected final @Nullable String javadocs; + + protected ParentedEntry(P parent, String name, String javadocs) { + this.parent = parent; + this.name = name; + this.javadocs = javadocs; + + Preconditions.checkNotNull(name, "Name cannot be null"); + } + + @Override + public abstract ParentedEntry

withParent(P parent); + + @Override + public abstract ParentedEntry

withName(String name); + + protected abstract ParentedEntry

translate(Translator translator, @Nullable EntryMapping mapping); + + @Override + public String getName() { + return name; + } + + @Override + @Nullable + public P getParent() { + return parent; + } + + @Nullable + @Override + public String getJavadocs() { + return javadocs; + } + + @Override + public ParentedEntry

translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + P parent = getParent(); + EntryMapping mapping = resolveMapping(resolver, mappings); + if (parent == null) { + return translate(translator, mapping); + } + P translatedParent = translator.translate(parent); + return withParent(translatedParent).translate(translator, mapping); + } + + private EntryMapping resolveMapping(EntryResolver resolver, EntryMap mappings) { + for (ParentedEntry

entry : resolver.resolveEntry(this, ResolutionStrategy.RESOLVE_ROOT)) { + EntryMapping mapping = mappings.get(entry); + if (mapping != null) { + return mapping; + } + } + return null; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/utils/I18n.java b/enigma/src/main/java/cuchaz/enigma/utils/I18n.java new file mode 100644 index 00000000..e18532b6 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/utils/I18n.java @@ -0,0 +1,95 @@ +package cuchaz.enigma.utils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.google.common.reflect.ClassPath; +import com.google.common.reflect.ClassPath.ResourceInfo; +import com.google.gson.Gson; + +public class I18n { + public static final String DEFAULT_LANGUAGE = "en_us"; + private static final Gson GSON = new Gson(); + private static Map translations = Maps.newHashMap(); + private static Map defaultTranslations = Maps.newHashMap(); + private static Map languageNames = Maps.newHashMap(); + + static { + defaultTranslations = load(DEFAULT_LANGUAGE); + translations = defaultTranslations; + } + + @SuppressWarnings("unchecked") + public static Map load(String language) { + try (InputStream inputStream = I18n.class.getResourceAsStream("/lang/" + language + ".json")) { + if (inputStream != null) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + return GSON.fromJson(reader, Map.class); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + return Collections.emptyMap(); + } + + public static String translate(String key) { + String value = translations.get(key); + if (value != null) { + return value; + } + value = defaultTranslations.get(key); + if (value != null) { + return value; + } + return key; + } + + public static String getLanguageName(String language) { + return languageNames.get(language); + } + + public static void setLanguage(String language) { + translations = load(language); + } + + public static ArrayList getAvailableLanguages() { + ArrayList list = new ArrayList(); + + try { + ImmutableList resources = ClassPath.from(Thread.currentThread().getContextClassLoader()).getResources().asList(); + Stream dirStream = resources.stream(); + dirStream.forEach(context -> { + String file = context.getResourceName(); + if (file.startsWith("lang/") && file.endsWith(".json")) { + String fileName = file.substring(5, file.length() - 5); + list.add(fileName); + loadLanguageName(fileName); + } + }); + } catch (IOException e) { + e.printStackTrace(); + } + return list; + } + + private static void loadLanguageName(String fileName) { + try (InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream("lang/" + fileName + ".json")) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + Map map = GSON.fromJson(reader, Map.class); + languageNames.put(fileName, map.get("language").toString()); + } + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/utils/Pair.java b/enigma/src/main/java/cuchaz/enigma/utils/Pair.java new file mode 100644 index 00000000..bf02ceff --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/utils/Pair.java @@ -0,0 +1,26 @@ +package cuchaz.enigma.utils; + +import java.util.Objects; + +public class Pair { + public final A a; + public final B b; + + public Pair(A a, B b) { + this.a = a; + this.b = b; + } + + @Override + public int hashCode() { + return Objects.hashCode(a) * 31 + + Objects.hashCode(b); + } + + @Override + public boolean equals(Object o) { + return o instanceof Pair && + Objects.equals(a, ((Pair) o).a) && + Objects.equals(b, ((Pair) o).b); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/utils/Utils.java b/enigma/src/main/java/cuchaz/enigma/utils/Utils.java new file mode 100644 index 00000000..26640993 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/utils/Utils.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.utils; + +import com.google.common.io.CharStreams; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class Utils { + public static String readStreamToString(InputStream in) throws IOException { + return CharStreams.toString(new InputStreamReader(in, StandardCharsets.UTF_8)); + } + + public static String readResourceToString(String path) throws IOException { + InputStream in = Utils.class.getResourceAsStream(path); + if (in == null) { + throw new IllegalArgumentException("Resource not found! " + path); + } + return readStreamToString(in); + } + + public static void delete(Path path) throws IOException { + if (Files.exists(path)) { + for (Path p : Files.walk(path).sorted(Comparator.reverseOrder()).collect(Collectors.toList())) { + Files.delete(p); + } + } + } + + public static byte[] zipSha1(Path path) throws IOException { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + // Algorithm guaranteed to be supported + throw new RuntimeException(e); + } + try (ZipFile zip = new ZipFile(path.toFile())) { + List entries = Collections.list(zip.entries()); + // only compare classes (some implementations may not generate directory entries) + entries.removeIf(entry -> !entry.getName().toLowerCase(Locale.ROOT).endsWith(".class")); + // different implementations may add zip entries in a different order + entries.sort(Comparator.comparing(ZipEntry::getName)); + byte[] buffer = new byte[8192]; + for (ZipEntry entry : entries) { + digest.update(entry.getName().getBytes(StandardCharsets.UTF_8)); + try (InputStream in = zip.getInputStream(entry)) { + int n; + while ((n = in.read(buffer)) != -1) { + digest.update(buffer, 0, n); + } + } + } + } + return digest.digest(); + } + + public static boolean isBlank(String input) { + if (input == null) { + return true; + } + for (int i = 0; i < input.length(); i++) { + if (!Character.isWhitespace(input.charAt(i))) { + return false; + } + } + return true; + } +} diff --git a/enigma/src/main/resources/META-INF/services/cuchaz.enigma.api.EnigmaPlugin b/enigma/src/main/resources/META-INF/services/cuchaz.enigma.api.EnigmaPlugin new file mode 100644 index 00000000..136a3e78 --- /dev/null +++ b/enigma/src/main/resources/META-INF/services/cuchaz.enigma.api.EnigmaPlugin @@ -0,0 +1 @@ +cuchaz.enigma.analysis.BuiltinPlugin diff --git a/enigma/src/main/resources/lang/en_us.json b/enigma/src/main/resources/lang/en_us.json new file mode 100644 index 00000000..dbf4b935 --- /dev/null +++ b/enigma/src/main/resources/lang/en_us.json @@ -0,0 +1,164 @@ +{ + "language": "English", + + "mapping_format.enigma_file": "Enigma File", + "mapping_format.enigma_directory": "Enigma Directory", + "mapping_format.enigma_zip": "Enigma ZIP", + "mapping_format.tiny_v2": "Tiny v2", + "mapping_format.tiny_file": "Tiny File", + "mapping_format.srg_file": "SRG File", + "mapping_format.proguard": "Proguard", + "type.methods": "Methods", + "type.fields": "Fields", + "type.parameters": "Parameters", + "type.classes": "Classes", + + "menu.file": "File", + "menu.file.jar.open": "Open Jar...", + "menu.file.jar.close": "Close Jar", + "menu.file.mappings.open": "Open Mappings...", + "menu.file.mappings.save": "Save Mappings", + "menu.file.mappings.save_as": "Save Mappings As...", + "menu.file.mappings.close": "Close Mappings", + "menu.file.mappings.drop": "Drop Invalid Mappings", + "menu.file.export.source": "Export Source...", + "menu.file.export.jar": "Export Jar...", + "menu.file.stats": "Mapping Stats...", + "menu.file.stats.title": "Choose Included Members", + "menu.file.stats.generate": "Generate Stats", + "menu.file.exit": "Exit", + "menu.decompiler": "Decompiler", + "menu.view": "View", + "menu.view.themes": "Themes", + "menu.view.themes.default": "Default", + "menu.view.themes.darcula": "Darcula", + "menu.view.themes.system": "System", + "menu.view.themes.none": "None (JVM Default)", + "menu.view.languages": "Languages", + "menu.view.scale": "Scale", + "menu.view.scale.custom": "Custom...", + "menu.view.scale.custom.title": "Custom Scale", + "menu.view.change.title": "Changes", + "menu.view.change.summary": "Changes will be applied after the next restart.", + "menu.view.change.ok": "Ok", + "menu.view.search": "Search", + "menu.collab": "Collab", + "menu.collab.connect": "Connect to server", + "menu.collab.connect.error": "Error connecting to server", + "menu.collab.disconnect": "Disconnect", + "menu.collab.server.start": "Start server", + "menu.collab.server.start.error": "Error starting server", + "menu.collab.server.stop": "Stop server", + "menu.help": "Help", + "menu.help.about": "About", + "menu.help.about.title": "%s - About", + "menu.help.about.ok": "Ok", + "menu.help.github": "Github Page", + + "popup_menu.rename": "Rename", + "popup_menu.javadoc": "Edit Javadoc", + "popup_menu.inheritance": "Show Inheritance", + "popup_menu.implementations": "Show Implementations", + "popup_menu.calls": "Show Calls (All Implementations)", + "popup_menu.calls.specific": "Show Calls (Specific)", + "popup_menu.declaration": "Go to Declaration", + "popup_menu.back": "Go back", + "popup_menu.forward": "Go forward", + "popup_menu.mark_deobfuscated": "Mark as deobfuscated", + "popup_menu.reset_obfuscated": "Reset to obfuscated", + "popup_menu.zoom.in": "Zoom in", + "popup_menu.zoom.out": "Zoom out", + "popup_menu.zoom.reset": "Reset zoom", + + "info_panel.classes.obfuscated": "Obfuscated Classes", + "info_panel.classes.deobfuscated": "De-obfuscated Classes", + "info_panel.identifier": "Identifier Info", + "info_panel.identifier.none": "No identifier selected", + "info_panel.identifier.variable": "Variable", + "info_panel.identifier.field": "Field", + "info_panel.identifier.method": "Method", + "info_panel.identifier.constructor": "Constructor", + "info_panel.identifier.class": "Class", + "info_panel.identifier.type_descriptor": "TypeDescriptor", + "info_panel.identifier.method_descriptor": "MethodDescriptor", + "info_panel.identifier.modifier": "Modifier", + "info_panel.identifier.index": "Index", + "info_panel.editor.class.decompiling": "(decompiling...)", + "info_panel.editor.class.not_found": "Unable to find class:", + "info_panel.tree.inheritance": "Inheritance", + "info_panel.tree.implementations": "Implementations", + "info_panel.tree.calls": "Call Graph", + + "log_panel.messages": "Messages", + "log_panel.users": "Users", + + "progress.operation": "%s - Operation in progress", + "progress.jar.indexing": "Indexing jar", + "progress.jar.indexing.entries": "Entries...", + "progress.jar.indexing.references": "Entry references...", + "progress.jar.indexing.methods": "Bridge methods...", + "progress.jar.indexing.process": "Processing...", + "progress.jar.writing": "Writing jar...", + "progress.sources.writing": "Writing sources...", + "progress.classes.deobfuscating": "Deobfuscating classes...", + "progress.classes.decompiling": "Decompiling classes...", + "progress.mappings.enigma_file.loading": "Loading mapping file", + "progress.mappings.enigma_file.done": "Done!", + "progress.mappings.enigma_file.writing": "Writing classes", + "progress.mappings.enigma_directory.loading": "Loading mapping files", + "progress.mappings.enigma_directory.writing": "Writing classes", + "progress.mappings.tiny_file.loading": "Loading mapping file", + "progress.mappings.tiny_v2.loading": "Loading mapping file", + "progress.mappings.srg_file.generating": "Generating mappings", + "progress.mappings.srg_file.writing": "Writing mappings", + "progress.stats": "Generating stats", + "progress.stats.data": "Generating data", + + "javadocs.edit": "Edit Javadocs", + "javadocs.instruction": "Edit javadocs here.", + "javadocs.cancel": "Cancel", + "javadocs.save": "Save", + + "prompt.close.title": "Save your changes?", + "prompt.close.summary": "Your mappings have not been saved yet. Do you want to save?", + "prompt.close.save": "Save and close", + "prompt.close.discard": "Discard changes", + "prompt.close.cancel": "Cancel", + "prompt.open": "Open", + "prompt.cancel": "Cancel", + "prompt.connect.title": "Connect to server", + "prompt.connect.username": "Username", + "prompt.connect.ip": "IP", + "prompt.port": "Port", + "prompt.port.nan": "Port is not a number", + "prompt.port.invalid": "Port is out of range, should be between 0-65535", + "prompt.password": "Password", + "prompt.password.too_long": "Password is too long, it must be at most 255 characters.", + "prompt.create_server.title": "Create server", + + "disconnect.disconnected": "Disconnected", + "disconnect.server_closed": "Server closed", + "disconnect.wrong_jar": "Jar checksums don't match (you have the wrong jar)!", + "disconnect.wrong_password": "Incorrect password", + "disconnect.username_taken": "Username is taken", + + "message.chat.text": "%s: %s", + "message.connect.text": "[+] %s", + "message.disconnect.text": "[-] %s", + "message.edit_docs.text": "%s edited docs for %s", + "message.mark_deobf.text": "%s marked %s as deobfuscated", + "message.remove_mapping.text": "%s removed mappings for %s", + "message.rename.text": "%s renamed %s to %s", + + "status.disconnected": "Disconnected.", + "status.connected": "Connected.", + "status.connected_user_count": "Connected (%d users).", + "status.ready": "Ready.", + + "crash.title": "%s - Crash Report", + "crash.summary": "%s has crashed! =(", + "crash.export": "Export", + "crash.ignore": "Ignore", + "crash.exit": "Exit", + "crash.exit.warning": "If you choose exit, you will lose any unsaved work." +} diff --git a/enigma/src/main/resources/lang/fr_fr.json b/enigma/src/main/resources/lang/fr_fr.json new file mode 100644 index 00000000..d169b9a0 --- /dev/null +++ b/enigma/src/main/resources/lang/fr_fr.json @@ -0,0 +1,164 @@ +{ + "language": "Français", + + "mapping_format.enigma_file": "Fichier Enigma", + "mapping_format.enigma_directory": "Répertoire Enigma", + "mapping_format.enigma_zip": "ZIP Enigma", + "mapping_format.tiny_v2": "Tiny v2", + "mapping_format.tiny_file": "Fichier Tiny", + "mapping_format.srg_file": "Fichier SRG", + "mapping_format.proguard": "Proguard", + "type.methods": "Méthodes", + "type.fields": "Champs", + "type.parameters": "Paramètres", + "type.classes": "Classes", + + "menu.file": "Fichier", + "menu.file.jar.open": "Ouvrir le jar...", + "menu.file.jar.close": "Fermer le jar", + "menu.file.mappings.open": "Ouvrir les mappings...", + "menu.file.mappings.save": "Enregistrer les mappings", + "menu.file.mappings.save_as": "Enregistrer les mappings sous...", + "menu.file.mappings.close": "Fermer les mappings", + "menu.file.mappings.drop": "Supprimer les mappings invalides", + "menu.file.export.source": "Exporter la source...", + "menu.file.export.jar": "Exporter le jar...", + "menu.file.stats": "Statistiques des mappings...", + "menu.file.stats.title": "Choisir les membres inclus", + "menu.file.stats.generate": "Générer les statistiques", + "menu.file.exit": "Quitter", + "menu.decompiler": "Décompilateur", + "menu.view": "Affichage", + "menu.view.themes": "Thèmes", + "menu.view.themes.default": "Par défaut", + "menu.view.themes.darcula": "Darcula", + "menu.view.themes.system": "Système", + "menu.view.themes.none": "Aucun (JVM par défaut)", + "menu.view.languages": "Langues", + "menu.view.scale": "Échelle", + "menu.view.scale.custom": "Personnalisée...", + "menu.view.scale.custom.title": "Échelle personnalisée", + "menu.view.change.title": "Modifications", + "menu.view.change.summary": "Les modifications seront appliquées lors du prochain redémarrage.", + "menu.view.change.ok": "Ok", + "menu.view.search": "Rechercher", + "menu.collab": "Collab", + "menu.collab.connect": "Se connecter à un serveur", + "menu.collab.connect.error": "Erreur lors de la connexion au serveur", + "menu.collab.disconnect": "Se déconnecter", + "menu.collab.server.start": "Démarrer le serveur", + "menu.collab.server.start.error": "Erreur lors du démarrage du serveur", + "menu.collab.server.stop": "Arrêter le serveur", + "menu.help": "Aide", + "menu.help.about": "À propos", + "menu.help.about.title": "%s - À propos", + "menu.help.about.ok": "Ok", + "menu.help.github": "Page Github", + + "popup_menu.rename": "Renommer", + "popup_menu.javadoc": "Éditer le Javadoc", + "popup_menu.inheritance": "Afficher l'héritage", + "popup_menu.implementations": "Afficher les implémentations", + "popup_menu.calls": "Afficher les appels (tous)", + "popup_menu.calls.specific": "Afficher les appels (spécifiques)", + "popup_menu.declaration": "Aller à la déclaration", + "popup_menu.back": "Annuler", + "popup_menu.forward": "Restaurer", + "popup_menu.mark_deobfuscated": "Marquer comme déobfusqué", + "popup_menu.reset_obfuscated": "Réinitialiser à obfusqué", + "popup_menu.zoom.in": "Zoomer", + "popup_menu.zoom.out": "Dézoomer", + "popup_menu.zoom.reset": "Réinitialiser le zoom", + + "info_panel.classes.obfuscated": "Classes obfusquées", + "info_panel.classes.deobfuscated": "Classes déobfusquées", + "info_panel.identifier": "Informations sur l'identifiant", + "info_panel.identifier.none": "Aucun identifiant sélectionné", + "info_panel.identifier.variable": "Variable", + "info_panel.identifier.field": "Champ", + "info_panel.identifier.method": "Méthode", + "info_panel.identifier.constructor": "Constructeur", + "info_panel.identifier.class": "Classe", + "info_panel.identifier.type_descriptor": "Descripteur de type", + "info_panel.identifier.method_descriptor": "Descripteur de méthode", + "info_panel.identifier.modifier": "Modificateur", + "info_panel.identifier.index": "Index", + "info_panel.editor.class.decompiling": "(décompilation...)", + "info_panel.editor.class.not_found": "Impossible de trouver la classe :", + "info_panel.tree.inheritance": "Héritage", + "info_panel.tree.implementations": "Implémentations", + "info_panel.tree.calls": "Graphique des appels", + + "log_panel.messages": "Messages", + "log_panel.users": "Utilisateurs", + + "progress.operation": "%s - Opération en cours", + "progress.jar.indexing": "Indexation du jar", + "progress.jar.indexing.entries": "Entrées...", + "progress.jar.indexing.references": "Références des entrées...", + "progress.jar.indexing.methods": "Mise en place des méthodes...", + "progress.jar.indexing.process": "Traitement...", + "progress.jar.writing": "Écriture du jar...", + "progress.sources.writing": "Écriture des sources...", + "progress.classes.deobfuscating": "Déobfuscation des classes...", + "progress.classes.decompiling": "Décompilation des classes...", + "progress.mappings.enigma_file.loading": "Chargement du fichier de mappings", + "progress.mappings.enigma_file.done": "Terminé !", + "progress.mappings.enigma_file.writing": "Écriture des classes", + "progress.mappings.enigma_directory.loading": "Chargement des fichiers de mappings", + "progress.mappings.enigma_directory.writing": "Écriture des classes", + "progress.mappings.tiny_file.loading": "Chargement du fichier de mappings", + "progress.mappings.tiny_v2.loading": "Chargement du fichier de mappings", + "progress.mappings.srg_file.generating": "Génération des mappings", + "progress.mappings.srg_file.writing": "Écriture des mappings", + "progress.stats": "Génération des statistiques", + "progress.stats.data": "Génération des données", + + "javadocs.edit": "Éditer les Javadocs", + "javadocs.instruction": "Éditer les Javadocs ici.", + "javadocs.cancel": "Annuler", + "javadocs.save": "Enregistrer", + + "prompt.close.title": "Enregistrer les modifications ?", + "prompt.close.summary": "Vos mappings n'ont pas encore été enregistrés. Souhaitez-vous enregistrer ?", + "prompt.close.save": "Enregistrer et fermer", + "prompt.close.discard": "Annuler les modifications", + "prompt.close.cancel": "Annuler", + "prompt.open": "Ouvrir", + "prompt.cancel": "Annuler", + "prompt.connect.title": "Se connecter à un serveur", + "prompt.connect.username": "Nom d'utilisateur", + "prompt.connect.ip": "IP", + "prompt.port": "Port", + "prompt.port.nan": "Le port n'est pas un nombre", + "prompt.port.invalid": "Le port est hors de portée. Il doit être compris entre 0 et 65535.", + "prompt.password": "Mot de passe", + "prompt.password.too_long": "Le mot de passe est trop long. Il ne doit pas dépasser 255 caractères.", + "prompt.create_server.title": "Créer un serveur", + + "disconnect.disconnected": "Déconnecté", + "disconnect.server_closed": "Serveur fermé", + "disconnect.wrong_jar": "Les sommes de contrôle du jar ne correspondent pas (vous avez le mauvais jar) !", + "disconnect.wrong_password": "Mot de passe incorrect", + "disconnect.username_taken": "Le nom d'utilisateur est déjà pris", + + "message.chat.text": "%s : %s", + "message.connect.text": "[+] %s", + "message.disconnect.text": "[-] %s", + "message.edit_docs.text": "%s a édité les javadocs de %s", + "message.mark_deobf.text": "%s a marqué %s comme déobfusqué", + "message.remove_mapping.text": "%s a supprimé les mappings de %s", + "message.rename.text": "%s a renommé %s en %s", + + "status.disconnected": "Déconnecté.", + "status.connected": "Connecté.", + "status.connected_user_count": "Connecté (%d utilisateurs).", + "status.ready": "Prêt.", + + "crash.title": "%s - Rapport de plantage", + "crash.summary": "%s a planté ! =(", + "crash.export": "Exporter", + "crash.ignore": "Ignorer", + "crash.exit": "Quitter", + "crash.exit.warning": "Si vous choisissez Quitter, vous perdrez tout travail non sauvegardé." +} diff --git a/enigma/src/main/resources/lang/zh_cn.json b/enigma/src/main/resources/lang/zh_cn.json new file mode 100644 index 00000000..f3f503aa --- /dev/null +++ b/enigma/src/main/resources/lang/zh_cn.json @@ -0,0 +1,118 @@ +{ + "language": "Chinese", + + "mapping_format.enigma_file": "Enigma 文件", + "mapping_format.enigma_directory": "Enigma 目录", + "mapping_format.enigma_zip": "Enigma ZIP", + "mapping_format.tiny_v2": "Tiny v2", + "mapping_format.tiny_file": "Tiny File", + "mapping_format.srg_file": "SRG File", + "mapping_format.proguard": "Proguard", + "type.methods": "方法", + "type.fields": "字段", + "type.parameters": "参数", + "type.classes": "类", + + "menu.file": "文件", + "menu.file.jar.open": "打开 Jar...", + "menu.file.jar.close": "关闭 Jar", + "menu.file.mappings.open": "打开映射...", + "menu.file.mappings.save": "保存映射", + "menu.file.mappings.save_as": "将映射另存为...", + "menu.file.mappings.close": "关闭映射", + "menu.file.mappings.drop": "删除无效映射", + "menu.file.export.source": "导出源码...", + "menu.file.export.jar": "导出Jar...", + "menu.file.stats": "映射统计范围...", + "menu.file.stats.title": "选择包括的成员", + "menu.file.stats.generate": "生成统计范围", + "menu.file.exit": "退出", + "menu.decompiler": "反编译", + "menu.view": "查看", + "menu.view.themes": "主题", + "menu.view.themes.default": "Default", + "menu.view.themes.darcula": "Darcula", + "menu.view.themes.system": "System", + "menu.view.themes.none": "None (JVM Default)", + "menu.view.languages": "语言", + "menu.view.languages.title": "更改语言", + "menu.view.languages.summary": "新语言将在下次重新启动后应用.", + "menu.view.languages.ok": "确定", + "menu.view.search": "搜索", + "menu.help": "帮助", + "menu.help.about": "关于", + "menu.help.about.title": "%s - 关于", + "menu.help.about.ok": "确定", + "menu.help.github": "GitHub 页面", + + "popup_menu.rename": "改名", + "popup_menu.javadoc": "编辑注释", + "popup_menu.inheritance": "显示继承", + "popup_menu.implementations": "显示实现", + "popup_menu.calls": "显示 Calls", + "popup_menu.calls.specific": "显示 Calls (具体)", + "popup_menu.declaration": "Go to Declaration", + "popup_menu.back": "Go back", + "popup_menu.forward": "Go forward", + "popup_menu.mark_deobfuscated": "标记为反混淆", + "popup_menu.reset_obfuscated": "重置混淆", + + "info_panel.classes.obfuscated": "混淆类", + "info_panel.classes.deobfuscated": "反混淆类", + "info_panel.identifier": "标识符信息", + "info_panel.identifier.none": "未选择标识符", + "info_panel.identifier.variable": "变量", + "info_panel.identifier.field": "字段", + "info_panel.identifier.method": "方法", + "info_panel.identifier.constructor": "构造器", + "info_panel.identifier.class": "类", + "info_panel.identifier.type_descriptor": "类型描述符", + "info_panel.identifier.method_descriptor": "方法描述符", + "info_panel.identifier.modifier": "修饰语", + "info_panel.identifier.index": "索引", + "info_panel.editor.class.decompiling": "(反编译中...)", + "info_panel.editor.class.not_found": "找不到类:", + "info_panel.tree.inheritance": "继承", + "info_panel.tree.implementations": "实现", + "info_panel.tree.calls": "调用图", + + "progress.operation": "%s - 进行中", + "progress.jar.indexing": "索引jar", + "progress.jar.indexing.entries": "条目...", + "progress.jar.indexing.references": "条目引用...", + "progress.jar.indexing.methods": "桥接方法...", + "progress.jar.indexing.process": "处理中...", + "progress.jar.writing": "写出jar中...", + "progress.sources.writing": "写出源码中...", + "progress.classes.deobfuscating": "反混淆类中...", + "progress.classes.decompiling": "反编译类中...", + "progress.mappings.enigma_file.loading": "加载映射文件", + "progress.mappings.enigma_file.done": "完成!", + "progress.mappings.enigma_file.writing": "写出类", + "progress.mappings.enigma_directory.loading": "加载映射文件", + "progress.mappings.enigma_directory.writing": "写出类", + "progress.mappings.tiny_file.loading": "加载映射文件", + "progress.mappings.tiny_v2.loading": "加载映射文件", + "progress.mappings.srg_file.generating": "生成映射", + "progress.mappings.srg_file.writing": "写出映射", + "progress.stats": "生成统计范围", + "progress.stats.data": "生成数据", + + "javadocs.edit": "编辑注释", + "javadocs.instruction": "在此处编辑编辑注释.", + "javadocs.cancel": "取消", + "javadocs.save": "保存", + + "prompt.close.title": "保存更改?", + "prompt.close.summary": "您的映射尚未保存。你想保存吗?", + "prompt.close.save": "保存并关闭", + "prompt.close.discard": "放弃更改", + "prompt.close.cancel": "取消", + + "crash.title": "%s - 崩溃报告", + "crash.summary": "%s 已经崩溃! =(", + "crash.export": "输出", + "crash.ignore": "忽略", + "crash.exit": "退出", + "crash.exit.warning": "如果选择退出,将丢失所有未保存的工作." +} diff --git a/enigma/src/main/resources/profile.json b/enigma/src/main/resources/profile.json new file mode 100644 index 00000000..e1af4cdb --- /dev/null +++ b/enigma/src/main/resources/profile.json @@ -0,0 +1,20 @@ +{ + "services": { + "jar_indexer": [ + { + "id": "enigma:enum_initializer_indexer" + }, + { + "id": "enigma:specialized_bridge_method_indexer" + } + ], + "name_proposal": [ + { + "id": "enigma:enum_name_proposer" + }, + { + "id": "enigma:specialized_method_name_proposer" + } + ] + } +} \ No newline at end of file diff --git a/enigma/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java b/enigma/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java new file mode 100644 index 00000000..1dc9748b --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma; + +import cuchaz.enigma.analysis.ClassCache; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.analysis.index.PackageVisibilityIndex; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import org.junit.Test; + +import java.nio.file.Paths; + +import static cuchaz.enigma.TestEntryFactory.newClass; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; + +public class PackageVisibilityIndexTest { + + private static final ClassEntry KEEP = newClass("cuchaz/enigma/inputs/Keep"); + private static final ClassEntry BASE = newClass("a"); + private static final ClassEntry SAME_PACKAGE_CHILD = newClass("b"); + private static final ClassEntry SAME_PACKAGE_CHILD_INNER = newClass("b$a"); + private static final ClassEntry OTHER_PACKAGE_CHILD = newClass("c"); + private static final ClassEntry OTHER_PACKAGE_CHILD_INNER = newClass("c$a"); + private final JarIndex jarIndex; + + public PackageVisibilityIndexTest() throws Exception { + ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/packageAccess.jar")); + jarIndex = classCache.index(ProgressListener.none()); + } + + @Test + public void test() { + PackageVisibilityIndex visibilityIndex = jarIndex.getPackageVisibilityIndex(); + assertThat(visibilityIndex.getPartition(BASE), containsInAnyOrder(BASE, SAME_PACKAGE_CHILD, SAME_PACKAGE_CHILD_INNER)); + System.out.println(visibilityIndex.getPartitions()); + assertThat(visibilityIndex.getPartitions(), containsInAnyOrder( + containsInAnyOrder(BASE, SAME_PACKAGE_CHILD, SAME_PACKAGE_CHILD_INNER), + containsInAnyOrder(OTHER_PACKAGE_CHILD, OTHER_PACKAGE_CHILD_INNER), + contains(KEEP) + )); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/TestDeobfed.java b/enigma/src/test/java/cuchaz/enigma/TestDeobfed.java new file mode 100644 index 00000000..494d959e --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/TestDeobfed.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma; + +import cuchaz.enigma.analysis.ClassCache; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.source.Decompiler; +import cuchaz.enigma.source.Decompilers; +import cuchaz.enigma.source.SourceSettings; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static cuchaz.enigma.TestEntryFactory.newClass; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; + +public class TestDeobfed { + private static Enigma enigma; + private static ClassCache classCache; + private static JarIndex index; + + @BeforeClass + public static void beforeClass() throws Exception { + enigma = Enigma.create(); + + Path obf = Paths.get("build/test-obf/translation.jar"); + Path deobf = Paths.get("build/test-deobf/translation.jar"); + Files.createDirectories(deobf.getParent()); + EnigmaProject project = enigma.openJar(obf, ProgressListener.none()); + project.exportRemappedJar(ProgressListener.none()).write(deobf, ProgressListener.none()); + + classCache = ClassCache.of(deobf); + index = classCache.index(ProgressListener.none()); + } + + @Test + public void obfEntries() { + assertThat(index.getEntryIndex().getClasses(), containsInAnyOrder( + newClass("cuchaz/enigma/inputs/Keep"), + newClass("a"), + newClass("b"), + newClass("c"), + newClass("d"), + newClass("d$1"), + newClass("e"), + newClass("f"), + newClass("g"), + newClass("g$a"), + newClass("g$a$a"), + newClass("g$b"), + newClass("g$b$a"), + newClass("h"), + newClass("h$a"), + newClass("h$a$a"), + newClass("h$b"), + newClass("h$b$a"), + newClass("h$b$a$a"), + newClass("h$b$a$b"), + newClass("i"), + newClass("i$a"), + newClass("i$b") + )); + } + + @Test + public void decompile() { + EnigmaProject project = new EnigmaProject(enigma, classCache, index, new byte[20]); + Decompiler decompiler = Decompilers.PROCYON.create(project.getClassCache(), new SourceSettings(false, false)); + + decompiler.getSource("a"); + decompiler.getSource("b"); + decompiler.getSource("c"); + decompiler.getSource("d"); + decompiler.getSource("d$1"); + decompiler.getSource("e"); + decompiler.getSource("f"); + decompiler.getSource("g"); + decompiler.getSource("g$a"); + decompiler.getSource("g$a$a"); + decompiler.getSource("g$b"); + decompiler.getSource("g$b$a"); + decompiler.getSource("h"); + decompiler.getSource("h$a"); + decompiler.getSource("h$a$a"); + decompiler.getSource("h$b"); + decompiler.getSource("h$b$a"); + decompiler.getSource("h$b$a$a"); + decompiler.getSource("h$b$a$b"); + decompiler.getSource("i"); + decompiler.getSource("i$a"); + decompiler.getSource("i$b"); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/TestDeobfuscator.java b/enigma/src/test/java/cuchaz/enigma/TestDeobfuscator.java new file mode 100644 index 00000000..6619d26e --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/TestDeobfuscator.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma; + +import cuchaz.enigma.source.Decompiler; +import cuchaz.enigma.source.Decompilers; +import cuchaz.enigma.source.SourceSettings; +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.Paths; + +public class TestDeobfuscator { + private EnigmaProject openProject() throws IOException { + Enigma enigma = Enigma.create(); + return enigma.openJar(Paths.get("build/test-obf/loneClass.jar"), ProgressListener.none()); + } + + @Test + public void loadJar() + throws Exception { + openProject(); + } + + @Test + public void decompileClass() throws Exception { + EnigmaProject project = openProject(); + Decompiler decompiler = Decompilers.PROCYON.create(project.getClassCache(), new SourceSettings(false, false)); + + decompiler.getSource("a").asString(); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/TestEntryFactory.java b/enigma/src/test/java/cuchaz/enigma/TestEntryFactory.java new file mode 100644 index 00000000..9e1425a2 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/TestEntryFactory.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma; + +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.translation.representation.*; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +public class TestEntryFactory { + + public static ClassEntry newClass(String name) { + return new ClassEntry(name); + } + + public static FieldEntry newField(String className, String fieldName, String fieldType) { + return newField(newClass(className), fieldName, fieldType); + } + + public static FieldEntry newField(ClassEntry classEntry, String fieldName, String fieldType) { + return new FieldEntry(classEntry, fieldName, new TypeDescriptor(fieldType)); + } + + public static MethodEntry newMethod(String className, String methodName, String methodSignature) { + return newMethod(newClass(className), methodName, methodSignature); + } + + public static MethodEntry newMethod(ClassEntry classEntry, String methodName, String methodSignature) { + return new MethodEntry(classEntry, methodName, new MethodDescriptor(methodSignature)); + } + + public static EntryReference newFieldReferenceByMethod(FieldEntry fieldEntry, String callerClassName, String callerName, String callerSignature) { + return new EntryReference<>(fieldEntry, "", newMethod(callerClassName, callerName, callerSignature)); + } + + public static EntryReference newBehaviorReferenceByMethod(MethodEntry methodEntry, String callerClassName, String callerName, String callerSignature) { + return new EntryReference<>(methodEntry, "", newMethod(callerClassName, callerName, callerSignature)); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/TestInnerClasses.java b/enigma/src/test/java/cuchaz/enigma/TestInnerClasses.java new file mode 100644 index 00000000..85c72f81 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/TestInnerClasses.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma; + +import cuchaz.enigma.analysis.ClassCache; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.source.Decompiler; +import cuchaz.enigma.source.Decompilers; +import cuchaz.enigma.source.SourceSettings; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import org.junit.Test; + +import java.nio.file.Paths; + +import static cuchaz.enigma.TestEntryFactory.newClass; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class TestInnerClasses { + + private static final ClassEntry SimpleOuter = newClass("d"); + private static final ClassEntry SimpleInner = newClass("d$a"); + private static final ClassEntry ConstructorArgsOuter = newClass("c"); + private static final ClassEntry ConstructorArgsInner = newClass("c$a"); + private static final ClassEntry ClassTreeRoot = newClass("f"); + private static final ClassEntry ClassTreeLevel1 = newClass("f$a"); + private static final ClassEntry ClassTreeLevel2 = newClass("f$a$a"); + private static final ClassEntry ClassTreeLevel3 = newClass("f$a$a$a"); + private final JarIndex index; + private final Decompiler decompiler; + + public TestInnerClasses() throws Exception { + ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/innerClasses.jar")); + index = classCache.index(ProgressListener.none()); + decompiler = Decompilers.PROCYON.create(classCache, new SourceSettings(false, false)); + } + + @Test + public void simple() { + decompile(SimpleOuter); + } + + @Test + public void constructorArgs() { + decompile(ConstructorArgsOuter); + } + + @Test + public void classTree() { + + // root level + assertThat(index.getEntryIndex().hasClass(ClassTreeRoot), is(true)); + + // level 1 + ClassEntry fullClassEntry = new ClassEntry(ClassTreeRoot.getName() + + "$" + ClassTreeLevel1.getSimpleName()); + assertThat(index.getEntryIndex().hasClass(fullClassEntry), is(true)); + + // level 2 + fullClassEntry = new ClassEntry(ClassTreeRoot.getName() + + "$" + ClassTreeLevel1.getSimpleName() + + "$" + ClassTreeLevel2.getSimpleName()); + assertThat(index.getEntryIndex().hasClass(fullClassEntry), is(true)); + + // level 3 + fullClassEntry = new ClassEntry(ClassTreeRoot.getName() + + "$" + ClassTreeLevel1.getSimpleName() + + "$" + ClassTreeLevel2.getSimpleName() + + "$" + ClassTreeLevel3.getSimpleName()); + assertThat(index.getEntryIndex().hasClass(fullClassEntry), is(true)); + } + + private void decompile(ClassEntry classEntry) { + decompiler.getSource(classEntry.getName()); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java b/enigma/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java new file mode 100644 index 00000000..48975c82 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma; + +import cuchaz.enigma.analysis.ClassCache; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; +import org.junit.Test; + +import java.nio.file.Paths; +import java.util.Collection; + +import static cuchaz.enigma.TestEntryFactory.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class TestJarIndexConstructorReferences { + + private JarIndex index; + + private ClassEntry baseClass = newClass("a"); + private ClassEntry subClass = newClass("d"); + private ClassEntry subsubClass = newClass("e"); + private ClassEntry defaultClass = newClass("c"); + private ClassEntry callerClass = newClass("b"); + + public TestJarIndexConstructorReferences() throws Exception { + ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/constructors.jar")); + index = classCache.index(ProgressListener.none()); + } + + @Test + public void obfEntries() { + assertThat(index.getEntryIndex().getClasses(), containsInAnyOrder(newClass("cuchaz/enigma/inputs/Keep"), baseClass, + subClass, subsubClass, defaultClass, callerClass)); + } + + @Test + @SuppressWarnings("unchecked") + public void baseDefault() { + MethodEntry source = newMethod(baseClass, "", "()V"); + Collection> references = index.getReferenceIndex().getReferencesToMethod(source); + assertThat(references, containsInAnyOrder( + newBehaviorReferenceByMethod(source, callerClass.getName(), "a", "()V"), + newBehaviorReferenceByMethod(source, subClass.getName(), "", "()V"), + newBehaviorReferenceByMethod(source, subClass.getName(), "", "(III)V") + )); + } + + @Test + @SuppressWarnings("unchecked") + public void baseInt() { + MethodEntry source = newMethod(baseClass, "", "(I)V"); + assertThat(index.getReferenceIndex().getReferencesToMethod(source), containsInAnyOrder( + newBehaviorReferenceByMethod(source, callerClass.getName(), "b", "()V") + )); + } + + @Test + @SuppressWarnings("unchecked") + public void subDefault() { + MethodEntry source = newMethod(subClass, "", "()V"); + assertThat(index.getReferenceIndex().getReferencesToMethod(source), containsInAnyOrder( + newBehaviorReferenceByMethod(source, callerClass.getName(), "c", "()V"), + newBehaviorReferenceByMethod(source, subClass.getName(), "", "(I)V") + )); + } + + @Test + @SuppressWarnings("unchecked") + public void subInt() { + MethodEntry source = newMethod(subClass, "", "(I)V"); + assertThat(index.getReferenceIndex().getReferencesToMethod(source), containsInAnyOrder( + newBehaviorReferenceByMethod(source, callerClass.getName(), "d", "()V"), + newBehaviorReferenceByMethod(source, subClass.getName(), "", "(II)V"), + newBehaviorReferenceByMethod(source, subsubClass.getName(), "", "(I)V") + )); + } + + @Test + @SuppressWarnings("unchecked") + public void subIntInt() { + MethodEntry source = newMethod(subClass, "", "(II)V"); + assertThat(index.getReferenceIndex().getReferencesToMethod(source), containsInAnyOrder( + newBehaviorReferenceByMethod(source, callerClass.getName(), "e", "()V") + )); + } + + @Test + public void subIntIntInt() { + MethodEntry source = newMethod(subClass, "", "(III)V"); + assertThat(index.getReferenceIndex().getReferencesToMethod(source), is(empty())); + } + + @Test + @SuppressWarnings("unchecked") + public void subsubInt() { + MethodEntry source = newMethod(subsubClass, "", "(I)V"); + assertThat(index.getReferenceIndex().getReferencesToMethod(source), containsInAnyOrder( + newBehaviorReferenceByMethod(source, callerClass.getName(), "f", "()V") + )); + } + + @Test + @SuppressWarnings("unchecked") + public void defaultConstructable() { + MethodEntry source = newMethod(defaultClass, "", "()V"); + assertThat(index.getReferenceIndex().getReferencesToMethod(source), containsInAnyOrder( + newBehaviorReferenceByMethod(source, callerClass.getName(), "g", "()V") + )); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java b/enigma/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java new file mode 100644 index 00000000..76e379c3 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java @@ -0,0 +1,227 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma; + +import cuchaz.enigma.analysis.ClassCache; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.analysis.index.EntryIndex; +import cuchaz.enigma.analysis.index.InheritanceIndex; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.IndexEntryResolver; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; +import org.junit.Test; +import org.objectweb.asm.Opcodes; + +import java.nio.file.Paths; +import java.util.Collection; + +import static cuchaz.enigma.TestEntryFactory.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class TestJarIndexInheritanceTree { + + private JarIndex index; + + private ClassEntry baseClass = newClass("a"); + private ClassEntry subClassA = newClass("b"); + private ClassEntry subClassAA = newClass("d"); + private ClassEntry subClassB = newClass("c"); + private FieldEntry nameField = newField(baseClass, "a", "Ljava/lang/String;"); + private FieldEntry numThingsField = newField(subClassB, "a", "I"); + + public TestJarIndexInheritanceTree() + throws Exception { + ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/inheritanceTree.jar")); + index = classCache.index(ProgressListener.none()); + } + + @Test + public void obfEntries() { + assertThat(index.getEntryIndex().getClasses(), containsInAnyOrder( + newClass("cuchaz/enigma/inputs/Keep"), baseClass, subClassA, subClassAA, subClassB + )); + } + + @Test + public void translationIndex() { + + InheritanceIndex index = this.index.getInheritanceIndex(); + + // base class + assertThat(index.getParents(baseClass), is(empty())); + assertThat(index.getAncestors(baseClass), is(empty())); + assertThat(index.getChildren(baseClass), containsInAnyOrder(subClassA, subClassB + )); + + // subclass a + assertThat(index.getParents(subClassA), contains(baseClass)); + assertThat(index.getAncestors(subClassA), containsInAnyOrder(baseClass)); + assertThat(index.getChildren(subClassA), contains(subClassAA)); + + // subclass aa + assertThat(index.getParents(subClassAA), contains(subClassA)); + assertThat(index.getAncestors(subClassAA), containsInAnyOrder(subClassA, baseClass)); + assertThat(index.getChildren(subClassAA), is(empty())); + + // subclass b + assertThat(index.getParents(subClassB), contains(baseClass)); + assertThat(index.getAncestors(subClassB), containsInAnyOrder(baseClass)); + assertThat(index.getChildren(subClassB), is(empty())); + } + + @Test + public void access() { + assertThat(index.getEntryIndex().getFieldAccess(nameField), is(new AccessFlags(Opcodes.ACC_PRIVATE))); + assertThat(index.getEntryIndex().getFieldAccess(numThingsField), is(new AccessFlags(Opcodes.ACC_PRIVATE))); + } + + @Test + public void relatedMethodImplementations() { + + Collection entries; + + EntryResolver resolver = new IndexEntryResolver(index); + // getName() + entries = resolver.resolveEquivalentMethods(newMethod(baseClass, "a", "()Ljava/lang/String;")); + assertThat(entries, containsInAnyOrder( + newMethod(baseClass, "a", "()Ljava/lang/String;"), + newMethod(subClassAA, "a", "()Ljava/lang/String;") + )); + entries = resolver.resolveEquivalentMethods(newMethod(subClassAA, "a", "()Ljava/lang/String;")); + assertThat(entries, containsInAnyOrder( + newMethod(baseClass, "a", "()Ljava/lang/String;"), + newMethod(subClassAA, "a", "()Ljava/lang/String;") + )); + + // doBaseThings() + entries = resolver.resolveEquivalentMethods(newMethod(baseClass, "a", "()V")); + assertThat(entries, containsInAnyOrder( + newMethod(baseClass, "a", "()V"), + newMethod(subClassAA, "a", "()V"), + newMethod(subClassB, "a", "()V") + )); + entries = resolver.resolveEquivalentMethods(newMethod(subClassAA, "a", "()V")); + assertThat(entries, containsInAnyOrder( + newMethod(baseClass, "a", "()V"), + newMethod(subClassAA, "a", "()V"), + newMethod(subClassB, "a", "()V") + )); + entries = resolver.resolveEquivalentMethods(newMethod(subClassB, "a", "()V")); + assertThat(entries, containsInAnyOrder( + newMethod(baseClass, "a", "()V"), + newMethod(subClassAA, "a", "()V"), + newMethod(subClassB, "a", "()V") + )); + + // doBThings + entries = resolver.resolveEquivalentMethods(newMethod(subClassB, "b", "()V")); + assertThat(entries, containsInAnyOrder(newMethod(subClassB, "b", "()V"))); + } + + @Test + @SuppressWarnings("unchecked") + public void fieldReferences() { + Collection> references; + + // name + references = index.getReferenceIndex().getReferencesToField(nameField); + assertThat(references, containsInAnyOrder( + newFieldReferenceByMethod(nameField, baseClass.getName(), "", "(Ljava/lang/String;)V"), + newFieldReferenceByMethod(nameField, baseClass.getName(), "a", "()Ljava/lang/String;") + )); + + // numThings + references = index.getReferenceIndex().getReferencesToField(numThingsField); + assertThat(references, containsInAnyOrder( + newFieldReferenceByMethod(numThingsField, subClassB.getName(), "", "()V"), + newFieldReferenceByMethod(numThingsField, subClassB.getName(), "b", "()V") + )); + } + + @Test + @SuppressWarnings("unchecked") + public void behaviorReferences() { + + MethodEntry source; + Collection> references; + + // baseClass constructor + source = newMethod(baseClass, "", "(Ljava/lang/String;)V"); + references = index.getReferenceIndex().getReferencesToMethod(source); + assertThat(references, containsInAnyOrder( + newBehaviorReferenceByMethod(source, subClassA.getName(), "", "(Ljava/lang/String;)V"), + newBehaviorReferenceByMethod(source, subClassB.getName(), "", "()V") + )); + + // subClassA constructor + source = newMethod(subClassA, "", "(Ljava/lang/String;)V"); + references = index.getReferenceIndex().getReferencesToMethod(source); + assertThat(references, containsInAnyOrder( + newBehaviorReferenceByMethod(source, subClassAA.getName(), "", "()V") + )); + + // baseClass.getName() + source = newMethod(baseClass, "a", "()Ljava/lang/String;"); + references = index.getReferenceIndex().getReferencesToMethod(source); + assertThat(references, containsInAnyOrder( + newBehaviorReferenceByMethod(source, subClassAA.getName(), "a", "()Ljava/lang/String;"), + newBehaviorReferenceByMethod(source, subClassB.getName(), "a", "()V") + )); + + // subclassAA.getName() + source = newMethod(subClassAA, "a", "()Ljava/lang/String;"); + references = index.getReferenceIndex().getReferencesToMethod(source); + assertThat(references, containsInAnyOrder( + newBehaviorReferenceByMethod(source, subClassAA.getName(), "a", "()V") + )); + } + + @Test + public void containsEntries() { + EntryIndex entryIndex = index.getEntryIndex(); + // classes + assertThat(entryIndex.hasClass(baseClass), is(true)); + assertThat(entryIndex.hasClass(subClassA), is(true)); + assertThat(entryIndex.hasClass(subClassAA), is(true)); + assertThat(entryIndex.hasClass(subClassB), is(true)); + + // fields + assertThat(entryIndex.hasField(nameField), is(true)); + assertThat(entryIndex.hasField(numThingsField), is(true)); + + // methods + // getName() + assertThat(entryIndex.hasMethod(newMethod(baseClass, "a", "()Ljava/lang/String;")), is(true)); + assertThat(entryIndex.hasMethod(newMethod(subClassA, "a", "()Ljava/lang/String;")), is(false)); + assertThat(entryIndex.hasMethod(newMethod(subClassAA, "a", "()Ljava/lang/String;")), is(true)); + assertThat(entryIndex.hasMethod(newMethod(subClassB, "a", "()Ljava/lang/String;")), is(false)); + + // doBaseThings() + assertThat(entryIndex.hasMethod(newMethod(baseClass, "a", "()V")), is(true)); + assertThat(entryIndex.hasMethod(newMethod(subClassA, "a", "()V")), is(false)); + assertThat(entryIndex.hasMethod(newMethod(subClassAA, "a", "()V")), is(true)); + assertThat(entryIndex.hasMethod(newMethod(subClassB, "a", "()V")), is(true)); + + // doBThings() + assertThat(entryIndex.hasMethod(newMethod(baseClass, "b", "()V")), is(false)); + assertThat(entryIndex.hasMethod(newMethod(subClassA, "b", "()V")), is(false)); + assertThat(entryIndex.hasMethod(newMethod(subClassAA, "b", "()V")), is(false)); + assertThat(entryIndex.hasMethod(newMethod(subClassB, "b", "()V")), is(true)); + + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java b/enigma/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java new file mode 100644 index 00000000..103c366b --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java @@ -0,0 +1,157 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma; + +import cuchaz.enigma.analysis.*; +import cuchaz.enigma.analysis.index.EntryIndex; +import cuchaz.enigma.analysis.index.InheritanceIndex; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.VoidTranslator; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; +import org.junit.Test; + +import java.nio.file.Paths; +import java.util.Collection; +import java.util.List; + +import static cuchaz.enigma.TestEntryFactory.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class TestJarIndexLoneClass { + + private JarIndex index; + + public TestJarIndexLoneClass() throws Exception { + ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/loneClass.jar")); + index = classCache.index(ProgressListener.none()); + } + + @Test + public void obfEntries() { + assertThat(index.getEntryIndex().getClasses(), containsInAnyOrder( + newClass("cuchaz/enigma/inputs/Keep"), + newClass("a") + )); + } + + @Test + public void translationIndex() { + InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); + assertThat(inheritanceIndex.getParents(new ClassEntry("a")), is(empty())); + assertThat(inheritanceIndex.getParents(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(empty())); + assertThat(inheritanceIndex.getAncestors(new ClassEntry("a")), is(empty())); + assertThat(inheritanceIndex.getAncestors(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(empty())); + assertThat(inheritanceIndex.getChildren(new ClassEntry("a")), is(empty())); + assertThat(inheritanceIndex.getChildren(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(empty())); + } + + @Test + public void access() { + EntryIndex entryIndex = index.getEntryIndex(); + assertThat(entryIndex.getFieldAccess(newField("a", "a", "Ljava/lang/String;")), is(AccessFlags.PRIVATE)); + assertThat(entryIndex.getMethodAccess(newMethod("a", "a", "()Ljava/lang/String;")), is(AccessFlags.PUBLIC)); + assertThat(entryIndex.getFieldAccess(newField("a", "b", "Ljava/lang/String;")), is(nullValue())); + assertThat(entryIndex.getFieldAccess(newField("a", "a", "LFoo;")), is(nullValue())); + } + + @Test + public void classInheritance() { + IndexTreeBuilder treeBuilder = new IndexTreeBuilder(index); + ClassInheritanceTreeNode node = treeBuilder.buildClassInheritance(VoidTranslator.INSTANCE, newClass("a")); + assertThat(node, is(not(nullValue()))); + assertThat(node.getObfClassName(), is("a")); + assertThat(node.getChildCount(), is(0)); + } + + @Test + public void methodInheritance() { + IndexTreeBuilder treeBuilder = new IndexTreeBuilder(index); + MethodEntry source = newMethod("a", "a", "()Ljava/lang/String;"); + MethodInheritanceTreeNode node = treeBuilder.buildMethodInheritance(VoidTranslator.INSTANCE, source); + assertThat(node, is(not(nullValue()))); + assertThat(node.getMethodEntry(), is(source)); + assertThat(node.getChildCount(), is(0)); + } + + @Test + public void classImplementations() { + IndexTreeBuilder treeBuilder = new IndexTreeBuilder(index); + ClassImplementationsTreeNode node = treeBuilder.buildClassImplementations(VoidTranslator.INSTANCE, newClass("a")); + assertThat(node, is(nullValue())); + } + + @Test + public void methodImplementations() { + IndexTreeBuilder treeBuilder = new IndexTreeBuilder(index); + MethodEntry source = newMethod("a", "a", "()Ljava/lang/String;"); + + List nodes = treeBuilder.buildMethodImplementations(VoidTranslator.INSTANCE, source); + assertThat(nodes, hasSize(1)); + assertThat(nodes.get(0).getMethodEntry(), is(source)); + } + + @Test + public void relatedMethodImplementations() { + Collection entries = index.getEntryResolver().resolveEquivalentMethods(newMethod("a", "a", "()Ljava/lang/String;")); + assertThat(entries, containsInAnyOrder( + newMethod("a", "a", "()Ljava/lang/String;") + )); + } + + @Test + @SuppressWarnings("unchecked") + public void fieldReferences() { + FieldEntry source = newField("a", "a", "Ljava/lang/String;"); + Collection> references = index.getReferenceIndex().getReferencesToField(source); + assertThat(references, containsInAnyOrder( + newFieldReferenceByMethod(source, "a", "", "(Ljava/lang/String;)V"), + newFieldReferenceByMethod(source, "a", "a", "()Ljava/lang/String;") + )); + } + + @Test + public void behaviorReferences() { + assertThat(index.getReferenceIndex().getReferencesToMethod(newMethod("a", "a", "()Ljava/lang/String;")), is(empty())); + } + + @Test + public void interfaces() { + assertThat(index.getInheritanceIndex().getParents(new ClassEntry("a")), is(empty())); + } + + @Test + public void implementingClasses() { + assertThat(index.getInheritanceIndex().getChildren(new ClassEntry("a")), is(empty())); + } + + @Test + public void isInterface() { + assertThat(index.getInheritanceIndex().isParent(new ClassEntry("a")), is(false)); + } + + @Test + public void testContains() { + EntryIndex entryIndex = index.getEntryIndex(); + assertThat(entryIndex.hasClass(newClass("a")), is(true)); + assertThat(entryIndex.hasClass(newClass("b")), is(false)); + assertThat(entryIndex.hasField(newField("a", "a", "Ljava/lang/String;")), is(true)); + assertThat(entryIndex.hasField(newField("a", "b", "Ljava/lang/String;")), is(false)); + assertThat(entryIndex.hasField(newField("a", "a", "LFoo;")), is(false)); + assertThat(entryIndex.hasMethod(newMethod("a", "a", "()Ljava/lang/String;")), is(true)); + assertThat(entryIndex.hasMethod(newMethod("a", "b", "()Ljava/lang/String;")), is(false)); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/TestMethodDescriptor.java b/enigma/src/test/java/cuchaz/enigma/TestMethodDescriptor.java new file mode 100644 index 00000000..a73880dd --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/TestMethodDescriptor.java @@ -0,0 +1,247 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma; + +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class TestMethodDescriptor { + + @Test + public void easiest() { + final MethodDescriptor sig = new MethodDescriptor("()V"); + assertThat(sig.getArgumentDescs(), is(empty())); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); + } + + @Test + public void primitives() { + { + final MethodDescriptor sig = new MethodDescriptor("(I)V"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("I") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("(I)I"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("I") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("I"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("(IBCJ)Z"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("I"), + new TypeDescriptor("B"), + new TypeDescriptor("C"), + new TypeDescriptor("J") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("Z"))); + } + } + + @Test + public void classes() { + { + final MethodDescriptor sig = new MethodDescriptor("([LFoo;)V"); + assertThat(sig.getArgumentDescs().size(), is(1)); + assertThat(sig.getArgumentDescs().get(0), is(new TypeDescriptor("[LFoo;"))); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("(LFoo;)LBar;"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("LFoo;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LBar;"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("(LFoo;LMoo;LZoo;)LBar;"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("LFoo;"), + new TypeDescriptor("LMoo;"), + new TypeDescriptor("LZoo;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LBar;"))); + } + } + + @Test + public void arrays() { + { + final MethodDescriptor sig = new MethodDescriptor("([I)V"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("[I") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("([I)[J"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("[I") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[J"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("([I[Z[F)[D"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("[I"), + new TypeDescriptor("[Z"), + new TypeDescriptor("[F") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[D"))); + } + } + + @Test + public void mixed() { + { + final MethodDescriptor sig = new MethodDescriptor("(I[JLFoo;)Z"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("I"), + new TypeDescriptor("[J"), + new TypeDescriptor("LFoo;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("Z"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("(III)[LFoo;"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("I"), + new TypeDescriptor("I"), + new TypeDescriptor("I") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[LFoo;"))); + } + } + + @Test + public void replaceClasses() { + { + final MethodDescriptor oldSig = new MethodDescriptor("()V"); + final MethodDescriptor sig = oldSig.remap(s -> null); + assertThat(sig.getArgumentDescs(), is(empty())); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); + } + { + final MethodDescriptor oldSig = new MethodDescriptor("(IJLFoo;)V"); + final MethodDescriptor sig = oldSig.remap(s -> null); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("I"), + new TypeDescriptor("J"), + new TypeDescriptor("LFoo;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); + } + { + final MethodDescriptor oldSig = new MethodDescriptor("(LFoo;LBar;)LMoo;"); + final MethodDescriptor sig = oldSig.remap(s -> { + if (s.equals("Foo")) { + return "Bar"; + } + return null; + }); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("LBar;"), + new TypeDescriptor("LBar;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LMoo;"))); + } + { + final MethodDescriptor oldSig = new MethodDescriptor("(LFoo;LBar;)LMoo;"); + final MethodDescriptor sig = oldSig.remap(s -> { + if (s.equals("Moo")) { + return "Cow"; + } + return null; + }); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("LFoo;"), + new TypeDescriptor("LBar;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LCow;"))); + } + } + + @Test + public void replaceArrayClasses() { + { + final MethodDescriptor oldSig = new MethodDescriptor("([LFoo;)[[[LBar;"); + final MethodDescriptor sig = oldSig.remap(s -> { + if (s.equals("Foo")) { + return "Food"; + } else if (s.equals("Bar")) { + return "Beer"; + } + return null; + }); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("[LFood;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[[[LBeer;"))); + } + } + + @Test + public void equals() { + + // base + assertThat(new MethodDescriptor("()V"), is(new MethodDescriptor("()V"))); + + // arguments + assertThat(new MethodDescriptor("(I)V"), is(new MethodDescriptor("(I)V"))); + assertThat(new MethodDescriptor("(ZIZ)V"), is(new MethodDescriptor("(ZIZ)V"))); + assertThat(new MethodDescriptor("(LFoo;)V"), is(new MethodDescriptor("(LFoo;)V"))); + assertThat(new MethodDescriptor("(LFoo;LBar;)V"), is(new MethodDescriptor("(LFoo;LBar;)V"))); + assertThat(new MethodDescriptor("([I)V"), is(new MethodDescriptor("([I)V"))); + assertThat(new MethodDescriptor("([[D[[[J)V"), is(new MethodDescriptor("([[D[[[J)V"))); + + assertThat(new MethodDescriptor("()V"), is(not(new MethodDescriptor("(I)V")))); + assertThat(new MethodDescriptor("(I)V"), is(not(new MethodDescriptor("()V")))); + assertThat(new MethodDescriptor("(IJ)V"), is(not(new MethodDescriptor("(JI)V")))); + assertThat(new MethodDescriptor("([[Z)V"), is(not(new MethodDescriptor("([[LFoo;)V")))); + assertThat(new MethodDescriptor("(LFoo;LBar;)V"), is(not(new MethodDescriptor("(LFoo;LCow;)V")))); + assertThat(new MethodDescriptor("([LFoo;LBar;)V"), is(not(new MethodDescriptor("(LFoo;LCow;)V")))); + + // return desc + assertThat(new MethodDescriptor("()I"), is(new MethodDescriptor("()I"))); + assertThat(new MethodDescriptor("()Z"), is(new MethodDescriptor("()Z"))); + assertThat(new MethodDescriptor("()[D"), is(new MethodDescriptor("()[D"))); + assertThat(new MethodDescriptor("()[[[Z"), is(new MethodDescriptor("()[[[Z"))); + assertThat(new MethodDescriptor("()LFoo;"), is(new MethodDescriptor("()LFoo;"))); + assertThat(new MethodDescriptor("()[LFoo;"), is(new MethodDescriptor("()[LFoo;"))); + + assertThat(new MethodDescriptor("()I"), is(not(new MethodDescriptor("()Z")))); + assertThat(new MethodDescriptor("()Z"), is(not(new MethodDescriptor("()I")))); + assertThat(new MethodDescriptor("()[D"), is(not(new MethodDescriptor("()[J")))); + assertThat(new MethodDescriptor("()[[[Z"), is(not(new MethodDescriptor("()[[Z")))); + assertThat(new MethodDescriptor("()LFoo;"), is(not(new MethodDescriptor("()LBar;")))); + assertThat(new MethodDescriptor("()[LFoo;"), is(not(new MethodDescriptor("()[LBar;")))); + } + + @Test + public void testToString() { + assertThat(new MethodDescriptor("()V").toString(), is("()V")); + assertThat(new MethodDescriptor("(I)V").toString(), is("(I)V")); + assertThat(new MethodDescriptor("(ZIZ)V").toString(), is("(ZIZ)V")); + assertThat(new MethodDescriptor("(LFoo;)V").toString(), is("(LFoo;)V")); + assertThat(new MethodDescriptor("(LFoo;LBar;)V").toString(), is("(LFoo;LBar;)V")); + assertThat(new MethodDescriptor("([I)V").toString(), is("([I)V")); + assertThat(new MethodDescriptor("([[D[[[J)V").toString(), is("([[D[[[J)V")); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/TestTokensConstructors.java b/enigma/src/test/java/cuchaz/enigma/TestTokensConstructors.java new file mode 100644 index 00000000..0398de4f --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/TestTokensConstructors.java @@ -0,0 +1,137 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma; + +import cuchaz.enigma.translation.representation.entry.MethodEntry; +import org.junit.Test; + +import java.nio.file.Paths; + +import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByMethod; +import static cuchaz.enigma.TestEntryFactory.newMethod; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class TestTokensConstructors extends TokenChecker { + + public TestTokensConstructors() + throws Exception { + super(Paths.get("build/test-obf/constructors.jar")); + } + + @Test + public void baseDeclarations() { + assertThat(getDeclarationToken(newMethod("a", "", "()V")), is("a")); + assertThat(getDeclarationToken(newMethod("a", "", "(I)V")), is("a")); + } + + @Test + public void subDeclarations() { + assertThat(getDeclarationToken(newMethod("d", "", "()V")), is("d")); + assertThat(getDeclarationToken(newMethod("d", "", "(I)V")), is("d")); + assertThat(getDeclarationToken(newMethod("d", "", "(II)V")), is("d")); + assertThat(getDeclarationToken(newMethod("d", "", "(III)V")), is("d")); + } + + @Test + public void subsubDeclarations() { + assertThat(getDeclarationToken(newMethod("e", "", "(I)V")), is("e")); + } + + @Test + public void defaultDeclarations() { + assertThat(getDeclarationToken(newMethod("c", "", "()V")), nullValue()); + } + + @Test + public void baseDefaultReferences() { + MethodEntry source = newMethod("a", "", "()V"); + assertThat( + getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "a", "()V")), + containsInAnyOrder("a") + ); + assertThat( + getReferenceTokens(newBehaviorReferenceByMethod(source, "d", "", "()V")), + is(empty()) // implicit call, not decompiled to token + ); + assertThat( + getReferenceTokens(newBehaviorReferenceByMethod(source, "d", "", "(III)V")), + is(empty()) // implicit call, not decompiled to token + ); + } + + @Test + public void baseIntReferences() { + MethodEntry source = newMethod("a", "", "(I)V"); + assertThat( + getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "b", "()V")), + containsInAnyOrder("a") + ); + } + + @Test + public void subDefaultReferences() { + MethodEntry source = newMethod("d", "", "()V"); + assertThat( + getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "c", "()V")), + containsInAnyOrder("d") + ); + assertThat( + getReferenceTokens(newBehaviorReferenceByMethod(source, "d", "", "(I)V")), + containsInAnyOrder("this") + ); + } + + @Test + public void subIntReferences() { + MethodEntry source = newMethod("d", "", "(I)V"); + assertThat(getReferenceTokens( + newBehaviorReferenceByMethod(source, "b", "d", "()V")), + containsInAnyOrder("d") + ); + assertThat(getReferenceTokens( + newBehaviorReferenceByMethod(source, "d", "", "(II)V")), + containsInAnyOrder("this") + ); + assertThat(getReferenceTokens( + newBehaviorReferenceByMethod(source, "e", "", "(I)V")), + containsInAnyOrder("super") + ); + } + + @Test + public void subIntIntReferences() { + MethodEntry source = newMethod("d", "", "(II)V"); + assertThat( + getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "e", "()V")), + containsInAnyOrder("d") + ); + } + + @Test + public void subsubIntReferences() { + MethodEntry source = newMethod("e", "", "(I)V"); + assertThat( + getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "f", "()V")), + containsInAnyOrder("e") + ); + } + + @Test + public void defaultConstructableReferences() { + MethodEntry source = newMethod("c", "", "()V"); + assertThat( + getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "g", "()V")), + containsInAnyOrder("c") + ); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/TestTranslator.java b/enigma/src/test/java/cuchaz/enigma/TestTranslator.java new file mode 100644 index 00000000..a420afe1 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/TestTranslator.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma; + +import cuchaz.enigma.translation.representation.entry.Entry; +import org.junit.BeforeClass; +import org.junit.Test; + +import static cuchaz.enigma.TestEntryFactory.*; + +public class TestTranslator { + + @BeforeClass + public static void beforeClass() + throws Exception { + //TODO FIx + //deobfuscator = new Enigma(new JarFile("build/test-obf/translation.jar")); + //try (InputStream in = TestTranslator.class.getResourceAsStream("/cuchaz/enigma/resources/translation.mappings")) { + // mappings = new MappingsJsonReader().read(new InputStreamReader(in)); + // deobfuscator.setMappings(mappings); + // deobfTranslator = deobfuscator.getTranslator(TranslationDirection.Deobfuscating); + // obfTranslator = deobfuscator.getTranslator(TranslationDirection.Obfuscating); + //} + } + + @Test + public void basicClasses() { + assertMapping(newClass("a"), newClass("deobf/A_Basic")); + assertMapping(newClass("b"), newClass("deobf/B_BaseClass")); + assertMapping(newClass("c"), newClass("deobf/C_SubClass")); + } + + @Test + public void basicFields() { + assertMapping(newField("a", "a", "I"), newField("deobf/A_Basic", "f1", "I")); + assertMapping(newField("a", "a", "F"), newField("deobf/A_Basic", "f2", "F")); + assertMapping(newField("a", "a", "Ljava/lang/String;"), newField("deobf/A_Basic", "f3", "Ljava/lang/String;")); + } + + @Test + public void basicMethods() { + assertMapping(newMethod("a", "a", "()V"), newMethod("deobf/A_Basic", "m1", "()V")); + assertMapping(newMethod("a", "a", "()I"), newMethod("deobf/A_Basic", "m2", "()I")); + assertMapping(newMethod("a", "a", "(I)V"), newMethod("deobf/A_Basic", "m3", "(I)V")); + assertMapping(newMethod("a", "a", "(I)I"), newMethod("deobf/A_Basic", "m4", "(I)I")); + } + + // TODO: basic constructors + + @Test + public void inheritanceFields() { + assertMapping(newField("b", "a", "I"), newField("deobf/B_BaseClass", "f1", "I")); + assertMapping(newField("b", "a", "C"), newField("deobf/B_BaseClass", "f2", "C")); + assertMapping(newField("c", "b", "I"), newField("deobf/C_SubClass", "f3", "I")); + assertMapping(newField("c", "c", "I"), newField("deobf/C_SubClass", "f4", "I")); + } + + @Test + public void inheritanceFieldsShadowing() { + assertMapping(newField("c", "b", "C"), newField("deobf/C_SubClass", "f2", "C")); + } + + @Test + public void inheritanceFieldsBySubClass() { + assertMapping(newField("c", "a", "I"), newField("deobf/C_SubClass", "f1", "I")); + // NOTE: can't reference b.C by subclass since it's shadowed + } + + @Test + public void inheritanceMethods() { + assertMapping(newMethod("b", "a", "()I"), newMethod("deobf/B_BaseClass", "m1", "()I")); + assertMapping(newMethod("b", "b", "()I"), newMethod("deobf/B_BaseClass", "m2", "()I")); + assertMapping(newMethod("c", "c", "()I"), newMethod("deobf/C_SubClass", "m3", "()I")); + } + + @Test + public void inheritanceMethodsOverrides() { + assertMapping(newMethod("c", "a", "()I"), newMethod("deobf/C_SubClass", "m1", "()I")); + } + + @Test + public void inheritanceMethodsBySubClass() { + assertMapping(newMethod("c", "b", "()I"), newMethod("deobf/C_SubClass", "m2", "()I")); + } + + @Test + public void innerClasses() { + + // classes + assertMapping(newClass("g"), newClass("deobf/G_OuterClass")); + assertMapping(newClass("g$a"), newClass("deobf/G_OuterClass$A_InnerClass")); + assertMapping(newClass("g$a$a"), newClass("deobf/G_OuterClass$A_InnerClass$A_InnerInnerClass")); + assertMapping(newClass("g$b"), newClass("deobf/G_OuterClass$b")); + assertMapping(newClass("g$b$a"), newClass("deobf/G_OuterClass$b$A_NamedInnerClass")); + + // fields + assertMapping(newField("g$a", "a", "I"), newField("deobf/G_OuterClass$A_InnerClass", "f1", "I")); + assertMapping(newField("g$a", "a", "Ljava/lang/String;"), newField("deobf/G_OuterClass$A_InnerClass", "f2", "Ljava/lang/String;")); + assertMapping(newField("g$a$a", "a", "I"), newField("deobf/G_OuterClass$A_InnerClass$A_InnerInnerClass", "f3", "I")); + assertMapping(newField("g$b$a", "a", "I"), newField("deobf/G_OuterClass$b$A_NamedInnerClass", "f4", "I")); + + // methods + assertMapping(newMethod("g$a", "a", "()V"), newMethod("deobf/G_OuterClass$A_InnerClass", "m1", "()V")); + assertMapping(newMethod("g$a$a", "a", "()V"), newMethod("deobf/G_OuterClass$A_InnerClass$A_InnerInnerClass", "m2", "()V")); + } + + @Test + public void namelessClass() { + assertMapping(newClass("h"), newClass("h")); + } + + @Test + public void testGenerics() { + + // classes + assertMapping(newClass("i"), newClass("deobf/I_Generics")); + assertMapping(newClass("i$a"), newClass("deobf/I_Generics$A_Type")); + assertMapping(newClass("i$b"), newClass("deobf/I_Generics$B_Generic")); + + // fields + assertMapping(newField("i", "a", "Ljava/util/List;"), newField("deobf/I_Generics", "f1", "Ljava/util/List;")); + assertMapping(newField("i", "b", "Ljava/util/List;"), newField("deobf/I_Generics", "f2", "Ljava/util/List;")); + assertMapping(newField("i", "a", "Ljava/util/Map;"), newField("deobf/I_Generics", "f3", "Ljava/util/Map;")); + assertMapping(newField("i$b", "a", "Ljava/lang/Object;"), newField("deobf/I_Generics$B_Generic", "f4", "Ljava/lang/Object;")); + assertMapping(newField("i", "a", "Li$b;"), newField("deobf/I_Generics", "f5", "Ldeobf/I_Generics$B_Generic;")); + assertMapping(newField("i", "b", "Li$b;"), newField("deobf/I_Generics", "f6", "Ldeobf/I_Generics$B_Generic;")); + + // methods + assertMapping(newMethod("i$b", "a", "()Ljava/lang/Object;"), newMethod("deobf/I_Generics$B_Generic", "m1", "()Ljava/lang/Object;")); + } + + private void assertMapping(Entry obf, Entry deobf) { + //assertThat(deobfTranslator.translateEntry(obf), is(deobf)); + //assertThat(obfTranslator.translateEntry(deobf), is(obf)); + + //String deobfName = deobfTranslator.translate(obf); + //if (deobfName != null) { + // assertThat(deobfName, is(deobf.getName())); + //} + + //String obfName = obfTranslator.translate(deobf); + //if (obfName != null) { + // assertThat(obfName, is(obf.getName())); + //} + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/TestTypeDescriptor.java b/enigma/src/test/java/cuchaz/enigma/TestTypeDescriptor.java new file mode 100644 index 00000000..b9ebe559 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/TestTypeDescriptor.java @@ -0,0 +1,243 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma; + +import cuchaz.enigma.translation.representation.TypeDescriptor; +import org.junit.Test; + +import static cuchaz.enigma.TestEntryFactory.newClass; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + +public class TestTypeDescriptor { + + @Test + public void isVoid() { + assertThat(new TypeDescriptor("V").isVoid(), is(true)); + assertThat(new TypeDescriptor("Z").isVoid(), is(false)); + assertThat(new TypeDescriptor("B").isVoid(), is(false)); + assertThat(new TypeDescriptor("C").isVoid(), is(false)); + assertThat(new TypeDescriptor("I").isVoid(), is(false)); + assertThat(new TypeDescriptor("J").isVoid(), is(false)); + assertThat(new TypeDescriptor("F").isVoid(), is(false)); + assertThat(new TypeDescriptor("D").isVoid(), is(false)); + assertThat(new TypeDescriptor("LFoo;").isVoid(), is(false)); + assertThat(new TypeDescriptor("[I").isVoid(), is(false)); + } + + @Test + public void isPrimitive() { + assertThat(new TypeDescriptor("V").isPrimitive(), is(false)); + assertThat(new TypeDescriptor("Z").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("B").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("C").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("I").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("J").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("F").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("D").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("LFoo;").isPrimitive(), is(false)); + assertThat(new TypeDescriptor("[I").isPrimitive(), is(false)); + } + + @Test + public void getPrimitive() { + assertThat(new TypeDescriptor("Z").getPrimitive(), is(TypeDescriptor.Primitive.BOOLEAN)); + assertThat(new TypeDescriptor("B").getPrimitive(), is(TypeDescriptor.Primitive.BYTE)); + assertThat(new TypeDescriptor("C").getPrimitive(), is(TypeDescriptor.Primitive.CHARACTER)); + assertThat(new TypeDescriptor("I").getPrimitive(), is(TypeDescriptor.Primitive.INTEGER)); + assertThat(new TypeDescriptor("J").getPrimitive(), is(TypeDescriptor.Primitive.LONG)); + assertThat(new TypeDescriptor("F").getPrimitive(), is(TypeDescriptor.Primitive.FLOAT)); + assertThat(new TypeDescriptor("D").getPrimitive(), is(TypeDescriptor.Primitive.DOUBLE)); + } + + @Test + public void isClass() { + assertThat(new TypeDescriptor("V").isType(), is(false)); + assertThat(new TypeDescriptor("Z").isType(), is(false)); + assertThat(new TypeDescriptor("B").isType(), is(false)); + assertThat(new TypeDescriptor("C").isType(), is(false)); + assertThat(new TypeDescriptor("I").isType(), is(false)); + assertThat(new TypeDescriptor("J").isType(), is(false)); + assertThat(new TypeDescriptor("F").isType(), is(false)); + assertThat(new TypeDescriptor("D").isType(), is(false)); + assertThat(new TypeDescriptor("LFoo;").isType(), is(true)); + assertThat(new TypeDescriptor("[I").isType(), is(false)); + } + + @Test + public void getClassEntry() { + assertThat(new TypeDescriptor("LFoo;").getTypeEntry(), is(newClass("Foo"))); + assertThat(new TypeDescriptor("Ljava/lang/String;").getTypeEntry(), is(newClass("java/lang/String"))); + } + + @Test + public void getArrayClassEntry() { + assertThat(new TypeDescriptor("[LFoo;").getTypeEntry(), is(newClass("Foo"))); + assertThat(new TypeDescriptor("[[[Ljava/lang/String;").getTypeEntry(), is(newClass("java/lang/String"))); + } + + @Test + public void isArray() { + assertThat(new TypeDescriptor("V").isArray(), is(false)); + assertThat(new TypeDescriptor("Z").isArray(), is(false)); + assertThat(new TypeDescriptor("B").isArray(), is(false)); + assertThat(new TypeDescriptor("C").isArray(), is(false)); + assertThat(new TypeDescriptor("I").isArray(), is(false)); + assertThat(new TypeDescriptor("J").isArray(), is(false)); + assertThat(new TypeDescriptor("F").isArray(), is(false)); + assertThat(new TypeDescriptor("D").isArray(), is(false)); + assertThat(new TypeDescriptor("LFoo;").isArray(), is(false)); + assertThat(new TypeDescriptor("[I").isArray(), is(true)); + } + + @Test + public void getArrayDimension() { + assertThat(new TypeDescriptor("[I").getArrayDimension(), is(1)); + assertThat(new TypeDescriptor("[[I").getArrayDimension(), is(2)); + assertThat(new TypeDescriptor("[[[I").getArrayDimension(), is(3)); + } + + @Test + public void getArrayType() { + assertThat(new TypeDescriptor("[I").getArrayType(), is(new TypeDescriptor("I"))); + assertThat(new TypeDescriptor("[[I").getArrayType(), is(new TypeDescriptor("I"))); + assertThat(new TypeDescriptor("[[[I").getArrayType(), is(new TypeDescriptor("I"))); + assertThat(new TypeDescriptor("[Ljava/lang/String;").getArrayType(), is(new TypeDescriptor("Ljava/lang/String;"))); + } + + @Test + public void hasClass() { + assertThat(new TypeDescriptor("LFoo;").containsType(), is(true)); + assertThat(new TypeDescriptor("Ljava/lang/String;").containsType(), is(true)); + assertThat(new TypeDescriptor("[LBar;").containsType(), is(true)); + assertThat(new TypeDescriptor("[[[LCat;").containsType(), is(true)); + + assertThat(new TypeDescriptor("V").containsType(), is(false)); + assertThat(new TypeDescriptor("[I").containsType(), is(false)); + assertThat(new TypeDescriptor("[[[I").containsType(), is(false)); + assertThat(new TypeDescriptor("Z").containsType(), is(false)); + } + + @Test + public void parseVoid() { + final String answer = "V"; + assertThat(TypeDescriptor.parseFirst("V"), is(answer)); + assertThat(TypeDescriptor.parseFirst("VVV"), is(answer)); + assertThat(TypeDescriptor.parseFirst("VIJ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("V[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("VLFoo;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("V[LFoo;"), is(answer)); + } + + @Test + public void parsePrimitive() { + final String answer = "I"; + assertThat(TypeDescriptor.parseFirst("I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("III"), is(answer)); + assertThat(TypeDescriptor.parseFirst("IJZ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("I[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("ILFoo;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("I[LFoo;"), is(answer)); + } + + @Test + public void parseClass() { + { + final String answer = "LFoo;"; + assertThat(TypeDescriptor.parseFirst("LFoo;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("LFoo;I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("LFoo;JZ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("LFoo;[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("LFoo;LFoo;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("LFoo;[LFoo;"), is(answer)); + } + { + final String answer = "Ljava/lang/String;"; + assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;JZ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;LFoo;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;[LFoo;"), is(answer)); + } + } + + @Test + public void parseArray() { + { + final String answer = "[I"; + assertThat(TypeDescriptor.parseFirst("[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[III"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[IJZ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[I[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[ILFoo;"), is(answer)); + } + { + final String answer = "[[I"; + assertThat(TypeDescriptor.parseFirst("[[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[[III"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[[IJZ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[[I[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[[ILFoo;"), is(answer)); + } + { + final String answer = "[LFoo;"; + assertThat(TypeDescriptor.parseFirst("[LFoo;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[LFoo;II"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[LFoo;JZ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[LFoo;[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[LFoo;LFoo;"), is(answer)); + } + } + + @Test + public void equals() { + assertThat(new TypeDescriptor("V"), is(new TypeDescriptor("V"))); + assertThat(new TypeDescriptor("Z"), is(new TypeDescriptor("Z"))); + assertThat(new TypeDescriptor("B"), is(new TypeDescriptor("B"))); + assertThat(new TypeDescriptor("C"), is(new TypeDescriptor("C"))); + assertThat(new TypeDescriptor("I"), is(new TypeDescriptor("I"))); + assertThat(new TypeDescriptor("J"), is(new TypeDescriptor("J"))); + assertThat(new TypeDescriptor("F"), is(new TypeDescriptor("F"))); + assertThat(new TypeDescriptor("D"), is(new TypeDescriptor("D"))); + assertThat(new TypeDescriptor("LFoo;"), is(new TypeDescriptor("LFoo;"))); + assertThat(new TypeDescriptor("[I"), is(new TypeDescriptor("[I"))); + assertThat(new TypeDescriptor("[[[I"), is(new TypeDescriptor("[[[I"))); + assertThat(new TypeDescriptor("[LFoo;"), is(new TypeDescriptor("[LFoo;"))); + + assertThat(new TypeDescriptor("V"), is(not(new TypeDescriptor("I")))); + assertThat(new TypeDescriptor("I"), is(not(new TypeDescriptor("J")))); + assertThat(new TypeDescriptor("I"), is(not(new TypeDescriptor("LBar;")))); + assertThat(new TypeDescriptor("I"), is(not(new TypeDescriptor("[I")))); + assertThat(new TypeDescriptor("LFoo;"), is(not(new TypeDescriptor("LBar;")))); + assertThat(new TypeDescriptor("[I"), is(not(new TypeDescriptor("[Z")))); + assertThat(new TypeDescriptor("[[[I"), is(not(new TypeDescriptor("[I")))); + assertThat(new TypeDescriptor("[LFoo;"), is(not(new TypeDescriptor("[LBar;")))); + } + + @Test + public void testToString() { + assertThat(new TypeDescriptor("V").toString(), is("V")); + assertThat(new TypeDescriptor("Z").toString(), is("Z")); + assertThat(new TypeDescriptor("B").toString(), is("B")); + assertThat(new TypeDescriptor("C").toString(), is("C")); + assertThat(new TypeDescriptor("I").toString(), is("I")); + assertThat(new TypeDescriptor("J").toString(), is("J")); + assertThat(new TypeDescriptor("F").toString(), is("F")); + assertThat(new TypeDescriptor("D").toString(), is("D")); + assertThat(new TypeDescriptor("LFoo;").toString(), is("LFoo;")); + assertThat(new TypeDescriptor("[I").toString(), is("[I")); + assertThat(new TypeDescriptor("[[[I").toString(), is("[[[I")); + assertThat(new TypeDescriptor("[LFoo;").toString(), is("[LFoo;")); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/TokenChecker.java b/enigma/src/test/java/cuchaz/enigma/TokenChecker.java new file mode 100644 index 00000000..96fc6dab --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/TokenChecker.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma; + +import com.google.common.collect.Lists; +import cuchaz.enigma.analysis.ClassCache; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.source.*; +import cuchaz.enigma.source.Token; +import cuchaz.enigma.translation.representation.entry.Entry; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; + +public class TokenChecker { + private final Decompiler decompiler; + + protected TokenChecker(Path path) throws IOException { + ClassCache classCache = ClassCache.of(path); + decompiler = Decompilers.PROCYON.create(classCache, new SourceSettings(false, false)); + } + + protected String getDeclarationToken(Entry entry) { + // decompile the class + Source source = decompiler.getSource(entry.getContainingClass().getFullName()); + // DEBUG + // tree.acceptVisitor( new TreeDumpVisitor( new File( "tree." + entry.getClassName().replace( '/', '.' ) + ".txt" ) ), null ); + String string = source.asString(); + SourceIndex index = source.index(); + + // get the token value + Token token = index.getDeclarationToken(entry); + if (token == null) { + return null; + } + return string.substring(token.start, token.end); + } + + @SuppressWarnings("unchecked") + protected Collection getReferenceTokens(EntryReference, ? extends Entry> reference) { + // decompile the class + Source source = decompiler.getSource(reference.context.getContainingClass().getFullName()); + String string = source.asString(); + SourceIndex index = source.index(); + + // get the token values + List values = Lists.newArrayList(); + for (Token token : index.getReferenceTokens((EntryReference, Entry>) reference)) { + values.add(string.substring(token.start, token.end)); + } + return values; + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/Keep.java b/enigma/src/test/java/cuchaz/enigma/inputs/Keep.java new file mode 100644 index 00000000..4dbe8e2f --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/Keep.java @@ -0,0 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs; + +public class Keep { + public static void main(String[] args) { + System.out.println("Keep me!"); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java new file mode 100644 index 00000000..f07e1f8b --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.constructors; + +// a +public class BaseClass { + + // ()V + public BaseClass() { + System.out.println("Default constructor"); + } + + // (I)V + public BaseClass(int i) { + System.out.println("Int constructor " + i); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/constructors/Caller.java b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/Caller.java new file mode 100644 index 00000000..71439fd1 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/Caller.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.constructors; + +// b +public class Caller { + + // a()V + public void callBaseDefault() { + // a.()V + System.out.println(new BaseClass()); + } + + // b()V + public void callBaseInt() { + // a.(I)V + System.out.println(new BaseClass(5)); + } + + // c()V + public void callSubDefault() { + // d.()V + System.out.println(new SubClass()); + } + + // d()V + public void callSubInt() { + // d.(I)V + System.out.println(new SubClass(6)); + } + + // e()V + public void callSubIntInt() { + // d.(II)V + System.out.println(new SubClass(4, 2)); + } + + // f()V + public void callSubSubInt() { + // e.(I)V + System.out.println(new SubSubClass(3)); + } + + // g()V + public void callDefaultConstructable() { + // c.()V + System.out.println(new DefaultConstructable()); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java new file mode 100644 index 00000000..c3d41705 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java @@ -0,0 +1,16 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.constructors; + +public class DefaultConstructable { + // only default constructor +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java new file mode 100644 index 00000000..bc56b3b2 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.constructors; + +// d extends a +public class SubClass extends BaseClass { + + // ()V + public SubClass() { + // a.()V + } + + // (I)V + public SubClass(int num) { + // ()V + this(); + System.out.println("SubClass " + num); + } + + // (II)V + public SubClass(int a, int b) { + // (I)V + this(a + b); + } + + // (III)V + public SubClass(int a, int b, int c) { + // a.()V + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java new file mode 100644 index 00000000..87b69d32 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.constructors; + +// e extends d +public class SubSubClass extends SubClass { + + // (I)V + public SubSubClass(int i) { + // c.(I)V + super(i); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java b/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java new file mode 100644 index 00000000..b9c4929c --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.inheritanceTree; + +// a +public abstract class BaseClass { + + // a + private String name; + + // (Ljava/lang/String;)V + protected BaseClass(String name) { + this.name = name; + } + + // a()Ljava/lang/String; + public String getName() { + return name; + } + + // a()V + public abstract void doBaseThings(); +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java b/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java new file mode 100644 index 00000000..50e963c0 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.inheritanceTree; + +// b extends a +public abstract class SubclassA extends BaseClass { + + // (Ljava/lang/String;)V + protected SubclassA(String name) { + // call to a.(Ljava/lang/String)V + super(name); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java b/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java new file mode 100644 index 00000000..d0dd664d --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.inheritanceTree; + +// c extends a +public class SubclassB extends BaseClass { + + // a + private int numThings; + + // ()V + protected SubclassB() { + // a.(Ljava/lang/String;)V + super("B"); + + // access to a + numThings = 4; + } + + @Override + // a()V + public void doBaseThings() { + // call to a.a()Ljava/lang/String; + System.out.println("Base things by B! " + getName()); + } + + // b()V + public void doBThings() { + // access to a + System.out.println("" + numThings + " B things!"); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java b/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java new file mode 100644 index 00000000..c5845702 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.inheritanceTree; + +// d extends b +public class SubsubclassAA extends SubclassA { + + protected SubsubclassAA() { + // call to b.(Ljava/lang/String;)V + super("AA"); + } + + @Override + // a()Ljava/lang/String; + public String getName() { + // call to b.a()Ljava/lang/String; + return "subsub" + super.getName(); + } + + @Override + // a()V + public void doBaseThings() { + // call to d.a()Ljava/lang/String; + System.out.println("Base things by " + getName()); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java new file mode 100644 index 00000000..f652d875 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.innerClasses; + +public class A_Anonymous { + + public void foo() { + Runnable runnable = new Runnable() { + @Override + public void run() { + // don't care + } + }; + runnable.run(); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java new file mode 100644 index 00000000..d1b7601f --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.innerClasses; + +public class B_AnonymousWithScopeArgs { + + public static void foo(final D_Simple arg) { + System.out.println(new Object() { + @Override + public String toString() { + return arg.toString(); + } + }); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java new file mode 100644 index 00000000..94061faa --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.innerClasses; + +@SuppressWarnings("unused") +public class C_ConstructorArgs { + + Inner i; + + public void foo() { + i = new Inner(5); + } + + class Inner { + + private int a; + + public Inner(int a) { + this.a = a; + } + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java new file mode 100644 index 00000000..71b3a6d8 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.innerClasses; + +public class D_Simple { + + class Inner { + // nothing to do + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java new file mode 100644 index 00000000..976ec426 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.innerClasses; + +public class E_AnonymousWithOuterAccess { + + // reproduction of error case documented at: + // https://bitbucket.org/cuchaz/enigma/issue/61/stackoverflowerror-when-deobfuscating + + public Object makeInner() { + outerMethod(); + return new Object() { + @Override + public String toString() { + return outerMethod(); + } + }; + } + + private String outerMethod() { + return "foo"; + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java new file mode 100644 index 00000000..b1de3c9a --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.innerClasses; + +public class F_ClassTree { + + public class Level1 { + + public int f1; + + public class Level2 { + + public int f2; + + public class Level3 { + + public int f3; + } + } + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/loneClass/LoneClass.java b/enigma/src/test/java/cuchaz/enigma/inputs/loneClass/LoneClass.java new file mode 100644 index 00000000..ddc4e319 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/loneClass/LoneClass.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.loneClass; + +public class LoneClass { + + private String name; + + public LoneClass(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/Base.java b/enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/Base.java new file mode 100644 index 00000000..6f5fe304 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/Base.java @@ -0,0 +1,7 @@ +package cuchaz.enigma.inputs.packageAccess; + +public class Base { + protected int make() { + return 42; + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/SamePackageChild.java b/enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/SamePackageChild.java new file mode 100644 index 00000000..cf0f6574 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/SamePackageChild.java @@ -0,0 +1,12 @@ +package cuchaz.enigma.inputs.packageAccess; + +public class SamePackageChild extends Base { + + class Inner { + final int value; + + Inner() { + value = SamePackageChild.this.make(); // no synthetic method + } + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/sub/OtherPackageChild.java b/enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/sub/OtherPackageChild.java new file mode 100644 index 00000000..19fb19c2 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/packageAccess/sub/OtherPackageChild.java @@ -0,0 +1,14 @@ +package cuchaz.enigma.inputs.packageAccess.sub; + +import cuchaz.enigma.inputs.packageAccess.Base; + +public class OtherPackageChild extends Base { + + class Inner { + final int value; + + Inner() { + value = OtherPackageChild.this.make(); // synthetic method call + } + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java b/enigma/src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java new file mode 100644 index 00000000..26f3718c --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.translation; + +public class A_Basic { + + public int one; + public float two; + public String three; + + public void m1() { + } + + public int m2() { + return 42; + } + + public void m3(int a1) { + } + + public int m4(int a1) { + return 5; // chosen by fair die roll, guaranteed to be random + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/translation/B_BaseClass.java b/enigma/src/test/java/cuchaz/enigma/inputs/translation/B_BaseClass.java new file mode 100644 index 00000000..fd7f6e7e --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/translation/B_BaseClass.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.translation; + +public class B_BaseClass { + + public int f1; + public char f2; + + public int m1() { + return 5; + } + + public int m2() { + return 42; + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java b/enigma/src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java new file mode 100644 index 00000000..9d74e443 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.translation; + +public class C_SubClass extends B_BaseClass { + + public char f2; // shadows B_BaseClass.f2 + public int f3; + public int f4; + + @Override + public int m1() { + return 32; + } + + public int m3() { + return 7; + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java b/enigma/src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java new file mode 100644 index 00000000..99c83bbf --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.translation; + +import java.util.ArrayList; +import java.util.List; + +public class D_AnonymousTesting { + + public List getObjs() { + List objs = new ArrayList(); + objs.add(new Object() { + @Override + public String toString() { + return "Object!"; + } + }); + return objs; + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java b/enigma/src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java new file mode 100644 index 00000000..0b8cf2a5 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.translation; + +import java.util.Iterator; + +public class E_Bridges implements Iterator { + + @Override + public boolean hasNext() { + return false; + } + + @Override + public String next() { + // the compiler will generate a bridge for this method + return "foo"; + } + + @Override + public void remove() { + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java b/enigma/src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java new file mode 100644 index 00000000..8a92792a --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.translation; + +@SuppressWarnings("FinalizeCalledExplicitly") +public class F_ObjectMethods { + + public void callEmAll() + throws Throwable { + clone(); + equals(this); + finalize(); + getClass(); + hashCode(); + notify(); + notifyAll(); + toString(); + wait(); + wait(0); + wait(0, 0); + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/translation/G_OuterClass.java b/enigma/src/test/java/cuchaz/enigma/inputs/translation/G_OuterClass.java new file mode 100644 index 00000000..a1e6a85c --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/translation/G_OuterClass.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.translation; + +public class G_OuterClass { + + public class A_InnerClass { + + public int f1; + public String f2; + + public void m1() {} + + public class A_InnerInnerClass { + + public int f3; + + public void m2() {} + } + } + + public class B_NamelessClass { + public class A_NamedInnerClass { + public int f4; + } + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java b/enigma/src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java new file mode 100644 index 00000000..013c55ae --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.translation; + +public class H_NamelessClass { + + public class A_InnerClass { + + public int f1; + public String f2; + + public void m1() {} + + public class A_InnerInnerClass { + + public int f3; + + public void m2() {} + } + } + + public class B_NamelessClass { + public class A_NamedInnerClass { + public int f4; + + public class A_AnotherInnerClass {} + + public class B_YetAnotherInnerClass {} + } + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/inputs/translation/I_Generics.java b/enigma/src/test/java/cuchaz/enigma/inputs/translation/I_Generics.java new file mode 100644 index 00000000..fd2ebdd5 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/inputs/translation/I_Generics.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.inputs.translation; + +import java.util.List; +import java.util.Map; + +public class I_Generics { + + public List f1; + public List f2; + public Map f3; + public B_Generic f5; + public B_Generic f6; + + public class A_Type { + } + + public class B_Generic { + public T f4; + + public T m1() { + return null; + } + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestComments.java b/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestComments.java new file mode 100644 index 00000000..e8319430 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestComments.java @@ -0,0 +1,39 @@ +package cuchaz.enigma.translation.mapping; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat; +import cuchaz.enigma.translation.mapping.serde.MappingParseException; +import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; +import cuchaz.enigma.translation.mapping.serde.enigma.EnigmaMappingsReader; +import cuchaz.enigma.translation.mapping.serde.tinyv2.TinyV2Writer; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import org.junit.Test; + +public class TestComments { + private static Path DIRECTORY; + + static { + try { + DIRECTORY = Paths.get(TestTinyV2InnerClasses.class.getResource("/comments/").toURI()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + @Test + public void testParseAndWrite() throws IOException, MappingParseException { + ProgressListener progressListener = ProgressListener.none(); + MappingSaveParameters params = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); + EntryTree mappings = EnigmaMappingsReader.DIRECTORY.read( + DIRECTORY, progressListener, params); + + new TinyV2Writer("intermediary", "named") + .write(mappings, DIRECTORY.resolve("convertedtiny.tiny"), progressListener, params); + } + +} \ No newline at end of file diff --git a/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestTinyV2InnerClasses.java b/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestTinyV2InnerClasses.java new file mode 100644 index 00000000..65941e54 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestTinyV2InnerClasses.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + * + * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.translation.mapping; + +import cuchaz.enigma.Enigma; +import cuchaz.enigma.EnigmaProject; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.mapping.serde.enigma.EnigmaMappingsReader; + +import java.nio.file.Path; +import java.nio.file.Paths; + +public final class TestTinyV2InnerClasses { + private Path jar; + private Path mappings; + + public TestTinyV2InnerClasses() throws Exception { + jar = Paths.get("build/test-obf/innerClasses.jar"); + mappings = Paths.get(TestTinyV2InnerClasses.class.getResource("/tinyV2InnerClasses/").toURI()); + } + +// @Test + public void testMappings() throws Exception { + EnigmaProject project = Enigma.create().openJar(jar, ProgressListener.none()); + project.setMappings(EnigmaMappingsReader.DIRECTORY.read(mappings, ProgressListener.none(), project.getEnigma().getProfile().getMappingSaveParameters())); + + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestV2Main.java b/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestV2Main.java new file mode 100644 index 00000000..6e4d7b99 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestV2Main.java @@ -0,0 +1,23 @@ +package cuchaz.enigma.translation.mapping; + +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat; +import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; +import cuchaz.enigma.translation.mapping.serde.enigma.EnigmaMappingsReader; +import cuchaz.enigma.translation.mapping.serde.tinyv2.TinyV2Writer; +import cuchaz.enigma.translation.mapping.tree.EntryTree; + +import java.nio.file.Path; +import java.nio.file.Paths; + +public final class TestV2Main { + public static void main(String... args) throws Exception { + Path path = Paths.get(TestV2Main.class.getResource("/tinyV2InnerClasses/").toURI()); + + MappingSaveParameters parameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); + + EntryTree tree = EnigmaMappingsReader.DIRECTORY.read(path, ProgressListener.none(), parameters); + + new TinyV2Writer("obf", "deobf").write(tree, Paths.get("currentYarn.tiny"), ProgressListener.none(), parameters); + } +} diff --git a/enigma/src/test/resources/comments/test.mapping b/enigma/src/test/resources/comments/test.mapping new file mode 100644 index 00000000..d1345583 --- /dev/null +++ b/enigma/src/test/resources/comments/test.mapping @@ -0,0 +1,18 @@ +CLASS net/minecraft/class_1158 net/minecraft/util/math/Quaternion + COMMENT it circel + COMMENT next line + FIELD field_21493 IDENTITY Lnet/minecraft/class_1158; + COMMENT moar comment thing + COMMENT near field + METHOD foo bar (FFFF)V + COMMENT method comment + COMMENT second line + COMMENT third line + ARG 1 b + COMMENT arg comment + CLASS old new + COMMENT inner comment + FIELD field_19263 iterator Lnet/minecraft/class_3980; + METHOD tryAdvance (Ljava/util/function/Consumer;)Z + ARG 1 consumer + COMMENT very inner comment \ No newline at end of file diff --git a/enigma/src/test/resources/proguard-build.conf b/enigma/src/test/resources/proguard-build.conf new file mode 100644 index 00000000..691d8a29 --- /dev/null +++ b/enigma/src/test/resources/proguard-build.conf @@ -0,0 +1,6 @@ +-dontoptimize +-dontobfuscate +-dontwarn +-keep class cuchaz.enigma.Main { static void main(java.lang.String[]); } +-keep class cuchaz.enigma.command.Main { static void main(java.lang.String[]); } +-keep class de.sciss.syntaxpane.** { *; } diff --git a/enigma/src/test/resources/proguard-test.conf b/enigma/src/test/resources/proguard-test.conf new file mode 100644 index 00000000..9411d269 --- /dev/null +++ b/enigma/src/test/resources/proguard-test.conf @@ -0,0 +1,8 @@ +-overloadaggressively +-repackageclasses +-allowaccessmodification +-dontoptimize +-dontshrink +-keepparameternames +-keepattributes +-keep class cuchaz.enigma.inputs.Keep diff --git a/enigma/src/test/resources/tinyV2InnerClasses/c.mapping b/enigma/src/test/resources/tinyV2InnerClasses/c.mapping new file mode 100644 index 00000000..f9b04428 --- /dev/null +++ b/enigma/src/test/resources/tinyV2InnerClasses/c.mapping @@ -0,0 +1,2 @@ +CLASS c + CLASS a Kid diff --git a/enigma/src/test/resources/tinyV2InnerClasses/cuchaz/enigma/Dad.mapping b/enigma/src/test/resources/tinyV2InnerClasses/cuchaz/enigma/Dad.mapping new file mode 100644 index 00000000..8d43ba90 --- /dev/null +++ b/enigma/src/test/resources/tinyV2InnerClasses/cuchaz/enigma/Dad.mapping @@ -0,0 +1,5 @@ +CLASS f cuchaz/enigma/Dad + CLASS a One + CLASS a Two + CLASS a + FIELD a value I diff --git a/enigma/src/test/resources/translation.mappings b/enigma/src/test/resources/translation.mappings new file mode 100644 index 00000000..c08765c7 --- /dev/null +++ b/enigma/src/test/resources/translation.mappings @@ -0,0 +1,41 @@ +CLASS a deobf/A_Basic + FIELD a f1 I + FIELD a f2 F + FIELD a f3 Ljava/lang/String; + METHOD a m1 ()V + METHOD a m2 ()I + METHOD a m3 (I)V + METHOD a m4 (I)I +CLASS b deobf/B_BaseClass + FIELD a f1 I + FIELD a f2 C + METHOD a m1 ()I + METHOD b m2 ()I +CLASS c deobf/C_SubClass + FIELD b f2 C + FIELD b f3 I + FIELD c f4 I + METHOD a m1 ()I + METHOD c m3 ()I +CLASS g deobf/G_OuterClass + CLASS g$a A_InnerClass + CLASS g$a$a A_InnerInnerClass + FIELD a f3 I + METHOD a m2 ()V + FIELD a f1 I + FIELD a f2 Ljava/lang/String; + METHOD a m1 ()V + CLASS g$b + CLASS g$b$a A_NamedInnerClass + FIELD a f4 I +CLASS h +CLASS i deobf/I_Generics + CLASS i$a A_Type + CLASS i$b B_Generic + FIELD a f4 Ljava/lang/Object; + METHOD a m1 ()Ljava/lang/Object; + FIELD a f1 Ljava/util/List; + FIELD b f2 Ljava/util/List; + FIELD a f3 Ljava/util/Map; + FIELD a f5 Li$b; + FIELD b f6 Li$b; diff --git a/settings.gradle b/settings.gradle index 8fa1712a..87540276 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,4 @@ -rootProject.name = 'enigma' \ No newline at end of file +include 'enigma' +include 'enigma-swing' +include 'enigma-server' +include 'enigma-cli' diff --git a/src/main/java/cuchaz/enigma/ClassProvider.java b/src/main/java/cuchaz/enigma/ClassProvider.java deleted file mode 100644 index 2b913792..00000000 --- a/src/main/java/cuchaz/enigma/ClassProvider.java +++ /dev/null @@ -1,10 +0,0 @@ -package cuchaz.enigma; - -import org.objectweb.asm.tree.ClassNode; - -import javax.annotation.Nullable; - -public interface ClassProvider { - @Nullable - ClassNode getClassNode(String name); -} diff --git a/src/main/java/cuchaz/enigma/CommandMain.java b/src/main/java/cuchaz/enigma/CommandMain.java deleted file mode 100644 index 93b013d5..00000000 --- a/src/main/java/cuchaz/enigma/CommandMain.java +++ /dev/null @@ -1,105 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -import cuchaz.enigma.command.*; - -import java.util.LinkedHashMap; -import java.util.Locale; -import java.util.Map; - -public class CommandMain { - - private static final Map COMMANDS = new LinkedHashMap<>(); - - public static void main(String... args) throws Exception { - try { - // process the command - if (args.length < 1) - throw new IllegalArgumentException("Requires a command"); - String command = args[0].toLowerCase(Locale.ROOT); - - Command cmd = COMMANDS.get(command); - if (cmd == null) - throw new IllegalArgumentException("Command not recognized: " + command); - - if (!cmd.isValidArgument(args.length - 1)) { - throw new CommandHelpException(cmd); - } - - String[] cmdArgs = new String[args.length - 1]; - System.arraycopy(args, 1, cmdArgs, 0, args.length - 1); - - try { - cmd.run(cmdArgs); - } catch (Exception ex) { - throw new CommandHelpException(cmd, ex); - } - } catch (CommandHelpException ex) { - System.err.println(ex.getMessage()); - System.out.println(String.format("%s - %s", Constants.NAME, Constants.VERSION)); - System.out.println("Command " + ex.command.name + " has encountered an error! Usage:"); - printHelp(ex.command); - System.exit(1); - } catch (IllegalArgumentException ex) { - System.err.println(ex.getMessage()); - printHelp(); - System.exit(1); - } - } - - private static void printHelp() { - System.out.println(String.format("%s - %s", Constants.NAME, Constants.VERSION)); - System.out.println("Usage:"); - System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain "); - System.out.println("\twhere is one of:"); - - for (Command command : COMMANDS.values()) { - printHelp(command); - } - } - - private static void printHelp(Command command) { - System.out.println("\t\t" + command.name + " " + command.getUsage()); - } - - private static void register(Command command) { - Command old = COMMANDS.put(command.name, command); - if (old != null) { - System.err.println("Command " + old + " with name " + command.name + " has been substituted by " + command); - } - } - - static { - register(new DeobfuscateCommand()); - register(new DecompileCommand()); - register(new ConvertMappingsCommand()); - register(new ComposeMappingsCommand()); - register(new InvertMappingsCommand()); - register(new CheckMappingsCommand()); - register(new MapSpecializedMethodsCommand()); - } - - private static final class CommandHelpException extends IllegalArgumentException { - - final Command command; - - CommandHelpException(Command command) { - this.command = command; - } - - CommandHelpException(Command command, Throwable cause) { - super(cause); - this.command = command; - } - } -} diff --git a/src/main/java/cuchaz/enigma/Constants.java b/src/main/java/cuchaz/enigma/Constants.java deleted file mode 100644 index 577315ff..00000000 --- a/src/main/java/cuchaz/enigma/Constants.java +++ /dev/null @@ -1,20 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -public class Constants { - public static final String NAME = "Enigma"; - public static final String VERSION = "@VERSION@/Fabric"; - public static final String URL = "https://fabricmc.net"; - public static final int MiB = 1024 * 1024; // 1 mebibyte - public static final int KiB = 1024; // 1 kebibyte -} diff --git a/src/main/java/cuchaz/enigma/Enigma.java b/src/main/java/cuchaz/enigma/Enigma.java deleted file mode 100644 index f5f06491..00000000 --- a/src/main/java/cuchaz/enigma/Enigma.java +++ /dev/null @@ -1,121 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableListMultimap; -import cuchaz.enigma.analysis.ClassCache; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.api.EnigmaPlugin; -import cuchaz.enigma.api.EnigmaPluginContext; -import cuchaz.enigma.api.service.EnigmaService; -import cuchaz.enigma.api.service.EnigmaServiceFactory; -import cuchaz.enigma.api.service.EnigmaServiceType; -import cuchaz.enigma.api.service.JarIndexerService; -import cuchaz.enigma.utils.Utils; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; -import java.util.ServiceLoader; - -public class Enigma { - private final EnigmaProfile profile; - private final EnigmaServices services; - - private Enigma(EnigmaProfile profile, EnigmaServices services) { - this.profile = profile; - this.services = services; - } - - public static Enigma create() { - return new Builder().build(); - } - - public static Builder builder() { - return new Builder(); - } - - public EnigmaProject openJar(Path path, ProgressListener progress) throws IOException { - ClassCache classCache = ClassCache.of(path); - JarIndex jarIndex = classCache.index(progress); - - services.get(JarIndexerService.TYPE).forEach(indexer -> indexer.acceptJar(classCache, jarIndex)); - - return new EnigmaProject(this, classCache, jarIndex, Utils.zipSha1(path)); - } - - public EnigmaProfile getProfile() { - return profile; - } - - public EnigmaServices getServices() { - return services; - } - - public static class Builder { - private EnigmaProfile profile = EnigmaProfile.EMPTY; - private Iterable plugins = ServiceLoader.load(EnigmaPlugin.class); - - private Builder() { - } - - public Builder setProfile(EnigmaProfile profile) { - Preconditions.checkNotNull(profile, "profile cannot be null"); - this.profile = profile; - return this; - } - - public Builder setPlugins(Iterable plugins) { - Preconditions.checkNotNull(plugins, "plugins cannot be null"); - this.plugins = plugins; - return this; - } - - public Enigma build() { - PluginContext pluginContext = new PluginContext(profile); - for (EnigmaPlugin plugin : plugins) { - plugin.init(pluginContext); - } - - EnigmaServices services = pluginContext.buildServices(); - return new Enigma(profile, services); - } - } - - private static class PluginContext implements EnigmaPluginContext { - private final EnigmaProfile profile; - - private final ImmutableListMultimap.Builder, EnigmaService> services = ImmutableListMultimap.builder(); - - PluginContext(EnigmaProfile profile) { - this.profile = profile; - } - - @Override - public void registerService(String id, EnigmaServiceType serviceType, EnigmaServiceFactory factory) { - List serviceProfiles = profile.getServiceProfiles(serviceType); - - for (EnigmaProfile.Service serviceProfile : serviceProfiles) { - if (serviceProfile.matches(id)) { - T service = factory.create(serviceProfile::getArgument); - services.put(serviceType, service); - break; - } - } - } - - EnigmaServices buildServices() { - return new EnigmaServices(services.build()); - } - } -} diff --git a/src/main/java/cuchaz/enigma/EnigmaProfile.java b/src/main/java/cuchaz/enigma/EnigmaProfile.java deleted file mode 100644 index 09b90f5f..00000000 --- a/src/main/java/cuchaz/enigma/EnigmaProfile.java +++ /dev/null @@ -1,131 +0,0 @@ -package cuchaz.enigma; - -import com.google.common.collect.ImmutableMap; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.google.gson.annotations.SerializedName; -import com.google.gson.reflect.TypeToken; -import cuchaz.enigma.api.service.EnigmaServiceType; -import cuchaz.enigma.translation.mapping.MappingFileNameFormat; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; - -import javax.annotation.Nullable; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.lang.reflect.Type; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -public final class EnigmaProfile { - public static final EnigmaProfile EMPTY = new EnigmaProfile(new ServiceContainer(ImmutableMap.of())); - - private static final MappingSaveParameters DEFAULT_MAPPING_SAVE_PARAMETERS = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); - private static final Gson GSON = new GsonBuilder() - .registerTypeAdapter(ServiceContainer.class, (JsonDeserializer) EnigmaProfile::loadServiceContainer) - .create(); - private static final Type SERVICE_LIST_TYPE = new TypeToken>() { - }.getType(); - - @SerializedName("services") - private final ServiceContainer serviceProfiles; - - @SerializedName("mapping_save_parameters") - private final MappingSaveParameters mappingSaveParameters = null; - - private EnigmaProfile(ServiceContainer serviceProfiles) { - this.serviceProfiles = serviceProfiles; - } - - public static EnigmaProfile read(@Nullable Path file) throws IOException { - if (file != null) { - try (BufferedReader reader = Files.newBufferedReader(file)) { - return EnigmaProfile.parse(reader); - } - } else { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(Main.class.getResourceAsStream("/profile.json"), StandardCharsets.UTF_8))) { - return EnigmaProfile.parse(reader); - } catch (IOException ex) { - System.err.println("Failed to load default profile, will use empty profile: " + ex.getMessage()); - return EnigmaProfile.EMPTY; - } - } - } - - public static EnigmaProfile parse(Reader reader) { - return GSON.fromJson(reader, EnigmaProfile.class); - } - - private static ServiceContainer loadServiceContainer(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - if (!json.isJsonObject()) { - throw new JsonParseException("services must be an Object!"); - } - - JsonObject object = json.getAsJsonObject(); - - ImmutableMap.Builder> builder = ImmutableMap.builder(); - - for (Map.Entry entry : object.entrySet()) { - JsonElement value = entry.getValue(); - if (value.isJsonObject()) { - builder.put(entry.getKey(), Collections.singletonList(GSON.fromJson(value, Service.class))); - } else if (value.isJsonArray()) { - builder.put(entry.getKey(), GSON.fromJson(value, SERVICE_LIST_TYPE)); - } else { - throw new JsonParseException(String.format("Don't know how to convert %s to a list of service!", value)); - } - } - - return new ServiceContainer(builder.build()); - } - - public List getServiceProfiles(EnigmaServiceType serviceType) { - return serviceProfiles.get(serviceType.key); - } - - public MappingSaveParameters getMappingSaveParameters() { - //noinspection ConstantConditions - return mappingSaveParameters == null ? EnigmaProfile.DEFAULT_MAPPING_SAVE_PARAMETERS : mappingSaveParameters; - } - - public static class Service { - private final String id; - private final Map args; - - Service(String id, Map args) { - this.id = id; - this.args = args; - } - - public boolean matches(String id) { - return this.id.equals(id); - } - - public Optional getArgument(String key) { - return args != null ? Optional.ofNullable(args.get(key)) : Optional.empty(); - } - } - - static final class ServiceContainer { - private final Map> services; - - ServiceContainer(Map> services) { - this.services = services; - } - - List get(String key) { - return services.getOrDefault(key, Collections.emptyList()); - } - } -} diff --git a/src/main/java/cuchaz/enigma/EnigmaProject.java b/src/main/java/cuchaz/enigma/EnigmaProject.java deleted file mode 100644 index b345fb39..00000000 --- a/src/main/java/cuchaz/enigma/EnigmaProject.java +++ /dev/null @@ -1,276 +0,0 @@ -package cuchaz.enigma; - -import com.google.common.base.Functions; -import com.google.common.base.Preconditions; -import cuchaz.enigma.analysis.ClassCache; -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.api.service.NameProposalService; -import cuchaz.enigma.bytecode.translators.SourceFixVisitor; -import cuchaz.enigma.bytecode.translators.TranslationClassVisitor; -import cuchaz.enigma.network.EnigmaServer; -import cuchaz.enigma.source.*; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.*; -import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree; -import cuchaz.enigma.translation.mapping.tree.EntryTree; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; -import cuchaz.enigma.utils.I18n; - -import cuchaz.enigma.utils.Utils; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.ClassNode; - -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collection; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.jar.JarEntry; -import java.util.jar.JarOutputStream; -import java.util.stream.Collectors; - -public class EnigmaProject { - private final Enigma enigma; - - private final ClassCache classCache; - private final JarIndex jarIndex; - private final byte[] jarChecksum; - - private EntryRemapper mapper; - - public EnigmaProject(Enigma enigma, ClassCache classCache, JarIndex jarIndex, byte[] jarChecksum) { - Preconditions.checkArgument(jarChecksum.length == EnigmaServer.CHECKSUM_SIZE); - this.enigma = enigma; - this.classCache = classCache; - this.jarIndex = jarIndex; - this.jarChecksum = jarChecksum; - - this.mapper = EntryRemapper.empty(jarIndex); - } - - public void setMappings(EntryTree mappings) { - if (mappings != null) { - mapper = EntryRemapper.mapped(jarIndex, mappings); - } else { - mapper = EntryRemapper.empty(jarIndex); - } - } - - public Enigma getEnigma() { - return enigma; - } - - public ClassCache getClassCache() { - return classCache; - } - - public JarIndex getJarIndex() { - return jarIndex; - } - - public byte[] getJarChecksum() { - return jarChecksum; - } - - public EntryRemapper getMapper() { - return mapper; - } - - public void dropMappings(ProgressListener progress) { - DeltaTrackingTree mappings = mapper.getObfToDeobf(); - - Collection> dropped = dropMappings(mappings, progress); - for (Entry entry : dropped) { - mappings.trackChange(entry); - } - } - - private Collection> dropMappings(EntryTree mappings, ProgressListener progress) { - // drop mappings that don't match the jar - MappingsChecker checker = new MappingsChecker(jarIndex, mappings); - MappingsChecker.Dropped dropped = checker.dropBrokenMappings(progress); - - Map, String> droppedMappings = dropped.getDroppedMappings(); - for (Map.Entry, String> mapping : droppedMappings.entrySet()) { - System.out.println("WARNING: Couldn't find " + mapping.getKey() + " (" + mapping.getValue() + ") in jar. Mapping was dropped."); - } - - return droppedMappings.keySet(); - } - - public boolean isRenamable(Entry obfEntry) { - if (obfEntry instanceof MethodEntry) { - // HACKHACK: Object methods are not obfuscated identifiers - MethodEntry obfMethodEntry = (MethodEntry) obfEntry; - String name = obfMethodEntry.getName(); - String sig = obfMethodEntry.getDesc().toString(); - if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) { - return false; - } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) { - return false; - } else if (name.equals("finalize") && sig.equals("()V")) { - return false; - } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) { - return false; - } else if (name.equals("hashCode") && sig.equals("()I")) { - return false; - } else if (name.equals("notify") && sig.equals("()V")) { - return false; - } else if (name.equals("notifyAll") && sig.equals("()V")) { - return false; - } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) { - return false; - } else if (name.equals("wait") && sig.equals("()V")) { - return false; - } else if (name.equals("wait") && sig.equals("(J)V")) { - return false; - } else if (name.equals("wait") && sig.equals("(JI)V")) { - return false; - } - } else if (obfEntry instanceof LocalVariableEntry && !((LocalVariableEntry) obfEntry).isArgument()) { - return false; - } - - return this.jarIndex.getEntryIndex().hasEntry(obfEntry); - } - - public boolean isRenamable(EntryReference, Entry> obfReference) { - return obfReference.isNamed() && isRenamable(obfReference.getNameableEntry()); - } - - public JarExport exportRemappedJar(ProgressListener progress) { - Collection classEntries = jarIndex.getEntryIndex().getClasses(); - - NameProposalService[] nameProposalServices = getEnigma().getServices().get(NameProposalService.TYPE).toArray(new NameProposalService[0]); - Translator deobfuscator = nameProposalServices.length == 0 ? mapper.getDeobfuscator() : new ProposingTranslator(mapper, nameProposalServices); - - AtomicInteger count = new AtomicInteger(); - progress.init(classEntries.size(), I18n.translate("progress.classes.deobfuscating")); - - Map compiled = classEntries.parallelStream() - .map(entry -> { - ClassEntry translatedEntry = deobfuscator.translate(entry); - progress.step(count.getAndIncrement(), translatedEntry.toString()); - - ClassNode node = classCache.getClassNode(entry.getFullName()); - if (node != null) { - ClassNode translatedNode = new ClassNode(); - node.accept(new TranslationClassVisitor(deobfuscator, Utils.ASM_VERSION, new SourceFixVisitor(Utils.ASM_VERSION, translatedNode, jarIndex))); - return translatedNode; - } - - return null; - }) - .filter(Objects::nonNull) - .collect(Collectors.toMap(n -> n.name, Functions.identity())); - - return new JarExport(jarIndex, compiled); - } - - public static final class JarExport { - private final JarIndex jarIndex; - private final Map compiled; - - JarExport(JarIndex jarIndex, Map compiled) { - this.jarIndex = jarIndex; - this.compiled = compiled; - } - - public void write(Path path, ProgressListener progress) throws IOException { - progress.init(this.compiled.size(), I18n.translate("progress.jar.writing")); - - try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(path))) { - AtomicInteger count = new AtomicInteger(); - - for (ClassNode node : this.compiled.values()) { - progress.step(count.getAndIncrement(), node.name); - - String entryName = node.name.replace('.', '/') + ".class"; - - ClassWriter writer = new ClassWriter(0); - node.accept(writer); - - out.putNextEntry(new JarEntry(entryName)); - out.write(writer.toByteArray()); - out.closeEntry(); - } - } - } - - public SourceExport decompile(ProgressListener progress, DecompilerService decompilerService) { - Collection classes = this.compiled.values().stream() - .filter(classNode -> classNode.name.indexOf('$') == -1) - .collect(Collectors.toList()); - - progress.init(classes.size(), I18n.translate("progress.classes.decompiling")); - - //create a common instance outside the loop as mappings shouldn't be changing while this is happening - Decompiler decompiler = decompilerService.create(compiled::get, new SourceSettings(false, false)); - - AtomicInteger count = new AtomicInteger(); - - Collection decompiled = classes.parallelStream() - .map(translatedNode -> { - progress.step(count.getAndIncrement(), translatedNode.name); - - String source = decompileClass(translatedNode, decompiler); - return new ClassSource(translatedNode.name, source); - }) - .collect(Collectors.toList()); - - return new SourceExport(decompiled); - } - - private String decompileClass(ClassNode translatedNode, Decompiler decompiler) { - return decompiler.getSource(translatedNode.name).asString(); - } - } - - public static final class SourceExport { - private final Collection decompiled; - - SourceExport(Collection decompiled) { - this.decompiled = decompiled; - } - - public void write(Path path, ProgressListener progress) throws IOException { - progress.init(decompiled.size(), I18n.translate("progress.sources.writing")); - - int count = 0; - for (ClassSource source : decompiled) { - progress.step(count++, source.name); - - Path sourcePath = source.resolvePath(path); - source.writeTo(sourcePath); - } - } - } - - private static class ClassSource { - private final String name; - private final String source; - - ClassSource(String name, String source) { - this.name = name; - this.source = source; - } - - void writeTo(Path path) throws IOException { - Files.createDirectories(path.getParent()); - try (BufferedWriter writer = Files.newBufferedWriter(path)) { - writer.write(source); - } - } - - Path resolvePath(Path root) { - return root.resolve(name.replace('.', '/') + ".java"); - } - } -} diff --git a/src/main/java/cuchaz/enigma/EnigmaServices.java b/src/main/java/cuchaz/enigma/EnigmaServices.java deleted file mode 100644 index df3b7bba..00000000 --- a/src/main/java/cuchaz/enigma/EnigmaServices.java +++ /dev/null @@ -1,20 +0,0 @@ -package cuchaz.enigma; - -import com.google.common.collect.ImmutableListMultimap; -import cuchaz.enigma.api.service.EnigmaService; -import cuchaz.enigma.api.service.EnigmaServiceType; - -import java.util.List; - -public final class EnigmaServices { - private final ImmutableListMultimap, EnigmaService> services; - - EnigmaServices(ImmutableListMultimap, EnigmaService> services) { - this.services = services; - } - - @SuppressWarnings("unchecked") - public List get(EnigmaServiceType type) { - return (List) services.get(type); - } -} diff --git a/src/main/java/cuchaz/enigma/ExceptionIgnorer.java b/src/main/java/cuchaz/enigma/ExceptionIgnorer.java deleted file mode 100644 index 84331cc1..00000000 --- a/src/main/java/cuchaz/enigma/ExceptionIgnorer.java +++ /dev/null @@ -1,35 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -public class ExceptionIgnorer { - - public static boolean shouldIgnore(Throwable t) { - - // is this that pesky concurrent access bug in the highlight painter system? - // (ancient ui code is ancient) - if (t instanceof ArrayIndexOutOfBoundsException) { - StackTraceElement[] stackTrace = t.getStackTrace(); - if (stackTrace.length > 1) { - - // does this stack frame match javax.swing.text.DefaultHighlighter.paint*() ? - StackTraceElement frame = stackTrace[1]; - if (frame.getClassName().equals("javax.swing.text.DefaultHighlighter") && frame.getMethodName().startsWith("paint")) { - return true; - } - } - } - - return false; - } - -} diff --git a/src/main/java/cuchaz/enigma/Main.java b/src/main/java/cuchaz/enigma/Main.java deleted file mode 100644 index 7c87669f..00000000 --- a/src/main/java/cuchaz/enigma/Main.java +++ /dev/null @@ -1,116 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.gui.GuiController; -import cuchaz.enigma.translation.mapping.serde.MappingFormat; - -import joptsimple.*; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -import com.google.common.io.MoreFiles; - -public class Main { - - public static void main(String[] args) throws IOException { - OptionParser parser = new OptionParser(); - - OptionSpec jar = parser.accepts("jar", "Jar file to open at startup") - .withRequiredArg() - .withValuesConvertedBy(PathConverter.INSTANCE); - - OptionSpec mappings = parser.accepts("mappings", "Mappings file to open at startup") - .withRequiredArg() - .withValuesConvertedBy(PathConverter.INSTANCE); - - OptionSpec profile = parser.accepts("profile", "Profile json to apply at startup") - .withRequiredArg() - .withValuesConvertedBy(PathConverter.INSTANCE); - - parser.accepts("help", "Displays help information"); - - try { - OptionSet options = parser.parse(args); - - if (options.has("help")) { - parser.printHelpOn(System.out); - return; - } - - EnigmaProfile parsedProfile = EnigmaProfile.read(options.valueOf(profile)); - - Gui gui = new Gui(parsedProfile); - GuiController controller = gui.getController(); - - if (options.has(jar)) { - Path jarPath = options.valueOf(jar); - controller.openJar(jarPath) - .whenComplete((v, t) -> { - if (options.has(mappings)) { - Path mappingsPath = options.valueOf(mappings); - if (Files.isDirectory(mappingsPath)) { - controller.openMappings(MappingFormat.ENIGMA_DIRECTORY, mappingsPath); - } else if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(mappingsPath))) { - controller.openMappings(MappingFormat.ENIGMA_ZIP, mappingsPath); - } else { - controller.openMappings(MappingFormat.ENIGMA_FILE, mappingsPath); - } - } - }); - } - } catch (OptionException e) { - System.out.println("Invalid arguments: " + e.getMessage()); - System.out.println(); - parser.printHelpOn(System.out); - } - } - - public static class PathConverter implements ValueConverter { - public static final ValueConverter INSTANCE = new PathConverter(); - - PathConverter() { - } - - @Override - public Path convert(String path) { - // expand ~ to the home dir - if (path.startsWith("~")) { - // get the home dir - Path dirHome = Paths.get(System.getProperty("user.home")); - - // is the path just ~/ or is it ~user/ ? - if (path.startsWith("~/")) { - return dirHome.resolve(path.substring(2)); - } else { - return dirHome.getParent().resolve(path.substring(1)); - } - } - - return Paths.get(path); - } - - @Override - public Class valueType() { - return Path.class; - } - - @Override - public String valuePattern() { - return "path"; - } - } -} diff --git a/src/main/java/cuchaz/enigma/ProgressListener.java b/src/main/java/cuchaz/enigma/ProgressListener.java deleted file mode 100644 index 6da3b81a..00000000 --- a/src/main/java/cuchaz/enigma/ProgressListener.java +++ /dev/null @@ -1,19 +0,0 @@ -package cuchaz.enigma; - -public interface ProgressListener { - static ProgressListener none() { - return new ProgressListener() { - @Override - public void init(int totalWork, String title) { - } - - @Override - public void step(int numDone, String message) { - } - }; - } - - void init(int totalWork, String title); - - void step(int numDone, String message); -} diff --git a/src/main/java/cuchaz/enigma/ProposingTranslator.java b/src/main/java/cuchaz/enigma/ProposingTranslator.java deleted file mode 100644 index 018fbfda..00000000 --- a/src/main/java/cuchaz/enigma/ProposingTranslator.java +++ /dev/null @@ -1,53 +0,0 @@ -package cuchaz.enigma; - -import cuchaz.enigma.api.service.NameProposalService; -import cuchaz.enigma.translation.Translatable; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryRemapper; -import cuchaz.enigma.translation.mapping.ResolutionStrategy; -import cuchaz.enigma.translation.representation.entry.Entry; - -import java.util.Arrays; -import java.util.Optional; - -public class ProposingTranslator implements Translator { - private final EntryRemapper mapper; - private final NameProposalService[] nameProposalServices; - - public ProposingTranslator(EntryRemapper mapper, NameProposalService[] nameProposalServices) { - this.mapper = mapper; - this.nameProposalServices = nameProposalServices; - } - - @Override - @SuppressWarnings("unchecked") - public T translate(T translatable) { - if (translatable == null) { - return null; - } - - T deobfuscated = mapper.deobfuscate(translatable); - - if (translatable instanceof Entry && ((Entry) deobfuscated).getName().equals(((Entry) translatable).getName())) { - return mapper.getObfResolver() - .resolveEntry((Entry) translatable, ResolutionStrategy.RESOLVE_ROOT) - .stream() - .map(this::proposeName) - .filter(Optional::isPresent) - .map(Optional::get) - .findFirst() - .map(newName -> (T) ((Entry) deobfuscated).withName(newName)) - .orElse(deobfuscated); - } - - return deobfuscated; - } - - private Optional proposeName(Entry entry) { - return Arrays.stream(nameProposalServices) - .map(service -> service.proposeName(entry, mapper)) - .filter(Optional::isPresent) - .map(Optional::get) - .findFirst(); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/Access.java b/src/main/java/cuchaz/enigma/analysis/Access.java deleted file mode 100644 index 82ca6692..00000000 --- a/src/main/java/cuchaz/enigma/analysis/Access.java +++ /dev/null @@ -1,43 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.analysis; - -import cuchaz.enigma.translation.representation.AccessFlags; - -import java.lang.reflect.Modifier; - -public enum Access { - - PUBLIC, PROTECTED, PACKAGE, PRIVATE; - - public static Access get(AccessFlags flags) { - return get(flags.getFlags()); - } - - public static Access get(int modifiers) { - boolean isPublic = Modifier.isPublic(modifiers); - boolean isProtected = Modifier.isProtected(modifiers); - boolean isPrivate = Modifier.isPrivate(modifiers); - - if (isPublic && !isProtected && !isPrivate) { - return PUBLIC; - } else if (!isPublic && isProtected && !isPrivate) { - return PROTECTED; - } else if (!isPublic && !isProtected && isPrivate) { - return PRIVATE; - } else if (!isPublic && !isProtected && !isPrivate) { - return PACKAGE; - } - // assume public by default - return PUBLIC; - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java b/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java deleted file mode 100644 index dc3f5535..00000000 --- a/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java +++ /dev/null @@ -1,157 +0,0 @@ -package cuchaz.enigma.analysis; - -import cuchaz.enigma.api.EnigmaPlugin; -import cuchaz.enigma.api.EnigmaPluginContext; -import cuchaz.enigma.api.service.JarIndexerService; -import cuchaz.enigma.api.service.NameProposalService; -import cuchaz.enigma.source.DecompilerService; -import cuchaz.enigma.source.Decompilers; -import cuchaz.enigma.source.procyon.ProcyonDecompiler; -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.utils.Pair; -import cuchaz.enigma.utils.Utils; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.FieldVisitor; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.FieldInsnNode; -import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.LdcInsnNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.analysis.Analyzer; -import org.objectweb.asm.tree.analysis.Frame; -import org.objectweb.asm.tree.analysis.SourceInterpreter; -import org.objectweb.asm.tree.analysis.SourceValue; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -public final class BuiltinPlugin implements EnigmaPlugin { - - public BuiltinPlugin() { - } - - @Override - public void init(EnigmaPluginContext ctx) { - registerEnumNamingService(ctx); - registerDecompilerServices(ctx); - } - - private void registerEnumNamingService(EnigmaPluginContext ctx) { - final Map, String> names = new HashMap<>(); - final EnumFieldNameFindingVisitor visitor = new EnumFieldNameFindingVisitor(names); - - ctx.registerService("enigma:enum_initializer_indexer", JarIndexerService.TYPE, ctx1 -> (classCache, jarIndex) -> classCache.visit(() -> visitor, ClassReader.SKIP_FRAMES)); - ctx.registerService("enigma:enum_name_proposer", NameProposalService.TYPE, ctx1 -> (obfEntry, remapper) -> Optional.ofNullable(names.get(obfEntry))); - } - - private void registerDecompilerServices(EnigmaPluginContext ctx) { - ctx.registerService("enigma:procyon", DecompilerService.TYPE, ctx1 -> Decompilers.PROCYON); - ctx.registerService("enigma:cfr", DecompilerService.TYPE, ctx1 -> Decompilers.CFR); - } - - private static final class EnumFieldNameFindingVisitor extends ClassVisitor { - - private ClassEntry clazz; - private String className; - private final Map, String> mappings; - private final Set> enumFields = new HashSet<>(); - private final List classInits = new ArrayList<>(); - - EnumFieldNameFindingVisitor(Map, String> mappings) { - super(Utils.ASM_VERSION); - this.mappings = mappings; - } - - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - super.visit(version, access, name, signature, superName, interfaces); - this.className = name; - this.clazz = new ClassEntry(name); - this.enumFields.clear(); - this.classInits.clear(); - } - - @Override - public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { - if ((access & Opcodes.ACC_ENUM) != 0) { - if (!enumFields.add(new Pair<>(name, descriptor))) { - throw new IllegalArgumentException("Found two enum fields with the same name \"" + name + "\" and desc \"" + descriptor + "\"!"); - } - } - return super.visitField(access, name, descriptor, signature, value); - } - - @Override - public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { - if ("".equals(name)) { - MethodNode node = new MethodNode(api, access, name, descriptor, signature, exceptions); - classInits.add(node); - return node; - } - return super.visitMethod(access, name, descriptor, signature, exceptions); - } - - @Override - public void visitEnd() { - super.visitEnd(); - try { - collectResults(); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - private void collectResults() throws Exception { - String owner = className; - Analyzer analyzer = new Analyzer<>(new SourceInterpreter()); - - for (MethodNode mn : classInits) { - Frame[] frames = analyzer.analyze(className, mn); - - InsnList instrs = mn.instructions; - for (int i = 1; i < instrs.size(); i++) { - AbstractInsnNode instr1 = instrs.get(i - 1); - AbstractInsnNode instr2 = instrs.get(i); - String s = null; - - if (instr2.getOpcode() == Opcodes.PUTSTATIC - && ((FieldInsnNode) instr2).owner.equals(owner) - && enumFields.contains(new Pair<>(((FieldInsnNode) instr2).name, ((FieldInsnNode) instr2).desc)) - && instr1.getOpcode() == Opcodes.INVOKESPECIAL - && "".equals(((MethodInsnNode) instr1).name)) { - - for (int j = 0; j < frames[i - 1].getStackSize(); j++) { - SourceValue sv = frames[i - 1].getStack(j); - for (AbstractInsnNode ci : sv.insns) { - if (ci instanceof LdcInsnNode && ((LdcInsnNode) ci).cst instanceof String) { - //if (s == null || !s.equals(((LdcInsnNode) ci).cst)) { - if (s == null) { - s = (String) (((LdcInsnNode) ci).cst); - // stringsFound++; - } - } - } - } - } - - if (s != null) { - mappings.put(new FieldEntry(clazz, ((FieldInsnNode) instr2).name, new TypeDescriptor(((FieldInsnNode) instr2).desc)), s); - } - // report otherwise? - } - } - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/ClassCache.java b/src/main/java/cuchaz/enigma/analysis/ClassCache.java deleted file mode 100644 index f694bf35..00000000 --- a/src/main/java/cuchaz/enigma/analysis/ClassCache.java +++ /dev/null @@ -1,127 +0,0 @@ -package cuchaz.enigma.analysis; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.collect.ImmutableSet; -import cuchaz.enigma.ClassProvider; -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.bytecode.translators.LocalVariableFixVisitor; -import cuchaz.enigma.utils.Utils; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.ClassNode; - -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.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; - -public final class ClassCache implements AutoCloseable, ClassProvider { - private final FileSystem fileSystem; - private final ImmutableSet classNames; - - private final Cache nodeCache = CacheBuilder.newBuilder() - .maximumSize(128) - .expireAfterAccess(1, TimeUnit.MINUTES) - .build(); - - private ClassCache(FileSystem fileSystem, ImmutableSet classNames) { - this.fileSystem = fileSystem; - this.classNames = classNames; - } - - public static ClassCache of(Path jarPath) throws IOException { - FileSystem fileSystem = FileSystems.newFileSystem(jarPath, (ClassLoader) null); - ImmutableSet classNames = collectClassNames(fileSystem); - - return new ClassCache(fileSystem, classNames); - } - - private static ImmutableSet collectClassNames(FileSystem fileSystem) throws IOException { - ImmutableSet.Builder classNames = ImmutableSet.builder(); - for (Path root : fileSystem.getRootDirectories()) { - Files.walk(root).map(Path::toString) - .forEach(path -> { - if (path.endsWith(".class")) { - String name = path.substring(1, path.length() - ".class".length()); - classNames.add(name); - } - }); - } - - return classNames.build(); - } - - @Nullable - @Override - public ClassNode getClassNode(String name) { - if (!classNames.contains(name)) { - return null; - } - - try { - return nodeCache.get(name, () -> parseNode(name)); - } catch (ExecutionException e) { - throw new RuntimeException(e); - } - } - - private ClassNode parseNode(String name) throws IOException { - ClassReader reader = getReader(name); - - ClassNode node = new ClassNode(); - - LocalVariableFixVisitor visitor = new LocalVariableFixVisitor(Utils.ASM_VERSION, node); - reader.accept(visitor, 0); - - return node; - } - - private ClassReader getReader(String name) throws IOException { - Path path = fileSystem.getPath(name + ".class"); - byte[] bytes = Files.readAllBytes(path); - return new ClassReader(bytes); - } - - public int getClassCount() { - return classNames.size(); - } - - public void visit(Supplier visitorSupplier, int readFlags) { - for (String className : classNames) { - ClassVisitor visitor = visitorSupplier.get(); - - ClassNode cached = nodeCache.getIfPresent(className); - if (cached != null) { - cached.accept(visitor); - continue; - } - - try { - ClassReader reader = getReader(className); - reader.accept(visitor, readFlags); - } catch (IOException e) { - System.out.println("Failed to visit class " + className); - e.printStackTrace(); - } - } - } - - @Override - public void close() throws IOException { - this.fileSystem.close(); - } - - public JarIndex index(ProgressListener progress) { - JarIndex index = JarIndex.empty(); - index.indexJar(this, progress); - return index; - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java deleted file mode 100644 index 0fc44ca6..00000000 --- a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java +++ /dev/null @@ -1,72 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.analysis; - -import com.google.common.collect.Lists; -import cuchaz.enigma.analysis.index.InheritanceIndex; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; - -import javax.swing.tree.DefaultMutableTreeNode; -import java.util.Collection; -import java.util.List; - -public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { - private final Translator translator; - private final ClassEntry entry; - - public ClassImplementationsTreeNode(Translator translator, ClassEntry entry) { - this.translator = translator; - this.entry = entry; - } - - public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) { - // is this the node? - if (node.entry.equals(entry.getParent())) { - return node; - } - - // recurse - for (int i = 0; i < node.getChildCount(); i++) { - ClassImplementationsTreeNode foundNode = findNode((ClassImplementationsTreeNode) node.getChildAt(i), entry); - if (foundNode != null) { - return foundNode; - } - } - return null; - } - - public ClassEntry getClassEntry() { - return this.entry; - } - - @Override - public String toString() { - return translator.translate(entry).toString(); - } - - public void load(JarIndex index) { - // get all method implementations - List nodes = Lists.newArrayList(); - InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); - - Collection inheritors = inheritanceIndex.getChildren(entry); - for (ClassEntry inheritor : inheritors) { - nodes.add(new ClassImplementationsTreeNode(translator, inheritor)); - } - - // add them to this node - nodes.forEach(this::add); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java deleted file mode 100644 index 7904c5f0..00000000 --- a/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java +++ /dev/null @@ -1,72 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.analysis; - -import com.google.common.collect.Lists; -import cuchaz.enigma.analysis.index.InheritanceIndex; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.representation.entry.ClassEntry; - -import javax.swing.tree.DefaultMutableTreeNode; -import java.util.List; - -public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { - private final Translator translator; - private final ClassEntry obfClassEntry; - - public ClassInheritanceTreeNode(Translator translator, String obfClassName) { - this.translator = translator; - this.obfClassEntry = new ClassEntry(obfClassName); - } - - public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) { - // is this the node? - if (node.getObfClassName().equals(entry.getFullName())) { - return node; - } - - // recurse - for (int i = 0; i < node.getChildCount(); i++) { - ClassInheritanceTreeNode foundNode = findNode((ClassInheritanceTreeNode) node.getChildAt(i), entry); - if (foundNode != null) { - return foundNode; - } - } - return null; - } - - public String getObfClassName() { - return this.obfClassEntry.getFullName(); - } - - @Override - public String toString() { - return translator.translate(obfClassEntry).getFullName(); - } - - public void load(InheritanceIndex ancestries, boolean recurse) { - // get all the child nodes - List nodes = Lists.newArrayList(); - for (ClassEntry inheritor : ancestries.getChildren(this.obfClassEntry)) { - nodes.add(new ClassInheritanceTreeNode(translator, inheritor.getFullName())); - } - - // add them to this node - nodes.forEach(this::add); - - if (recurse) { - for (ClassInheritanceTreeNode node : nodes) { - node.load(ancestries, true); - } - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java deleted file mode 100644 index 90d8a6cf..00000000 --- a/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java +++ /dev/null @@ -1,94 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.analysis; - -import com.google.common.collect.Sets; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.analysis.index.ReferenceIndex; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.translation.representation.entry.MethodDefEntry; - -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.TreeNode; -import java.util.Set; - -public class ClassReferenceTreeNode extends DefaultMutableTreeNode - implements ReferenceTreeNode { - - private Translator deobfuscatingTranslator; - private ClassEntry entry; - private EntryReference reference; - - public ClassReferenceTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) { - this.deobfuscatingTranslator = deobfuscatingTranslator; - this.entry = entry; - this.reference = null; - } - - public ClassReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference reference) { - this.deobfuscatingTranslator = deobfuscatingTranslator; - this.entry = reference.entry; - this.reference = reference; - } - - @Override - public ClassEntry getEntry() { - return this.entry; - } - - @Override - public EntryReference getReference() { - return this.reference; - } - - @Override - public String toString() { - if (this.reference != null) { - return String.format("%s", this.deobfuscatingTranslator.translate(this.reference.context)); - } - return this.deobfuscatingTranslator.translate(this.entry).getFullName(); - } - - public void load(JarIndex index, boolean recurse) { - ReferenceIndex referenceIndex = index.getReferenceIndex(); - - // get all the child nodes - for (EntryReference reference : referenceIndex.getReferencesToClass(this.entry)) { - add(new ClassReferenceTreeNode(this.deobfuscatingTranslator, reference)); - } - - if (recurse && this.children != null) { - for (Object child : this.children) { - if (child instanceof ClassReferenceTreeNode) { - ClassReferenceTreeNode node = (ClassReferenceTreeNode) child; - - // don't recurse into ancestor - Set> ancestors = Sets.newHashSet(); - TreeNode n = node; - while (n.getParent() != null) { - n = n.getParent(); - if (n instanceof ClassReferenceTreeNode) { - ancestors.add(((ClassReferenceTreeNode) n).getEntry()); - } - } - if (ancestors.contains(node.getEntry())) { - continue; - } - - node.load(index, true); - } - } - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/EntryReference.java b/src/main/java/cuchaz/enigma/analysis/EntryReference.java deleted file mode 100644 index 2e738c01..00000000 --- a/src/main/java/cuchaz/enigma/analysis/EntryReference.java +++ /dev/null @@ -1,140 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.analysis; - -import cuchaz.enigma.translation.Translatable; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.mapping.EntryMap; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; -import cuchaz.enigma.utils.Utils; - -import java.util.Arrays; -import java.util.List; - -public class EntryReference, C extends Entry> implements Translatable { - - private static final List CONSTRUCTOR_NON_NAMES = Arrays.asList("this", "super", "static"); - public E entry; - public C context; - public ReferenceTargetType targetType; - - private boolean sourceName; - - public EntryReference(E entry, String sourceName) { - this(entry, sourceName, null); - } - - public EntryReference(E entry, String sourceName, C context) { - this(entry, sourceName, context, ReferenceTargetType.none()); - } - - public EntryReference(E entry, String sourceName, C context, ReferenceTargetType targetType) { - if (entry == null) { - throw new IllegalArgumentException("Entry cannot be null!"); - } - - this.entry = entry; - this.context = context; - this.targetType = targetType; - - this.sourceName = sourceName != null && !sourceName.isEmpty(); - if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor() && CONSTRUCTOR_NON_NAMES.contains(sourceName)) { - this.sourceName = false; - } - } - - public EntryReference(E entry, C context, EntryReference other) { - this.entry = entry; - this.context = context; - this.sourceName = other.sourceName; - this.targetType = other.targetType; - } - - public ClassEntry getLocationClassEntry() { - if (context != null) { - return context.getContainingClass(); - } - return entry.getContainingClass(); - } - - public boolean isNamed() { - return this.sourceName; - } - - public Entry getNameableEntry() { - if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor()) { - // renaming a constructor really means renaming the class - return entry.getContainingClass(); - } - return entry; - } - - public String getNameableName() { - return getNameableEntry().getName(); - } - - @Override - public int hashCode() { - if (context != null) { - return Utils.combineHashesOrdered(entry.hashCode(), context.hashCode()); - } - return entry.hashCode(); - } - - @Override - public boolean equals(Object other) { - return other instanceof EntryReference && equals((EntryReference) other); - } - - public boolean equals(EntryReference other) { - // check entry first - boolean isEntrySame = entry.equals(other.entry); - if (!isEntrySame) { - return false; - } - - // check caller - if (context == null && other.context == null) { - return true; - } else if (context != null && other.context != null) { - return context.equals(other.context); - } - return false; - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - buf.append(entry); - - if (context != null) { - buf.append(" called from "); - buf.append(context); - } - - if (targetType != null && targetType.getKind() != ReferenceTargetType.Kind.NONE) { - buf.append(" on target of type "); - buf.append(targetType); - } - - return buf.toString(); - } - - @Override - public Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings) { - return new EntryReference<>(translator.translate(entry), translator.translate(context), this); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java deleted file mode 100644 index 4beab7f8..00000000 --- a/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java +++ /dev/null @@ -1,83 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.analysis; - -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.analysis.index.ReferenceIndex; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.representation.entry.FieldEntry; -import cuchaz.enigma.translation.representation.entry.MethodDefEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; - -import javax.swing.tree.DefaultMutableTreeNode; - -public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode { - - private final Translator translator; - private FieldEntry entry; - private EntryReference reference; - - public FieldReferenceTreeNode(Translator translator, FieldEntry entry) { - this.translator = translator; - this.entry = entry; - this.reference = null; - } - - private FieldReferenceTreeNode(Translator translator, EntryReference reference) { - this.translator = translator; - this.entry = reference.entry; - this.reference = reference; - } - - @Override - public FieldEntry getEntry() { - return this.entry; - } - - @Override - public EntryReference getReference() { - return this.reference; - } - - @Override - public String toString() { - if (this.reference != null) { - return String.format("%s", translator.translate(this.reference.context)); - } - return translator.translate(entry).toString(); - } - - public void load(JarIndex index, boolean recurse) { - ReferenceIndex referenceIndex = index.getReferenceIndex(); - - // get all the child nodes - if (this.reference == null) { - for (EntryReference reference : referenceIndex.getReferencesToField(this.entry)) { - add(new FieldReferenceTreeNode(translator, reference)); - } - } else { - for (EntryReference reference : referenceIndex.getReferencesToMethod(this.reference.context)) { - add(new MethodReferenceTreeNode(translator, reference)); - } - } - - if (recurse && children != null) { - for (Object node : children) { - if (node instanceof MethodReferenceTreeNode) { - ((MethodReferenceTreeNode) node).load(index, true, false); - } else if (node instanceof FieldReferenceTreeNode) { - ((FieldReferenceTreeNode) node).load(index, true); - } - } - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java b/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java deleted file mode 100644 index 80a71546..00000000 --- a/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java +++ /dev/null @@ -1,154 +0,0 @@ -package cuchaz.enigma.analysis; - -import cuchaz.enigma.analysis.index.EntryIndex; -import cuchaz.enigma.analysis.index.InheritanceIndex; -import cuchaz.enigma.translation.representation.AccessFlags; -import cuchaz.enigma.translation.representation.entry.ClassDefEntry; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.utils.Utils; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.analysis.BasicValue; -import org.objectweb.asm.tree.analysis.SimpleVerifier; - -import java.util.Set; - -public class IndexSimpleVerifier extends SimpleVerifier { - private static final Type OBJECT_TYPE = Type.getType("Ljava/lang/Object;"); - private final EntryIndex entryIndex; - private final InheritanceIndex inheritanceIndex; - - public IndexSimpleVerifier(EntryIndex entryIndex, InheritanceIndex inheritanceIndex) { - super(Utils.ASM_VERSION, null, null, null, false); - this.entryIndex = entryIndex; - this.inheritanceIndex = inheritanceIndex; - } - - @Override - protected boolean isSubTypeOf(BasicValue value, BasicValue expected) { - Type expectedType = expected.getType(); - Type type = value.getType(); - switch (expectedType.getSort()) { - case Type.INT: - case Type.FLOAT: - case Type.LONG: - case Type.DOUBLE: - return type.equals(expectedType); - case Type.ARRAY: - case Type.OBJECT: - if (type.equals(NULL_TYPE)) { - return true; - } else if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) { - if (isAssignableFrom(expectedType, type)) { - return true; - } else if (isInterface(expectedType)) { - return isAssignableFrom(OBJECT_TYPE, type); - } else { - return false; - } - } else { - return false; - } - default: - throw new AssertionError(); - } - } - - @Override - protected boolean isInterface(Type type) { - AccessFlags classAccess = entryIndex.getClassAccess(new ClassEntry(type.getInternalName())); - if (classAccess != null) { - return classAccess.isInterface(); - } - - Class clazz = getClass(type); - if (clazz != null) { - return clazz.isInterface(); - } - - return false; - } - - @Override - protected Type getSuperClass(Type type) { - ClassDefEntry definition = entryIndex.getDefinition(new ClassEntry(type.getInternalName())); - if (definition != null) { - return Type.getType('L' + definition.getSuperClass().getFullName() + ';'); - } - - Class clazz = getClass(type); - if (clazz != null) { - return Type.getType(clazz.getSuperclass()); - } - - return OBJECT_TYPE; - } - - @Override - protected boolean isAssignableFrom(Type type1, Type type2) { - if (type1.equals(type2)) { - return true; - } - - if (type2.equals(NULL_TYPE)) { - return true; - } - - if (type1.getSort() == Type.ARRAY) { - return type2.getSort() == Type.ARRAY && isAssignableFrom(Type.getType(type1.getDescriptor().substring(1)), Type.getType(type2.getDescriptor().substring(1))); - } - - if (type2.getSort() == Type.ARRAY) { - return type1.equals(OBJECT_TYPE); - } - - if (type1.getSort() == Type.OBJECT && type2.getSort() == Type.OBJECT) { - if (type1.equals(OBJECT_TYPE)) { - return true; - } - - ClassEntry class1 = new ClassEntry(type1.getInternalName()); - ClassEntry class2 = new ClassEntry(type2.getInternalName()); - - if (entryIndex.hasClass(class1) && entryIndex.hasClass(class2)) { - return inheritanceIndex.getAncestors(class2).contains(class1); - } - - Class class1Class = getClass(Type.getType('L' + class1.getFullName() + ';')); - Class class2Class = getClass(Type.getType('L' + class2.getFullName() + ';')); - - if (class1Class == null) { - return true; // missing classes to find out - } - - if (class2Class != null) { - return class1Class.isAssignableFrom(class2Class); - } - - if (entryIndex.hasClass(class2)) { - Set ancestors = inheritanceIndex.getAncestors(class2); - - for (ClassEntry ancestorEntry : ancestors) { - Class ancestor = getClass(Type.getType('L' + ancestorEntry.getFullName() + ';')); - if (ancestor == null || class1Class.isAssignableFrom(ancestor)) { - return true; // assignable, or missing classes to find out - } - } - - return false; - } - - return true; // missing classes to find out - } - - return false; - } - - @Override - protected final Class getClass(Type type) { - try { - return Class.forName(type.getSort() == Type.ARRAY ? type.getDescriptor().replace('/', '.') : type.getClassName(), false, null); - } catch (ClassNotFoundException e) { - return null; - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java b/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java deleted file mode 100644 index 0c2dfd77..00000000 --- a/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java +++ /dev/null @@ -1,74 +0,0 @@ -package cuchaz.enigma.analysis; - -import com.google.common.collect.Lists; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.mapping.ResolutionStrategy; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; - -import java.util.Collection; -import java.util.List; - -public class IndexTreeBuilder { - private final JarIndex index; - - public IndexTreeBuilder(JarIndex index) { - this.index = index; - } - - public ClassInheritanceTreeNode buildClassInheritance(Translator translator, ClassEntry obfClassEntry) { - // get the root node - List ancestry = Lists.newArrayList(); - ancestry.add(obfClassEntry.getFullName()); - for (ClassEntry classEntry : index.getInheritanceIndex().getAncestors(obfClassEntry)) { - ancestry.add(classEntry.getFullName()); - } - - ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(translator, ancestry.get(ancestry.size() - 1)); - - // expand all children recursively - rootNode.load(index.getInheritanceIndex(), true); - - return rootNode; - } - - public ClassImplementationsTreeNode buildClassImplementations(Translator translator, ClassEntry obfClassEntry) { - if (index.getInheritanceIndex().isParent(obfClassEntry)) { - ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(translator, obfClassEntry); - node.load(index); - return node; - } - return null; - } - - public MethodInheritanceTreeNode buildMethodInheritance(Translator translator, MethodEntry obfMethodEntry) { - MethodEntry resolvedEntry = index.getEntryResolver().resolveFirstEntry(obfMethodEntry, ResolutionStrategy.RESOLVE_ROOT); - - // make a root node at the base - MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode( - translator, resolvedEntry, - index.getEntryIndex().hasMethod(resolvedEntry) - ); - - // expand the full tree - rootNode.load(index); - - return rootNode; - } - - public List buildMethodImplementations(Translator translator, MethodEntry obfMethodEntry) { - EntryResolver resolver = index.getEntryResolver(); - Collection resolvedEntries = resolver.resolveEntry(obfMethodEntry, ResolutionStrategy.RESOLVE_ROOT); - - List nodes = Lists.newArrayList(); - for (MethodEntry resolvedEntry : resolvedEntries) { - MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(translator, resolvedEntry); - node.load(index); - nodes.add(node); - } - - return nodes; - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java b/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java deleted file mode 100644 index 8a1c2388..00000000 --- a/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java +++ /dev/null @@ -1,131 +0,0 @@ -package cuchaz.enigma.analysis; - -import cuchaz.enigma.utils.Utils; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.analysis.AnalyzerException; -import org.objectweb.asm.tree.analysis.Interpreter; -import org.objectweb.asm.tree.analysis.Value; - -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -public class InterpreterPair extends Interpreter> { - private final Interpreter left; - private final Interpreter right; - - public InterpreterPair(Interpreter left, Interpreter right) { - super(Utils.ASM_VERSION); - this.left = left; - this.right = right; - } - - @Override - public PairValue newValue(Type type) { - return pair( - left.newValue(type), - right.newValue(type) - ); - } - - @Override - public PairValue newOperation(AbstractInsnNode insn) throws AnalyzerException { - return pair( - left.newOperation(insn), - right.newOperation(insn) - ); - } - - @Override - public PairValue copyOperation(AbstractInsnNode insn, PairValue value) throws AnalyzerException { - return pair( - left.copyOperation(insn, value.left), - right.copyOperation(insn, value.right) - ); - } - - @Override - public PairValue unaryOperation(AbstractInsnNode insn, PairValue value) throws AnalyzerException { - return pair( - left.unaryOperation(insn, value.left), - right.unaryOperation(insn, value.right) - ); - } - - @Override - public PairValue binaryOperation(AbstractInsnNode insn, PairValue value1, PairValue value2) throws AnalyzerException { - return pair( - left.binaryOperation(insn, value1.left, value2.left), - right.binaryOperation(insn, value1.right, value2.right) - ); - } - - @Override - public PairValue ternaryOperation(AbstractInsnNode insn, PairValue value1, PairValue value2, PairValue value3) throws AnalyzerException { - return pair( - left.ternaryOperation(insn, value1.left, value2.left, value3.left), - right.ternaryOperation(insn, value1.right, value2.right, value3.right) - ); - } - - @Override - public PairValue naryOperation(AbstractInsnNode insn, List> values) throws AnalyzerException { - return pair( - left.naryOperation(insn, values.stream().map(v -> v.left).collect(Collectors.toList())), - right.naryOperation(insn, values.stream().map(v -> v.right).collect(Collectors.toList())) - ); - } - - @Override - public void returnOperation(AbstractInsnNode insn, PairValue value, PairValue expected) throws AnalyzerException { - left.returnOperation(insn, value.left, expected.left); - right.returnOperation(insn, value.right, expected.right); - } - - @Override - public PairValue merge(PairValue value1, PairValue value2) { - return pair( - left.merge(value1.left, value2.left), - right.merge(value1.right, value2.right) - ); - } - - private PairValue pair(V left, W right) { - if (left == null && right == null) { - return null; - } - - return new PairValue<>(left, right); - } - - public static final class PairValue implements Value { - public final V left; - public final W right; - - public PairValue(V left, W right) { - if (left == null && right == null) { - throw new IllegalArgumentException("should use null rather than pair of nulls"); - } - - this.left = left; - this.right = right; - } - - @Override - public boolean equals(Object o) { - return o instanceof InterpreterPair.PairValue && Objects.equals(left, ((PairValue) o).left) && Objects.equals(right, ((PairValue) o).right); - } - - @Override - public int hashCode() { - return left.hashCode() * 31 + right.hashCode(); - } - - @Override - public int getSize() { - return (left == null ? right : left).getSize(); - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java deleted file mode 100644 index b09f7ac6..00000000 --- a/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java +++ /dev/null @@ -1,85 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.analysis; - -import com.google.common.collect.Lists; -import cuchaz.enigma.analysis.index.EntryIndex; -import cuchaz.enigma.analysis.index.InheritanceIndex; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; - -import javax.swing.tree.DefaultMutableTreeNode; -import java.util.Collection; -import java.util.List; - -public class MethodImplementationsTreeNode extends DefaultMutableTreeNode { - - private final Translator translator; - private MethodEntry entry; - - public MethodImplementationsTreeNode(Translator translator, MethodEntry entry) { - this.translator = translator; - if (entry == null) { - throw new IllegalArgumentException("Entry cannot be null!"); - } - - this.entry = entry; - } - - public static MethodImplementationsTreeNode findNode(MethodImplementationsTreeNode node, MethodEntry entry) { - // is this the node? - if (node.getMethodEntry().equals(entry)) { - return node; - } - - // recurse - for (int i = 0; i < node.getChildCount(); i++) { - MethodImplementationsTreeNode foundNode = findNode((MethodImplementationsTreeNode) node.getChildAt(i), entry); - if (foundNode != null) { - return foundNode; - } - } - return null; - } - - public MethodEntry getMethodEntry() { - return this.entry; - } - - @Override - public String toString() { - MethodEntry translatedEntry = translator.translate(entry); - String className = translatedEntry.getParent().getFullName(); - String methodName = translatedEntry.getName(); - return className + "." + methodName + "()"; - } - - public void load(JarIndex index) { - // get all method implementations - List nodes = Lists.newArrayList(); - EntryIndex entryIndex = index.getEntryIndex(); - InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); - - Collection descendants = inheritanceIndex.getDescendants(entry.getParent()); - for (ClassEntry inheritor : descendants) { - MethodEntry methodEntry = entry.withParent(inheritor); - if (entryIndex.hasMethod(methodEntry)) { - nodes.add(new MethodImplementationsTreeNode(translator, methodEntry)); - } - } - - // add them to this node - nodes.forEach(this::add); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java deleted file mode 100644 index e77b5cce..00000000 --- a/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java +++ /dev/null @@ -1,95 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.analysis; - -import cuchaz.enigma.analysis.index.EntryIndex; -import cuchaz.enigma.analysis.index.InheritanceIndex; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; - -import javax.swing.tree.DefaultMutableTreeNode; - -public class MethodInheritanceTreeNode extends DefaultMutableTreeNode { - - private final Translator translator; - private MethodEntry entry; - private boolean implemented; - - public MethodInheritanceTreeNode(Translator translator, MethodEntry entry, boolean implemented) { - this.translator = translator; - this.entry = entry; - this.implemented = implemented; - } - - public static MethodInheritanceTreeNode findNode(MethodInheritanceTreeNode node, MethodEntry entry) { - // is this the node? - if (node.getMethodEntry().equals(entry)) { - return node; - } - - // recurse - for (int i = 0; i < node.getChildCount(); i++) { - MethodInheritanceTreeNode foundNode = findNode((MethodInheritanceTreeNode) node.getChildAt(i), entry); - if (foundNode != null) { - return foundNode; - } - } - return null; - } - - public MethodEntry getMethodEntry() { - return this.entry; - } - - public boolean isImplemented() { - return this.implemented; - } - - @Override - public String toString() { - MethodEntry translatedEntry = translator.translate(entry); - String className = translatedEntry.getContainingClass().getFullName(); - - if (!this.implemented) { - return className; - } else { - String methodName = translatedEntry.getName(); - return className + "." + methodName + "()"; - } - } - - /** - * Returns true if there is sub-node worthy to display. - */ - public boolean load(JarIndex index) { - // get all the child nodes - EntryIndex entryIndex = index.getEntryIndex(); - InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); - - boolean ret = false; - for (ClassEntry inheritorEntry : inheritanceIndex.getChildren(this.entry.getParent())) { - MethodEntry methodEntry = new MethodEntry(inheritorEntry, this.entry.getName(), this.entry.getDesc()); - - MethodInheritanceTreeNode node = new MethodInheritanceTreeNode(translator, methodEntry, entryIndex.hasMethod(methodEntry)); - boolean childOverride = node.load(index); - - if (childOverride || node.implemented) { - this.add(node); - ret = true; - } - } - - return ret; - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java b/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java deleted file mode 100644 index 81171038..00000000 --- a/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java +++ /dev/null @@ -1,19 +0,0 @@ -package cuchaz.enigma.analysis; - -import org.objectweb.asm.tree.MethodNode; - -import java.util.function.Consumer; - -public class MethodNodeWithAction extends MethodNode { - private final Consumer action; - - public MethodNodeWithAction(int api, int access, String name, String descriptor, String signature, String[] exceptions, Consumer action) { - super(api, access, name, descriptor, signature, exceptions); - this.action = action; - } - - @Override - public void visitEnd() { - action.accept(this); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java deleted file mode 100644 index 8995eb5c..00000000 --- a/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java +++ /dev/null @@ -1,113 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.analysis; - -import com.google.common.collect.Sets; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.analysis.index.ReferenceIndex; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.translation.representation.entry.MethodDefEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; - -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.TreeNode; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Set; - -public class MethodReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode { - - private final Translator translator; - private MethodEntry entry; - private EntryReference reference; - - public MethodReferenceTreeNode(Translator translator, MethodEntry entry) { - this.translator = translator; - this.entry = entry; - this.reference = null; - } - - public MethodReferenceTreeNode(Translator translator, EntryReference reference) { - this.translator = translator; - this.entry = reference.entry; - this.reference = reference; - } - - @Override - public MethodEntry getEntry() { - return this.entry; - } - - @Override - public EntryReference getReference() { - return this.reference; - } - - @Override - public String toString() { - if (this.reference != null) { - return String.format("%s", translator.translate(this.reference.context)); - } - return translator.translate(this.entry).getName(); - } - - public void load(JarIndex index, boolean recurse, boolean recurseMethod) { - // get all the child nodes - Collection> references = getReferences(index, recurseMethod); - - for (EntryReference reference : references) { - add(new MethodReferenceTreeNode(translator, reference)); - } - - if (recurse && this.children != null) { - for (Object child : this.children) { - if (child instanceof MethodReferenceTreeNode) { - MethodReferenceTreeNode node = (MethodReferenceTreeNode) child; - - // don't recurse into ancestor - Set> ancestors = Sets.newHashSet(); - TreeNode n = node; - while (n.getParent() != null) { - n = n.getParent(); - if (n instanceof MethodReferenceTreeNode) { - ancestors.add(((MethodReferenceTreeNode) n).getEntry()); - } - } - if (ancestors.contains(node.getEntry())) { - continue; - } - - node.load(index, true, false); - } - } - } - } - - private Collection> getReferences(JarIndex index, boolean recurseMethod) { - ReferenceIndex referenceIndex = index.getReferenceIndex(); - - if (recurseMethod) { - Collection> references = new ArrayList<>(); - - EntryResolver entryResolver = index.getEntryResolver(); - for (MethodEntry methodEntry : entryResolver.resolveEquivalentMethods(entry)) { - references.addAll(referenceIndex.getReferencesToMethod(methodEntry)); - } - - return references; - } else { - return referenceIndex.getReferencesToMethod(entry); - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/ReferenceTargetType.java b/src/main/java/cuchaz/enigma/analysis/ReferenceTargetType.java deleted file mode 100644 index 5b19d189..00000000 --- a/src/main/java/cuchaz/enigma/analysis/ReferenceTargetType.java +++ /dev/null @@ -1,74 +0,0 @@ -package cuchaz.enigma.analysis; - -import cuchaz.enigma.translation.representation.entry.ClassEntry; - -public abstract class ReferenceTargetType { - private static final None NONE = new None(); - private static final Uninitialized UNINITIALIZED = new Uninitialized(); - - public abstract Kind getKind(); - - public static None none() { - return NONE; - } - - public static Uninitialized uninitialized() { - return UNINITIALIZED; - } - - public static ClassType classType(ClassEntry name) { - return new ClassType(name); - } - - public enum Kind { - NONE, - UNINITIALIZED, - CLASS_TYPE - } - - public static class None extends ReferenceTargetType { - @Override - public Kind getKind() { - return Kind.NONE; - } - - @Override - public String toString() { - return "(none)"; - } - } - - public static class Uninitialized extends ReferenceTargetType { - @Override - public Kind getKind() { - return Kind.UNINITIALIZED; - } - - @Override - public String toString() { - return "(uninitialized)"; - } - } - - public static class ClassType extends ReferenceTargetType { - private final ClassEntry entry; - - private ClassType(ClassEntry entry) { - this.entry = entry; - } - - public ClassEntry getEntry() { - return entry; - } - - @Override - public Kind getKind() { - return Kind.CLASS_TYPE; - } - - @Override - public String toString() { - return entry.toString(); - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java deleted file mode 100644 index c0a3a754..00000000 --- a/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java +++ /dev/null @@ -1,20 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.analysis; - -import cuchaz.enigma.translation.representation.entry.Entry; - -public interface ReferenceTreeNode, C extends Entry> { - E getEntry(); - - EntryReference getReference(); -} diff --git a/src/main/java/cuchaz/enigma/analysis/Token.java b/src/main/java/cuchaz/enigma/analysis/Token.java deleted file mode 100644 index f0155e56..00000000 --- a/src/main/java/cuchaz/enigma/analysis/Token.java +++ /dev/null @@ -1,72 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.analysis; - -public class Token implements Comparable { - - public int start; - public int end; - public String text; - - public Token(int start, int end, String text) { - this.start = start; - this.end = end; - this.text = text; - } - - public int getRenameOffset(String to) { - int length = this.end - this.start; - return to.length() - length; - } - - public void rename(StringBuffer source, String to) { - int oldEnd = this.end; - this.text = to; - this.end = this.start + to.length(); - - source.replace(start, oldEnd, to); - } - - public Token move(int offset) { - Token token = new Token(this.start + offset, this.end + offset, null); - token.text = text; - return token; - } - - public boolean contains(int pos) { - return pos >= start && pos <= end; - } - - @Override - public int compareTo(Token other) { - return start - other.start; - } - - @Override - public boolean equals(Object other) { - return other instanceof Token && equals((Token) other); - } - - @Override - public int hashCode() { - return start * 37 + end; - } - - public boolean equals(Token other) { - return start == other.start && end == other.end && text.equals(other.text); - } - - @Override - public String toString() { - return String.format("[%d,%d]", start, end); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java b/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java deleted file mode 100644 index a4b1aac9..00000000 --- a/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java +++ /dev/null @@ -1,156 +0,0 @@ -package cuchaz.enigma.analysis.index; - -import com.google.common.collect.Maps; -import cuchaz.enigma.translation.representation.AccessFlags; -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.MethodDefEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; - -import javax.annotation.Nullable; -import java.util.*; - -public class BridgeMethodIndex implements JarIndexer { - private final EntryIndex entryIndex; - private final InheritanceIndex inheritanceIndex; - private final ReferenceIndex referenceIndex; - - private final Map bridgeToSpecialized = Maps.newHashMap(); - private final Map specializedToBridge = Maps.newHashMap(); - - public BridgeMethodIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex) { - this.entryIndex = entryIndex; - this.inheritanceIndex = inheritanceIndex; - this.referenceIndex = referenceIndex; - } - - public void findBridgeMethods() { - // look for access and bridged methods - for (MethodEntry methodEntry : entryIndex.getMethods()) { - MethodDefEntry methodDefEntry = (MethodDefEntry) methodEntry; - - AccessFlags access = methodDefEntry.getAccess(); - if (access == null || !access.isSynthetic()) { - continue; - } - - indexSyntheticMethod(methodDefEntry, access); - } - } - - @Override - public void processIndex(JarIndex index) { - Map copiedAccessToBridge = new HashMap<>(specializedToBridge); - - for (Map.Entry entry : copiedAccessToBridge.entrySet()) { - MethodEntry specializedEntry = entry.getKey(); - MethodEntry bridgeEntry = entry.getValue(); - if (bridgeEntry.getName().equals(specializedEntry.getName())) { - continue; - } - - MethodEntry renamedSpecializedEntry = specializedEntry.withName(bridgeEntry.getName()); - specializedToBridge.put(renamedSpecializedEntry, specializedToBridge.get(specializedEntry)); - } - } - - private void indexSyntheticMethod(MethodDefEntry syntheticMethod, AccessFlags access) { - MethodEntry specializedMethod = findSpecializedMethod(syntheticMethod); - if (specializedMethod == null) { - return; - } - - if (access.isBridge() || isPotentialBridge(syntheticMethod, specializedMethod)) { - bridgeToSpecialized.put(syntheticMethod, specializedMethod); - specializedToBridge.put(specializedMethod, syntheticMethod); - } - } - - private MethodEntry findSpecializedMethod(MethodEntry method) { - // we want to find all compiler-added methods that directly call another with no processing - - // get all the methods that we call - final Collection referencedMethods = referenceIndex.getMethodsReferencedBy(method); - - // is there just one? - if (referencedMethods.size() != 1) { - return null; - } - - return referencedMethods.stream().findFirst().orElse(null); - } - - private boolean isPotentialBridge(MethodDefEntry bridgeMethod, MethodEntry specializedMethod) { - // Bridge methods only exist for inheritance purposes, if we're private, final, or static, we cannot be inherited - AccessFlags bridgeAccess = bridgeMethod.getAccess(); - if (bridgeAccess.isPrivate() || bridgeAccess.isFinal() || bridgeAccess.isStatic()) { - return false; - } - - MethodDescriptor bridgeDesc = bridgeMethod.getDesc(); - MethodDescriptor specializedDesc = specializedMethod.getDesc(); - List bridgeArguments = bridgeDesc.getArgumentDescs(); - List specializedArguments = specializedDesc.getArgumentDescs(); - - // A bridge method will always have the same number of arguments - if (bridgeArguments.size() != specializedArguments.size()) { - return false; - } - - // Check that all argument types are bridge-compatible - for (int i = 0; i < bridgeArguments.size(); i++) { - if (!areTypesBridgeCompatible(bridgeArguments.get(i), specializedArguments.get(i))) { - return false; - } - } - - // Check that the return type is bridge-compatible - return areTypesBridgeCompatible(bridgeDesc.getReturnDesc(), specializedDesc.getReturnDesc()); - } - - private boolean areTypesBridgeCompatible(TypeDescriptor bridgeDesc, TypeDescriptor specializedDesc) { - if (bridgeDesc.equals(specializedDesc)) { - return true; - } - - // Either the descs will be equal, or they are both types and different through a generic - if (bridgeDesc.isType() && specializedDesc.isType()) { - ClassEntry bridgeType = bridgeDesc.getTypeEntry(); - ClassEntry accessedType = specializedDesc.getTypeEntry(); - - // If the given types are completely unrelated to each other, this can't be bridge compatible - InheritanceIndex.Relation relation = inheritanceIndex.computeClassRelation(accessedType, bridgeType); - return relation != InheritanceIndex.Relation.UNRELATED; - } - - return false; - } - - public boolean isBridgeMethod(MethodEntry entry) { - return bridgeToSpecialized.containsKey(entry); - } - - public boolean isSpecializedMethod(MethodEntry entry) { - return specializedToBridge.containsKey(entry); - } - - @Nullable - public MethodEntry getBridgeFromSpecialized(MethodEntry specialized) { - return specializedToBridge.get(specialized); - } - - public MethodEntry getSpecializedFromBridge(MethodEntry bridge) { - return bridgeToSpecialized.get(bridge); - } - - /** Includes "renamed specialized -> bridge" entries. */ - public Map getSpecializedToBridge() { - return Collections.unmodifiableMap(specializedToBridge); - } - - /** Only "bridge -> original name" entries. **/ - public Map getBridgeToSpecialized() { - return Collections.unmodifiableMap(bridgeToSpecialized); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java b/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java deleted file mode 100644 index 9a2726e5..00000000 --- a/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java +++ /dev/null @@ -1,102 +0,0 @@ -package cuchaz.enigma.analysis.index; - -import cuchaz.enigma.translation.representation.AccessFlags; -import cuchaz.enigma.translation.representation.entry.*; - -import javax.annotation.Nullable; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -public class EntryIndex implements JarIndexer { - private Map classes = new HashMap<>(); - private Map fields = new HashMap<>(); - private Map methods = new HashMap<>(); - private Map definitions = new HashMap<>(); - - @Override - public void indexClass(ClassDefEntry classEntry) { - definitions.put(classEntry, classEntry); - classes.put(classEntry, classEntry.getAccess()); - } - - @Override - public void indexMethod(MethodDefEntry methodEntry) { - methods.put(methodEntry, methodEntry.getAccess()); - } - - @Override - public void indexField(FieldDefEntry fieldEntry) { - fields.put(fieldEntry, fieldEntry.getAccess()); - } - - public boolean hasClass(ClassEntry entry) { - return classes.containsKey(entry); - } - - public boolean hasMethod(MethodEntry entry) { - return methods.containsKey(entry); - } - - public boolean hasField(FieldEntry entry) { - return fields.containsKey(entry); - } - - public boolean hasEntry(Entry entry) { - if (entry instanceof ClassEntry) { - return hasClass((ClassEntry) entry); - } else if (entry instanceof MethodEntry) { - return hasMethod((MethodEntry) entry); - } else if (entry instanceof FieldEntry) { - return hasField((FieldEntry) entry); - } else if (entry instanceof LocalVariableEntry) { - return hasMethod(((LocalVariableEntry) entry).getParent()); - } - - return false; - } - - @Nullable - public AccessFlags getMethodAccess(MethodEntry entry) { - return methods.get(entry); - } - - @Nullable - public AccessFlags getFieldAccess(FieldEntry entry) { - return fields.get(entry); - } - - @Nullable - public AccessFlags getClassAccess(ClassEntry entry) { - return classes.get(entry); - } - - @Nullable - public AccessFlags getEntryAccess(Entry entry) { - if (entry instanceof MethodEntry) { - return getMethodAccess((MethodEntry) entry); - } else if (entry instanceof FieldEntry) { - return getFieldAccess((FieldEntry) entry); - } else if (entry instanceof LocalVariableEntry) { - return getMethodAccess(((LocalVariableEntry) entry).getParent()); - } - - return null; - } - - public ClassDefEntry getDefinition(ClassEntry entry) { - return definitions.get(entry); - } - - public Collection getClasses() { - return classes.keySet(); - } - - public Collection getMethods() { - return methods.keySet(); - } - - public Collection getFields() { - return fields.keySet(); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java deleted file mode 100644 index f9cb23ce..00000000 --- a/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java +++ /dev/null @@ -1,40 +0,0 @@ -package cuchaz.enigma.analysis.index; - -import cuchaz.enigma.translation.representation.entry.ClassDefEntry; -import cuchaz.enigma.translation.representation.entry.FieldDefEntry; -import cuchaz.enigma.translation.representation.entry.MethodDefEntry; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.FieldVisitor; -import org.objectweb.asm.MethodVisitor; - -public class IndexClassVisitor extends ClassVisitor { - private final JarIndexer indexer; - private ClassDefEntry classEntry; - - public IndexClassVisitor(JarIndex indexer, int api) { - super(api); - this.indexer = indexer; - } - - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - classEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces); - indexer.indexClass(classEntry); - - super.visit(version, access, name, signature, superName, interfaces); - } - - @Override - public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { - indexer.indexField(FieldDefEntry.parse(classEntry, access, name, desc, signature)); - - return super.visitField(access, name, desc, signature, value); - } - - @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - indexer.indexMethod(MethodDefEntry.parse(classEntry, access, name, desc, signature)); - - return super.visitMethod(access, name, desc, signature, exceptions); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java b/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java deleted file mode 100644 index f3d419ee..00000000 --- a/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java +++ /dev/null @@ -1,180 +0,0 @@ -package cuchaz.enigma.analysis.index; - -import cuchaz.enigma.analysis.IndexSimpleVerifier; -import cuchaz.enigma.analysis.InterpreterPair; -import cuchaz.enigma.analysis.MethodNodeWithAction; -import cuchaz.enigma.analysis.ReferenceTargetType; -import cuchaz.enigma.translation.representation.AccessFlags; -import cuchaz.enigma.translation.representation.Lambda; -import cuchaz.enigma.translation.representation.MethodDescriptor; -import cuchaz.enigma.translation.representation.Signature; -import cuchaz.enigma.translation.representation.entry.*; -import org.objectweb.asm.*; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.FieldInsnNode; -import org.objectweb.asm.tree.InvokeDynamicInsnNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.analysis.*; - -import java.util.List; -import java.util.stream.Collectors; - -public class IndexReferenceVisitor extends ClassVisitor { - private final JarIndexer indexer; - private final EntryIndex entryIndex; - private final InheritanceIndex inheritanceIndex; - private ClassEntry classEntry; - private String className; - - public IndexReferenceVisitor(JarIndexer indexer, EntryIndex entryIndex, InheritanceIndex inheritanceIndex, int api) { - super(api); - this.indexer = indexer; - this.entryIndex = entryIndex; - this.inheritanceIndex = inheritanceIndex; - } - - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - classEntry = new ClassEntry(name); - className = name; - } - - @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - MethodDefEntry entry = new MethodDefEntry(classEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access)); - return new MethodNodeWithAction(api, access, name, desc, signature, exceptions, methodNode -> { - try { - new Analyzer<>(new MethodInterpreter(entry, indexer, entryIndex, inheritanceIndex)).analyze(className, methodNode); - } catch (AnalyzerException e) { - throw new RuntimeException(e); - } - }); - } - - private static class MethodInterpreter extends InterpreterPair { - private final MethodDefEntry callerEntry; - private JarIndexer indexer; - - public MethodInterpreter(MethodDefEntry callerEntry, JarIndexer indexer, EntryIndex entryIndex, InheritanceIndex inheritanceIndex) { - super(new IndexSimpleVerifier(entryIndex, inheritanceIndex), new SourceInterpreter()); - this.callerEntry = callerEntry; - this.indexer = indexer; - } - - @Override - public PairValue newOperation(AbstractInsnNode insn) throws AnalyzerException { - if (insn.getOpcode() == Opcodes.GETSTATIC) { - FieldInsnNode field = (FieldInsnNode) insn; - indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), ReferenceTargetType.none()); - } - - return super.newOperation(insn); - } - - @Override - public PairValue unaryOperation(AbstractInsnNode insn, PairValue value) throws AnalyzerException { - if (insn.getOpcode() == Opcodes.PUTSTATIC) { - FieldInsnNode field = (FieldInsnNode) insn; - indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), ReferenceTargetType.none()); - } - - if (insn.getOpcode() == Opcodes.GETFIELD) { - FieldInsnNode field = (FieldInsnNode) insn; - indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), getReferenceTargetType(value, insn)); - } - - return super.unaryOperation(insn, value); - } - - - @Override - public PairValue binaryOperation(AbstractInsnNode insn, PairValue value1, PairValue value2) throws AnalyzerException { - if (insn.getOpcode() == Opcodes.PUTFIELD) { - FieldInsnNode field = (FieldInsnNode) insn; - FieldEntry fieldEntry = FieldEntry.parse(field.owner, field.name, field.desc); - indexer.indexFieldReference(callerEntry, fieldEntry, ReferenceTargetType.none()); - } - - return super.binaryOperation(insn, value1, value2); - } - - @Override - public PairValue naryOperation(AbstractInsnNode insn, List> values) throws AnalyzerException { - if (insn.getOpcode() == Opcodes.INVOKEINTERFACE || insn.getOpcode() == Opcodes.INVOKESPECIAL || insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { - MethodInsnNode methodInsn = (MethodInsnNode) insn; - indexer.indexMethodReference(callerEntry, MethodEntry.parse(methodInsn.owner, methodInsn.name, methodInsn.desc), getReferenceTargetType(values.get(0), insn)); - } - - if (insn.getOpcode() == Opcodes.INVOKESTATIC) { - MethodInsnNode methodInsn = (MethodInsnNode) insn; - indexer.indexMethodReference(callerEntry, MethodEntry.parse(methodInsn.owner, methodInsn.name, methodInsn.desc), ReferenceTargetType.none()); - } - - if (insn.getOpcode() == Opcodes.INVOKEDYNAMIC) { - InvokeDynamicInsnNode invokeDynamicInsn = (InvokeDynamicInsnNode) insn; - List args = values.stream().map(v -> v.right.insns.stream().findFirst().orElseThrow(AssertionError::new)).collect(Collectors.toList()); - - if ("java/lang/invoke/LambdaMetafactory".equals(invokeDynamicInsn.bsm.getOwner()) && "metafactory".equals(invokeDynamicInsn.bsm.getName())) { - Type samMethodType = (Type) invokeDynamicInsn.bsmArgs[0]; - Handle implMethod = (Handle) invokeDynamicInsn.bsmArgs[1]; - Type instantiatedMethodType = (Type) invokeDynamicInsn.bsmArgs[2]; - - ReferenceTargetType targetType; - if (implMethod.getTag() != Opcodes.H_GETSTATIC && implMethod.getTag() != Opcodes.H_PUTFIELD && implMethod.getTag() != Opcodes.H_INVOKESTATIC) { - if (instantiatedMethodType.getArgumentTypes().length < Type.getArgumentTypes(implMethod.getDesc()).length) { - targetType = getReferenceTargetType(values.get(0), insn); - } else { - targetType = ReferenceTargetType.none(); // no "this" argument - } - } else { - targetType = ReferenceTargetType.none(); - } - - indexer.indexLambda(callerEntry, new Lambda( - invokeDynamicInsn.name, - new MethodDescriptor(invokeDynamicInsn.desc), - new MethodDescriptor(samMethodType.getDescriptor()), - getHandleEntry(implMethod), - new MethodDescriptor(instantiatedMethodType.getDescriptor()) - ), targetType); - } - } - - return super.naryOperation(insn, values); - } - - private ReferenceTargetType getReferenceTargetType(PairValue target, AbstractInsnNode insn) throws AnalyzerException { - if (target.left == BasicValue.UNINITIALIZED_VALUE) { - return ReferenceTargetType.uninitialized(); - } - - if (target.left.getType().getSort() == Type.OBJECT) { - return ReferenceTargetType.classType(new ClassEntry(target.left.getType().getInternalName())); - } - - if (target.left.getType().getSort() == Type.ARRAY) { - return ReferenceTargetType.classType(new ClassEntry("java/lang/Object")); - } - - throw new AnalyzerException(insn, "called method on or accessed field of non-object type"); - } - - private static ParentedEntry getHandleEntry(Handle handle) { - switch (handle.getTag()) { - case Opcodes.H_GETFIELD: - case Opcodes.H_GETSTATIC: - case Opcodes.H_PUTFIELD: - case Opcodes.H_PUTSTATIC: - return FieldEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); - case Opcodes.H_INVOKEINTERFACE: - case Opcodes.H_INVOKESPECIAL: - case Opcodes.H_INVOKESTATIC: - case Opcodes.H_INVOKEVIRTUAL: - case Opcodes.H_NEWINVOKESPECIAL: - return MethodEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); - } - - throw new RuntimeException("Invalid handle tag " + handle.getTag()); - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java b/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java deleted file mode 100644 index 1ab2abdf..00000000 --- a/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java +++ /dev/null @@ -1,127 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.analysis.index; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import com.google.common.collect.Sets; -import cuchaz.enigma.translation.representation.entry.ClassDefEntry; -import cuchaz.enigma.translation.representation.entry.ClassEntry; - -import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.Set; - -public class InheritanceIndex implements JarIndexer { - private final EntryIndex entryIndex; - - private Multimap classParents = HashMultimap.create(); - private Multimap classChildren = HashMultimap.create(); - - public InheritanceIndex(EntryIndex entryIndex) { - this.entryIndex = entryIndex; - } - - @Override - public void indexClass(ClassDefEntry classEntry) { - if (classEntry.isJre()) { - return; - } - - ClassEntry superClass = classEntry.getSuperClass(); - if (superClass != null && !superClass.getName().equals("java/lang/Object")) { - indexParent(classEntry, superClass); - } - - for (ClassEntry interfaceEntry : classEntry.getInterfaces()) { - indexParent(classEntry, interfaceEntry); - } - } - - private void indexParent(ClassEntry childEntry, ClassEntry parentEntry) { - classParents.put(childEntry, parentEntry); - classChildren.put(parentEntry, childEntry); - } - - public Collection getParents(ClassEntry classEntry) { - return classParents.get(classEntry); - } - - public Collection getChildren(ClassEntry classEntry) { - return classChildren.get(classEntry); - } - - public Collection getDescendants(ClassEntry classEntry) { - Collection descendants = new HashSet<>(); - - LinkedList descendantQueue = new LinkedList<>(); - descendantQueue.push(classEntry); - - while (!descendantQueue.isEmpty()) { - ClassEntry descendant = descendantQueue.pop(); - Collection children = getChildren(descendant); - - children.forEach(descendantQueue::push); - descendants.addAll(children); - } - - return descendants; - } - - public Set getAncestors(ClassEntry classEntry) { - Set ancestors = Sets.newHashSet(); - - LinkedList ancestorQueue = new LinkedList<>(); - ancestorQueue.push(classEntry); - - while (!ancestorQueue.isEmpty()) { - ClassEntry ancestor = ancestorQueue.pop(); - Collection parents = getParents(ancestor); - - parents.forEach(ancestorQueue::push); - ancestors.addAll(parents); - } - - return ancestors; - } - - public Relation computeClassRelation(ClassEntry classEntry, ClassEntry potentialAncestor) { - if (potentialAncestor.getName().equals("java/lang/Object")) return Relation.RELATED; - if (!entryIndex.hasClass(classEntry)) return Relation.UNKNOWN; - - for (ClassEntry ancestor : getAncestors(classEntry)) { - if (potentialAncestor.equals(ancestor)) { - return Relation.RELATED; - } else if (!entryIndex.hasClass(ancestor)) { - return Relation.UNKNOWN; - } - } - - return Relation.UNRELATED; - } - - public boolean isParent(ClassEntry classEntry) { - return classChildren.containsKey(classEntry); - } - - public boolean hasParents(ClassEntry classEntry) { - Collection parents = classParents.get(classEntry); - return parents != null && !parents.isEmpty(); - } - - public enum Relation { - RELATED, - UNRELATED, - UNKNOWN - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java deleted file mode 100644 index e401c2f7..00000000 --- a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java +++ /dev/null @@ -1,171 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.analysis.index; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.analysis.ClassCache; -import cuchaz.enigma.analysis.ReferenceTargetType; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.mapping.IndexEntryResolver; -import cuchaz.enigma.translation.representation.Lambda; -import cuchaz.enigma.translation.representation.entry.*; -import cuchaz.enigma.utils.I18n; - -import cuchaz.enigma.utils.Utils; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.Opcodes; - -import java.util.Arrays; -import java.util.Collection; - -public class JarIndex implements JarIndexer { - private final EntryIndex entryIndex; - private final InheritanceIndex inheritanceIndex; - private final ReferenceIndex referenceIndex; - private final BridgeMethodIndex bridgeMethodIndex; - private final PackageVisibilityIndex packageVisibilityIndex; - private final EntryResolver entryResolver; - - private final Collection indexers; - - private final Multimap methodImplementations = HashMultimap.create(); - - public JarIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex, PackageVisibilityIndex packageVisibilityIndex) { - this.entryIndex = entryIndex; - this.inheritanceIndex = inheritanceIndex; - this.referenceIndex = referenceIndex; - this.bridgeMethodIndex = bridgeMethodIndex; - this.packageVisibilityIndex = packageVisibilityIndex; - this.indexers = Arrays.asList(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); - this.entryResolver = new IndexEntryResolver(this); - } - - public static JarIndex empty() { - EntryIndex entryIndex = new EntryIndex(); - InheritanceIndex inheritanceIndex = new InheritanceIndex(entryIndex); - ReferenceIndex referenceIndex = new ReferenceIndex(); - BridgeMethodIndex bridgeMethodIndex = new BridgeMethodIndex(entryIndex, inheritanceIndex, referenceIndex); - PackageVisibilityIndex packageVisibilityIndex = new PackageVisibilityIndex(); - return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); - } - - public void indexJar(ClassCache classCache, ProgressListener progress) { - progress.init(4, I18n.translate("progress.jar.indexing")); - - progress.step(1, I18n.translate("progress.jar.indexing.entries")); - classCache.visit(() -> new IndexClassVisitor(this, Utils.ASM_VERSION), ClassReader.SKIP_CODE); - - progress.step(2, I18n.translate("progress.jar.indexing.references")); - classCache.visit(() -> new IndexReferenceVisitor(this, entryIndex, inheritanceIndex, Utils.ASM_VERSION), 0); - - progress.step(3, I18n.translate("progress.jar.indexing.methods")); - bridgeMethodIndex.findBridgeMethods(); - - progress.step(4, I18n.translate("progress.jar.indexing.process")); - processIndex(this); - } - - @Override - public void processIndex(JarIndex index) { - indexers.forEach(indexer -> indexer.processIndex(index)); - } - - @Override - public void indexClass(ClassDefEntry classEntry) { - if (classEntry.isJre()) { - return; - } - - for (ClassEntry interfaceEntry : classEntry.getInterfaces()) { - if (classEntry.equals(interfaceEntry)) { - throw new IllegalArgumentException("Class cannot be its own interface! " + classEntry); - } - } - - indexers.forEach(indexer -> indexer.indexClass(classEntry)); - } - - @Override - public void indexField(FieldDefEntry fieldEntry) { - if (fieldEntry.getParent().isJre()) { - return; - } - - indexers.forEach(indexer -> indexer.indexField(fieldEntry)); - } - - @Override - public void indexMethod(MethodDefEntry methodEntry) { - if (methodEntry.getParent().isJre()) { - return; - } - - indexers.forEach(indexer -> indexer.indexMethod(methodEntry)); - - if (!methodEntry.isConstructor()) { - methodImplementations.put(methodEntry.getParent().getFullName(), methodEntry); - } - } - - @Override - public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) { - if (callerEntry.getParent().isJre()) { - return; - } - - indexers.forEach(indexer -> indexer.indexMethodReference(callerEntry, referencedEntry, targetType)); - } - - @Override - public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) { - if (callerEntry.getParent().isJre()) { - return; - } - - indexers.forEach(indexer -> indexer.indexFieldReference(callerEntry, referencedEntry, targetType)); - } - - @Override - public void indexLambda(MethodDefEntry callerEntry, Lambda lambda, ReferenceTargetType targetType) { - if (callerEntry.getParent().isJre()) { - return; - } - - indexers.forEach(indexer -> indexer.indexLambda(callerEntry, lambda, targetType)); - } - - public EntryIndex getEntryIndex() { - return entryIndex; - } - - public InheritanceIndex getInheritanceIndex() { - return this.inheritanceIndex; - } - - public ReferenceIndex getReferenceIndex() { - return referenceIndex; - } - - public BridgeMethodIndex getBridgeMethodIndex() { - return bridgeMethodIndex; - } - - public PackageVisibilityIndex getPackageVisibilityIndex() { - return packageVisibilityIndex; - } - - public EntryResolver getEntryResolver() { - return entryResolver; - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java b/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java deleted file mode 100644 index f17e7c98..00000000 --- a/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java +++ /dev/null @@ -1,28 +0,0 @@ -package cuchaz.enigma.analysis.index; - -import cuchaz.enigma.analysis.ReferenceTargetType; -import cuchaz.enigma.translation.representation.Lambda; -import cuchaz.enigma.translation.representation.entry.*; - -public interface JarIndexer { - default void indexClass(ClassDefEntry classEntry) { - } - - default void indexField(FieldDefEntry fieldEntry) { - } - - default void indexMethod(MethodDefEntry methodEntry) { - } - - default void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) { - } - - default void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) { - } - - default void indexLambda(MethodDefEntry callerEntry, Lambda lambda, ReferenceTargetType targetType) { - } - - default void processIndex(JarIndex index) { - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java b/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java deleted file mode 100644 index 63eb7300..00000000 --- a/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java +++ /dev/null @@ -1,147 +0,0 @@ -package cuchaz.enigma.analysis.index; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.analysis.ReferenceTargetType; -import cuchaz.enigma.translation.representation.AccessFlags; -import cuchaz.enigma.translation.representation.entry.*; - -import java.util.*; - -public class PackageVisibilityIndex implements JarIndexer { - private static boolean requiresSamePackage(AccessFlags entryAcc, EntryReference ref, InheritanceIndex inheritanceIndex) { - if (entryAcc.isPublic()) { - return false; - } - - if (entryAcc.isProtected()) { - ClassEntry contextClass = ref.context.getContainingClass(); - ClassEntry referencedClass = ref.entry.getContainingClass(); - - if (!inheritanceIndex.getAncestors(contextClass).contains(referencedClass)) { - return true; // access to protected member not in superclass - } - - if (ref.targetType.getKind() == ReferenceTargetType.Kind.NONE) { - return false; // access to superclass or static superclass member - } - - // access to instance member only valid if target's class assignable to context class - return !(ref.targetType.getKind() == ReferenceTargetType.Kind.UNINITIALIZED || - ((ReferenceTargetType.ClassType) ref.targetType).getEntry().equals(contextClass) || - inheritanceIndex.getAncestors(((ReferenceTargetType.ClassType) ref.targetType).getEntry()).contains(contextClass)); - } - - return true; - } - - private final HashMultimap connections = HashMultimap.create(); - private final List> partitions = Lists.newArrayList(); - private final Map> classPartitions = Maps.newHashMap(); - - private void addConnection(ClassEntry classA, ClassEntry classB) { - if (classA != classB) { - connections.put(classA, classB); - connections.put(classB, classA); - } - } - - private void buildPartition(Set unassignedClasses, Set partition, ClassEntry member) { - for (ClassEntry connected : connections.get(member)) { - if (unassignedClasses.remove(connected)) { - partition.add(connected); - buildPartition(unassignedClasses, partition, connected); - } - } - } - - private void addConnections(EntryIndex entryIndex, ReferenceIndex referenceIndex, InheritanceIndex inheritanceIndex) { - for (FieldEntry entry : entryIndex.getFields()) { - AccessFlags entryAcc = entryIndex.getFieldAccess(entry); - if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { - for (EntryReference ref : referenceIndex.getReferencesToField(entry)) { - if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { - addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); - } - } - } - } - - for (MethodEntry entry : entryIndex.getMethods()) { - AccessFlags entryAcc = entryIndex.getMethodAccess(entry); - if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { - for (EntryReference ref : referenceIndex.getReferencesToMethod(entry)) { - if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { - addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); - } - } - } - } - - for (ClassEntry entry : entryIndex.getClasses()) { - AccessFlags entryAcc = entryIndex.getClassAccess(entry); - if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { - for (EntryReference ref : referenceIndex.getFieldTypeReferencesToClass(entry)) { - if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { - addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); - } - } - - for (EntryReference ref : referenceIndex.getMethodTypeReferencesToClass(entry)) { - if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { - addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); - } - } - } - - for (ClassEntry parent : inheritanceIndex.getParents(entry)) { - AccessFlags parentAcc = entryIndex.getClassAccess(parent); - if (parentAcc != null && !parentAcc.isPublic() && !parentAcc.isPrivate()) { - addConnection(entry, parent); - } - } - - ClassEntry outerClass = entry.getOuterClass(); - if (outerClass != null) { - addConnection(entry, outerClass); - } - } - } - - private void addPartitions(EntryIndex entryIndex) { - Set unassignedClasses = Sets.newHashSet(entryIndex.getClasses()); - while (!unassignedClasses.isEmpty()) { - Iterator iterator = unassignedClasses.iterator(); - ClassEntry initialEntry = iterator.next(); - iterator.remove(); - - HashSet partition = Sets.newHashSet(); - partition.add(initialEntry); - buildPartition(unassignedClasses, partition, initialEntry); - partitions.add(partition); - for (ClassEntry entry : partition) { - classPartitions.put(entry, partition); - } - } - } - - public Collection> getPartitions() { - return partitions; - } - - public Set getPartition(ClassEntry classEntry) { - return classPartitions.get(classEntry); - } - - @Override - public void processIndex(JarIndex index) { - EntryIndex entryIndex = index.getEntryIndex(); - ReferenceIndex referenceIndex = index.getReferenceIndex(); - InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); - addConnections(entryIndex, referenceIndex, inheritanceIndex); - addPartitions(entryIndex); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java b/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java deleted file mode 100644 index b6797c21..00000000 --- a/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java +++ /dev/null @@ -1,148 +0,0 @@ -package cuchaz.enigma.analysis.index; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.analysis.ReferenceTargetType; -import cuchaz.enigma.translation.mapping.ResolutionStrategy; -import cuchaz.enigma.translation.representation.Lambda; -import cuchaz.enigma.translation.representation.MethodDescriptor; -import cuchaz.enigma.translation.representation.TypeDescriptor; -import cuchaz.enigma.translation.representation.entry.*; - -import java.util.Collection; -import java.util.Map; - -public class ReferenceIndex implements JarIndexer { - private Multimap methodReferences = HashMultimap.create(); - - private Multimap> referencesToMethods = HashMultimap.create(); - private Multimap> referencesToClasses = HashMultimap.create(); - private Multimap> referencesToFields = HashMultimap.create(); - private Multimap> fieldTypeReferences = HashMultimap.create(); - private Multimap> methodTypeReferences = HashMultimap.create(); - - @Override - public void indexMethod(MethodDefEntry methodEntry) { - indexMethodDescriptor(methodEntry, methodEntry.getDesc()); - } - - private void indexMethodDescriptor(MethodDefEntry entry, MethodDescriptor descriptor) { - for (TypeDescriptor typeDescriptor : descriptor.getArgumentDescs()) { - indexMethodTypeDescriptor(entry, typeDescriptor); - } - indexMethodTypeDescriptor(entry, descriptor.getReturnDesc()); - } - - private void indexMethodTypeDescriptor(MethodDefEntry method, TypeDescriptor typeDescriptor) { - if (typeDescriptor.isType()) { - ClassEntry referencedClass = typeDescriptor.getTypeEntry(); - methodTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), method)); - } else if (typeDescriptor.isArray()) { - indexMethodTypeDescriptor(method, typeDescriptor.getArrayType()); - } - } - - @Override - public void indexField(FieldDefEntry fieldEntry) { - indexFieldTypeDescriptor(fieldEntry, fieldEntry.getDesc()); - } - - private void indexFieldTypeDescriptor(FieldDefEntry field, TypeDescriptor typeDescriptor) { - if (typeDescriptor.isType()) { - ClassEntry referencedClass = typeDescriptor.getTypeEntry(); - fieldTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), field)); - } else if (typeDescriptor.isArray()) { - indexFieldTypeDescriptor(field, typeDescriptor.getArrayType()); - } - } - - @Override - public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) { - referencesToMethods.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); - methodReferences.put(callerEntry, referencedEntry); - - if (referencedEntry.isConstructor()) { - ClassEntry referencedClass = referencedEntry.getParent(); - referencesToClasses.put(referencedClass, new EntryReference<>(referencedClass, referencedEntry.getName(), callerEntry, targetType)); - } - } - - @Override - public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) { - referencesToFields.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); - } - - @Override - public void indexLambda(MethodDefEntry callerEntry, Lambda lambda, ReferenceTargetType targetType) { - if (lambda.getImplMethod() instanceof MethodEntry) { - indexMethodReference(callerEntry, (MethodEntry) lambda.getImplMethod(), targetType); - } else { - indexFieldReference(callerEntry, (FieldEntry) lambda.getImplMethod(), targetType); - } - - indexMethodDescriptor(callerEntry, lambda.getInvokedType()); - indexMethodDescriptor(callerEntry, lambda.getSamMethodType()); - indexMethodDescriptor(callerEntry, lambda.getInstantiatedMethodType()); - } - - @Override - public void processIndex(JarIndex index) { - methodReferences = remapReferences(index, methodReferences); - referencesToMethods = remapReferencesTo(index, referencesToMethods); - referencesToClasses = remapReferencesTo(index, referencesToClasses); - referencesToFields = remapReferencesTo(index, referencesToFields); - fieldTypeReferences = remapReferencesTo(index, fieldTypeReferences); - methodTypeReferences = remapReferencesTo(index, methodTypeReferences); - } - - private , V extends Entry> Multimap remapReferences(JarIndex index, Multimap multimap) { - final int keySetSize = multimap.keySet().size(); - Multimap resolved = HashMultimap.create(multimap.keySet().size(), keySetSize == 0 ? 0 : multimap.size() / keySetSize); - for (Map.Entry entry : multimap.entries()) { - resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue())); - } - return resolved; - } - - private , C extends Entry> Multimap> remapReferencesTo(JarIndex index, Multimap> multimap) { - final int keySetSize = multimap.keySet().size(); - Multimap> resolved = HashMultimap.create(keySetSize, keySetSize == 0 ? 0 : multimap.size() / keySetSize); - for (Map.Entry> entry : multimap.entries()) { - resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue())); - } - return resolved; - } - - private > E remap(JarIndex index, E entry) { - return index.getEntryResolver().resolveFirstEntry(entry, ResolutionStrategy.RESOLVE_CLOSEST); - } - - private , C extends Entry> EntryReference remap(JarIndex index, EntryReference reference) { - return index.getEntryResolver().resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST); - } - - public Collection getMethodsReferencedBy(MethodEntry entry) { - return methodReferences.get(entry); - } - - public Collection> getReferencesToField(FieldEntry entry) { - return referencesToFields.get(entry); - } - - public Collection> getReferencesToClass(ClassEntry entry) { - return referencesToClasses.get(entry); - } - - public Collection> getReferencesToMethod(MethodEntry entry) { - return referencesToMethods.get(entry); - } - - public Collection> getFieldTypeReferencesToClass(ClassEntry entry) { - return fieldTypeReferences.get(entry); - } - - public Collection> getMethodTypeReferencesToClass(ClassEntry entry) { - return methodTypeReferences.get(entry); - } -} diff --git a/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java b/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java deleted file mode 100644 index bdd60150..00000000 --- a/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java +++ /dev/null @@ -1,5 +0,0 @@ -package cuchaz.enigma.api; - -public interface EnigmaPlugin { - void init(EnigmaPluginContext ctx); -} diff --git a/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java b/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java deleted file mode 100644 index a59051ad..00000000 --- a/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java +++ /dev/null @@ -1,9 +0,0 @@ -package cuchaz.enigma.api; - -import cuchaz.enigma.api.service.EnigmaService; -import cuchaz.enigma.api.service.EnigmaServiceFactory; -import cuchaz.enigma.api.service.EnigmaServiceType; - -public interface EnigmaPluginContext { - void registerService(String id, EnigmaServiceType serviceType, EnigmaServiceFactory factory); -} diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaService.java b/src/main/java/cuchaz/enigma/api/service/EnigmaService.java deleted file mode 100644 index 526dda77..00000000 --- a/src/main/java/cuchaz/enigma/api/service/EnigmaService.java +++ /dev/null @@ -1,4 +0,0 @@ -package cuchaz.enigma.api.service; - -public interface EnigmaService { -} diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java deleted file mode 100644 index 9e433fb0..00000000 --- a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java +++ /dev/null @@ -1,11 +0,0 @@ -package cuchaz.enigma.api.service; - -import java.util.Optional; - -public interface EnigmaServiceContext { - static EnigmaServiceContext empty() { - return key -> Optional.empty(); - } - - Optional getArgument(String key); -} diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java deleted file mode 100644 index 7c10ac26..00000000 --- a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java +++ /dev/null @@ -1,5 +0,0 @@ -package cuchaz.enigma.api.service; - -public interface EnigmaServiceFactory { - T create(EnigmaServiceContext ctx); -} diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java deleted file mode 100644 index 358828f0..00000000 --- a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java +++ /dev/null @@ -1,29 +0,0 @@ -package cuchaz.enigma.api.service; - -public final class EnigmaServiceType { - public final String key; - - private EnigmaServiceType(String key) { - this.key = key; - } - - public static EnigmaServiceType create(String key) { - return new EnigmaServiceType<>(key); - } - - @Override - public int hashCode() { - return key.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - - if (obj instanceof EnigmaServiceType) { - return ((EnigmaServiceType) obj).key.equals(key); - } - - return false; - } -} diff --git a/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java b/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java deleted file mode 100644 index 0cda1998..00000000 --- a/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java +++ /dev/null @@ -1,10 +0,0 @@ -package cuchaz.enigma.api.service; - -import cuchaz.enigma.analysis.ClassCache; -import cuchaz.enigma.analysis.index.JarIndex; - -public interface JarIndexerService extends EnigmaService { - EnigmaServiceType TYPE = EnigmaServiceType.create("jar_indexer"); - - void acceptJar(ClassCache classCache, JarIndex jarIndex); -} diff --git a/src/main/java/cuchaz/enigma/api/service/NameProposalService.java b/src/main/java/cuchaz/enigma/api/service/NameProposalService.java deleted file mode 100644 index 4c357db1..00000000 --- a/src/main/java/cuchaz/enigma/api/service/NameProposalService.java +++ /dev/null @@ -1,12 +0,0 @@ -package cuchaz.enigma.api.service; - -import cuchaz.enigma.translation.mapping.EntryRemapper; -import cuchaz.enigma.translation.representation.entry.Entry; - -import java.util.Optional; - -public interface NameProposalService extends EnigmaService { - EnigmaServiceType TYPE = EnigmaServiceType.create("name_proposal"); - - Optional proposeName(Entry obfEntry, EntryRemapper remapper); -} diff --git a/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java b/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java deleted file mode 100644 index af0cf30b..00000000 --- a/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java +++ /dev/null @@ -1,9 +0,0 @@ -package cuchaz.enigma.api.service; - -import cuchaz.enigma.translation.representation.entry.Entry; - -public interface ObfuscationTestService extends EnigmaService { - EnigmaServiceType TYPE = EnigmaServiceType.create("obfuscation_test"); - - boolean testDeobfuscated(Entry entry); -} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java b/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java deleted file mode 100644 index 1a2b47fb..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java +++ /dev/null @@ -1,46 +0,0 @@ -package cuchaz.enigma.bytecode.translators; - -import cuchaz.enigma.translation.Translator; -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.MethodEntry; -import org.objectweb.asm.Handle; -import org.objectweb.asm.Type; - -public class AsmObjectTranslator { - public static Type translateType(Translator translator, Type type) { - String descString = type.getDescriptor(); - switch (type.getSort()) { - case Type.OBJECT: { - ClassEntry classEntry = new ClassEntry(type.getInternalName()); - return Type.getObjectType(translator.translate(classEntry).getFullName()); - } - case Type.ARRAY: { - TypeDescriptor descriptor = new TypeDescriptor(descString); - return Type.getType(translator.translate(descriptor).toString()); - } - case Type.METHOD: { - MethodDescriptor descriptor = new MethodDescriptor(descString); - return Type.getMethodType(translator.translate(descriptor).toString()); - } - } - return type; - } - - public static Handle translateHandle(Translator translator, Handle handle) { - MethodEntry entry = new MethodEntry(new ClassEntry(handle.getOwner()), handle.getName(), new MethodDescriptor(handle.getDesc())); - MethodEntry translatedMethod = translator.translate(entry); - ClassEntry ownerClass = translatedMethod.getParent(); - return new Handle(handle.getTag(), ownerClass.getFullName(), translatedMethod.getName(), translatedMethod.getDesc().toString(), handle.isInterface()); - } - - public static Object translateValue(Translator translator, Object value) { - if (value instanceof Type) { - return translateType(translator, (Type) value); - } else if (value instanceof Handle) { - return translateHandle(translator, (Handle) value); - } - return value; - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java deleted file mode 100644 index cfd8fbee..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java +++ /dev/null @@ -1,126 +0,0 @@ -package cuchaz.enigma.bytecode.translators; - -import com.google.common.base.CharMatcher; -import cuchaz.enigma.translation.LocalNameGenerator; -import cuchaz.enigma.translation.representation.TypeDescriptor; -import cuchaz.enigma.translation.representation.entry.ClassDefEntry; -import cuchaz.enigma.translation.representation.entry.MethodDefEntry; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class LocalVariableFixVisitor extends ClassVisitor { - private ClassDefEntry ownerEntry; - - public LocalVariableFixVisitor(int api, ClassVisitor visitor) { - super(api, visitor); - } - - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - ownerEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces); - super.visit(version, access, name, signature, superName, interfaces); - } - - @Override - public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { - MethodDefEntry methodEntry = MethodDefEntry.parse(ownerEntry, access, name, descriptor, signature); - return new Method(api, methodEntry, super.visitMethod(access, name, descriptor, signature, exceptions)); - } - - private class Method extends MethodVisitor { - private final MethodDefEntry methodEntry; - private final Map parameterNames = new HashMap<>(); - private final Map parameterIndices = new HashMap<>(); - private boolean hasParameterTable; - private int parameterIndex = 0; - - Method(int api, MethodDefEntry methodEntry, MethodVisitor visitor) { - super(api, visitor); - this.methodEntry = methodEntry; - - int lvIndex = methodEntry.getAccess().isStatic() ? 0 : 1; - List parameters = methodEntry.getDesc().getArgumentDescs(); - for (int parameterIndex = 0; parameterIndex < parameters.size(); parameterIndex++) { - TypeDescriptor param = parameters.get(parameterIndex); - parameterIndices.put(lvIndex, parameterIndex); - lvIndex += param.getSize(); - } - } - - @Override - public void visitParameter(String name, int access) { - hasParameterTable = true; - super.visitParameter(fixParameterName(parameterIndex, name), fixParameterAccess(parameterIndex, access)); - parameterIndex++; - } - - @Override - public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { - if (index == 0 && !methodEntry.getAccess().isStatic()) { - name = "this"; - } else if (parameterIndices.containsKey(index)) { - name = fixParameterName(parameterIndices.get(index), name); - } else if (isInvalidName(name)) { - name = LocalNameGenerator.generateLocalVariableName(index, new TypeDescriptor(desc)); - } - - super.visitLocalVariable(name, desc, signature, start, end, index); - } - - private boolean isInvalidName(String name) { - return name == null || name.isEmpty() || !CharMatcher.ascii().matchesAllOf(name); - } - - @Override - public void visitEnd() { - if (!hasParameterTable) { - List arguments = methodEntry.getDesc().getArgumentDescs(); - for (int argumentIndex = 0; argumentIndex < arguments.size(); argumentIndex++) { - super.visitParameter(fixParameterName(argumentIndex, null), fixParameterAccess(argumentIndex, 0)); - } - } - - super.visitEnd(); - } - - private String fixParameterName(int index, String name) { - if (parameterNames.get(index) != null) { - return parameterNames.get(index); // to make sure that LVT names are consistent with parameter table names - } - - if (isInvalidName(name)) { - List arguments = methodEntry.getDesc().getArgumentDescs(); - name = LocalNameGenerator.generateArgumentName(index, arguments.get(index), arguments); - } - - if (index == 0 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("")) { - name = "name"; - } - - if (index == 1 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("")) { - name = "ordinal"; - } - - parameterNames.put(index, name); - return name; - } - - private int fixParameterAccess(int index, int access) { - if (index == 0 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("")) { - access |= Opcodes.ACC_SYNTHETIC; - } - - if (index == 1 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("")) { - access |= Opcodes.ACC_SYNTHETIC; - } - - return access; - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/SourceFixVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/SourceFixVisitor.java deleted file mode 100644 index 2b750eac..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/SourceFixVisitor.java +++ /dev/null @@ -1,39 +0,0 @@ -package cuchaz.enigma.bytecode.translators; - -import cuchaz.enigma.analysis.index.BridgeMethodIndex; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.translation.representation.entry.ClassDefEntry; -import cuchaz.enigma.translation.representation.entry.MethodDefEntry; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; - -public class SourceFixVisitor extends ClassVisitor { - private final JarIndex index; - private ClassDefEntry ownerEntry; - - public SourceFixVisitor(int api, ClassVisitor visitor, JarIndex index) { - super(api, visitor); - this.index = index; - } - - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - ownerEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces); - super.visit(version, access, name, signature, superName, interfaces); - } - - @Override - public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { - MethodDefEntry methodEntry = MethodDefEntry.parse(ownerEntry, access, name, descriptor, signature); - - BridgeMethodIndex bridgeIndex = index.getBridgeMethodIndex(); - if (bridgeIndex.isBridgeMethod(methodEntry)) { - access |= Opcodes.ACC_BRIDGE; - } else if (bridgeIndex.isSpecializedMethod(methodEntry)) { - name = bridgeIndex.getBridgeFromSpecialized(methodEntry).getName(); - } - - return super.visitMethod(access, name, descriptor, signature, exceptions); - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java deleted file mode 100644 index cb843ad4..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java +++ /dev/null @@ -1,51 +0,0 @@ -package cuchaz.enigma.bytecode.translators; - -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.representation.TypeDescriptor; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.FieldEntry; -import org.objectweb.asm.AnnotationVisitor; - -public class TranslationAnnotationVisitor extends AnnotationVisitor { - private final Translator translator; - private final ClassEntry annotationEntry; - - public TranslationAnnotationVisitor(Translator translator, ClassEntry annotationEntry, int api, AnnotationVisitor av) { - super(api, av); - this.translator = translator; - this.annotationEntry = annotationEntry; - } - - @Override - public void visit(String name, Object value) { - super.visit(name, AsmObjectTranslator.translateValue(translator, value)); - } - - @Override - public AnnotationVisitor visitArray(String name) { - return new TranslationAnnotationVisitor(translator, annotationEntry, api, super.visitArray(name)); - } - - @Override - public AnnotationVisitor visitAnnotation(String name, String desc) { - TypeDescriptor type = new TypeDescriptor(desc); - if (name != null) { - FieldEntry annotationField = translator.translate(new FieldEntry(annotationEntry, name, type)); - return super.visitAnnotation(annotationField.getName(), annotationField.getDesc().toString()); - } else { - return super.visitAnnotation(null, translator.translate(type).toString()); - } - } - - @Override - public void visitEnum(String name, String desc, String value) { - TypeDescriptor type = new TypeDescriptor(desc); - FieldEntry enumField = translator.translate(new FieldEntry(type.getTypeEntry(), value, type)); - if (name != null) { - FieldEntry annotationField = translator.translate(new FieldEntry(annotationEntry, name, type)); - super.visitEnum(annotationField.getName(), annotationField.getDesc().toString(), enumField.getName()); - } else { - super.visitEnum(null, translator.translate(type).toString(), enumField.getName()); - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java deleted file mode 100644 index e4c41d32..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java +++ /dev/null @@ -1,102 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.bytecode.translators; - -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.representation.MethodDescriptor; -import cuchaz.enigma.translation.representation.TypeDescriptor; -import cuchaz.enigma.translation.representation.entry.*; -import org.objectweb.asm.*; - -import java.util.Arrays; - -public class TranslationClassVisitor extends ClassVisitor { - private final Translator translator; - - private ClassDefEntry obfClassEntry; - - public TranslationClassVisitor(Translator translator, int api, ClassVisitor cv) { - super(api, cv); - this.translator = translator; - } - - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - obfClassEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces); - - ClassDefEntry translatedEntry = translator.translate(obfClassEntry); - String translatedSuper = translatedEntry.getSuperClass() != null ? translatedEntry.getSuperClass().getFullName() : null; - String[] translatedInterfaces = Arrays.stream(translatedEntry.getInterfaces()).map(ClassEntry::getFullName).toArray(String[]::new); - - super.visit(version, translatedEntry.getAccess().getFlags(), translatedEntry.getFullName(), translatedEntry.getSignature().toString(), translatedSuper, translatedInterfaces); - } - - @Override - public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { - FieldDefEntry entry = FieldDefEntry.parse(obfClassEntry, access, name, desc, signature); - FieldDefEntry translatedEntry = translator.translate(entry); - FieldVisitor fv = super.visitField(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), value); - return new TranslationFieldVisitor(translator, translatedEntry, api, fv); - } - - @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - MethodDefEntry entry = MethodDefEntry.parse(obfClassEntry, access, name, desc, signature); - MethodDefEntry translatedEntry = translator.translate(entry); - String[] translatedExceptions = new String[exceptions.length]; - for (int i = 0; i < exceptions.length; i++) { - translatedExceptions[i] = translator.translate(new ClassEntry(exceptions[i])).getFullName(); - } - MethodVisitor mv = super.visitMethod(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), translatedExceptions); - return new TranslationMethodVisitor(translator, obfClassEntry, entry, api, mv); - } - - @Override - public void visitInnerClass(String name, String outerName, String innerName, int access) { - ClassDefEntry classEntry = ClassDefEntry.parse(access, name, obfClassEntry.getSignature().toString(), null, new String[0]); - ClassDefEntry translatedEntry = translator.translate(classEntry); - ClassEntry translatedOuterClass = translatedEntry.getOuterClass(); - if (translatedOuterClass == null) { - throw new IllegalStateException("Translated inner class did not have outer class"); - } - - // Anonymous classes do not specify an outer or inner name. As we do not translate from the given parameter, ignore if the input is null - String translatedName = translatedEntry.getFullName(); - String translatedOuterName = outerName != null ? translatedOuterClass.getFullName() : null; - String translatedInnerName = innerName != null ? translatedEntry.getName() : null; - super.visitInnerClass(translatedName, translatedOuterName, translatedInnerName, translatedEntry.getAccess().getFlags()); - } - - @Override - public void visitOuterClass(String owner, String name, String desc) { - if (desc != null) { - MethodEntry translatedEntry = translator.translate(new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc))); - super.visitOuterClass(translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString()); - } else { - super.visitOuterClass(owner, name, desc); - } - } - - @Override - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - TypeDescriptor translatedDesc = translator.translate(new TypeDescriptor(desc)); - AnnotationVisitor av = super.visitAnnotation(translatedDesc.toString(), visible); - return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av); - } - - @Override - public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { - TypeDescriptor translatedDesc = translator.translate(new TypeDescriptor(desc)); - AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, translatedDesc.toString(), visible); - return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av); - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java deleted file mode 100644 index 28fc199c..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java +++ /dev/null @@ -1,33 +0,0 @@ -package cuchaz.enigma.bytecode.translators; - -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.representation.TypeDescriptor; -import cuchaz.enigma.translation.representation.entry.FieldDefEntry; -import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.FieldVisitor; -import org.objectweb.asm.TypePath; - -public class TranslationFieldVisitor extends FieldVisitor { - private final FieldDefEntry fieldEntry; - private final Translator translator; - - public TranslationFieldVisitor(Translator translator, FieldDefEntry fieldEntry, int api, FieldVisitor fv) { - super(api, fv); - this.translator = translator; - this.fieldEntry = fieldEntry; - } - - @Override - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); - AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible); - return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); - } - - @Override - public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { - TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); - AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible); - return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java deleted file mode 100644 index a82df1b0..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java +++ /dev/null @@ -1,145 +0,0 @@ -package cuchaz.enigma.bytecode.translators; - -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.representation.MethodDescriptor; -import cuchaz.enigma.translation.representation.Signature; -import cuchaz.enigma.translation.representation.TypeDescriptor; -import cuchaz.enigma.translation.representation.entry.*; -import org.objectweb.asm.*; - -public class TranslationMethodVisitor extends MethodVisitor { - private final MethodDefEntry methodEntry; - private final Translator translator; - - private int parameterIndex = 0; - private int parameterLvIndex; - - public TranslationMethodVisitor(Translator translator, ClassDefEntry ownerEntry, MethodDefEntry methodEntry, int api, MethodVisitor mv) { - super(api, mv); - this.translator = translator; - this.methodEntry = methodEntry; - - parameterLvIndex = methodEntry.getAccess().isStatic() ? 0 : 1; - } - - @Override - public void visitParameter(String name, int access) { - name = translateVariableName(parameterLvIndex, name); - parameterLvIndex += methodEntry.getDesc().getArgumentDescs().get(parameterIndex++).getSize(); - - super.visitParameter(name, access); - } - - @Override - public void visitFieldInsn(int opcode, String owner, String name, String desc) { - FieldEntry entry = new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc)); - FieldEntry translatedEntry = translator.translate(entry); - super.visitFieldInsn(opcode, translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString()); - } - - @Override - public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { - MethodEntry entry = new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc)); - MethodEntry translatedEntry = translator.translate(entry); - super.visitMethodInsn(opcode, translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString(), itf); - } - - @Override - public void visitFrame(int type, int localCount, Object[] locals, int stackCount, Object[] stack) { - Object[] translatedLocals = this.getTranslatedFrame(locals, localCount); - Object[] translatedStack = this.getTranslatedFrame(stack, stackCount); - super.visitFrame(type, localCount, translatedLocals, stackCount, translatedStack); - } - - private Object[] getTranslatedFrame(Object[] array, int count) { - if (array == null) { - return null; - } - for (int i = 0; i < count; i++) { - Object object = array[i]; - if (object instanceof String) { - String type = (String) object; - array[i] = translator.translate(new ClassEntry(type)).getFullName(); - } - } - return array; - } - - @Override - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); - AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible); - return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); - } - - @Override - public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { - TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); - AnnotationVisitor av = super.visitParameterAnnotation(parameter, typeDesc.toString(), visible); - return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); - } - - @Override - public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { - TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc)); - AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, typeDesc.toString(), visible); - return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av); - } - - @Override - public void visitTypeInsn(int opcode, String type) { - ClassEntry translatedEntry = translator.translate(new ClassEntry(type)); - super.visitTypeInsn(opcode, translatedEntry.getFullName()); - } - - @Override - public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { - MethodDescriptor translatedMethodDesc = translator.translate(new MethodDescriptor(desc)); - Object[] translatedBsmArgs = new Object[bsmArgs.length]; - for (int i = 0; i < bsmArgs.length; i++) { - translatedBsmArgs[i] = AsmObjectTranslator.translateValue(translator, bsmArgs[i]); - } - super.visitInvokeDynamicInsn(name, translatedMethodDesc.toString(), AsmObjectTranslator.translateHandle(translator, bsm), translatedBsmArgs); - } - - @Override - public void visitLdcInsn(Object cst) { - super.visitLdcInsn(AsmObjectTranslator.translateValue(translator, cst)); - } - - @Override - public void visitMultiANewArrayInsn(String desc, int dims) { - super.visitMultiANewArrayInsn(translator.translate(new TypeDescriptor(desc)).toString(), dims); - } - - @Override - public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { - if (type != null) { - ClassEntry translatedEntry = translator.translate(new ClassEntry(type)); - super.visitTryCatchBlock(start, end, handler, translatedEntry.getFullName()); - } else { - super.visitTryCatchBlock(start, end, handler, type); - } - } - - @Override - public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { - signature = translator.translate(Signature.createTypedSignature(signature)).toString(); - name = translateVariableName(index, name); - desc = translator.translate(new TypeDescriptor(desc)).toString(); - - super.visitLocalVariable(name, desc, signature, start, end, index); - } - - private String translateVariableName(int index, String name) { - LocalVariableEntry entry = new LocalVariableEntry(methodEntry, index, "", true,null); - LocalVariableEntry translatedEntry = translator.translate(entry); - String translatedName = translatedEntry.getName(); - - if (!translatedName.isEmpty()) { - return translatedName; - } - - return name; - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java deleted file mode 100644 index eebd6509..00000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java +++ /dev/null @@ -1,129 +0,0 @@ -package cuchaz.enigma.bytecode.translators; - -import cuchaz.enigma.utils.Utils; -import org.objectweb.asm.signature.SignatureVisitor; - -import java.util.Stack; -import java.util.function.Function; - -public class TranslationSignatureVisitor extends SignatureVisitor { - private final Function remapper; - - private final SignatureVisitor sv; - private final Stack classStack = new Stack<>(); - - public TranslationSignatureVisitor(Function remapper, SignatureVisitor sv) { - super(Utils.ASM_VERSION); - this.remapper = remapper; - this.sv = sv; - } - - @Override - public void visitClassType(String name) { - classStack.push(name); - String translatedEntry = this.remapper.apply(name); - this.sv.visitClassType(translatedEntry); - } - - @Override - public void visitInnerClassType(String name) { - String lastClass = classStack.pop(); - if (!name.startsWith(lastClass+"$")){//todo see if there's a way to base this on whether there were type params or not - name = lastClass+"$"+name; - } - String translatedEntry = this.remapper.apply(name); - if (translatedEntry.contains("/")){ - translatedEntry = translatedEntry.substring(translatedEntry.lastIndexOf("/")+1); - } - if (translatedEntry.contains("$")){ - translatedEntry = translatedEntry.substring(translatedEntry.lastIndexOf("$")+1); - } - this.sv.visitInnerClassType(translatedEntry); - } - - @Override - public void visitFormalTypeParameter(String name) { - this.sv.visitFormalTypeParameter(name); - } - - @Override - public void visitTypeVariable(String name) { - this.sv.visitTypeVariable(name); - } - - @Override - public SignatureVisitor visitArrayType() { - this.sv.visitArrayType(); - return this; - } - - @Override - public void visitBaseType(char descriptor) { - this.sv.visitBaseType(descriptor); - } - - @Override - public SignatureVisitor visitClassBound() { - this.sv.visitClassBound(); - return this; - } - - @Override - public SignatureVisitor visitExceptionType() { - this.sv.visitExceptionType(); - return this; - } - - @Override - public SignatureVisitor visitInterface() { - this.sv.visitInterface(); - return this; - } - - @Override - public SignatureVisitor visitInterfaceBound() { - this.sv.visitInterfaceBound(); - return this; - } - - @Override - public SignatureVisitor visitParameterType() { - this.sv.visitParameterType(); - return this; - } - - @Override - public SignatureVisitor visitReturnType() { - this.sv.visitReturnType(); - return this; - } - - @Override - public SignatureVisitor visitSuperclass() { - this.sv.visitSuperclass(); - return this; - } - - @Override - public void visitTypeArgument() { - this.sv.visitTypeArgument(); - } - - @Override - public SignatureVisitor visitTypeArgument(char wildcard) { - this.sv.visitTypeArgument(wildcard); - return this; - } - - @Override - public void visitEnd() { - this.sv.visitEnd(); - if (!classStack.empty()) - classStack.pop(); - } - - @Override - public String toString() { - return this.sv.toString(); - } -} diff --git a/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java b/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java deleted file mode 100644 index 9d238e3a..00000000 --- a/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java +++ /dev/null @@ -1,77 +0,0 @@ -package cuchaz.enigma.command; - -import cuchaz.enigma.Enigma; -import cuchaz.enigma.EnigmaProject; -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; -import cuchaz.enigma.translation.mapping.serde.MappingFormat; -import cuchaz.enigma.translation.mapping.tree.EntryTree; -import cuchaz.enigma.translation.representation.entry.ClassEntry; - -import java.nio.file.Path; -import java.util.Set; -import java.util.stream.Collectors; - -public class CheckMappingsCommand extends Command { - - public CheckMappingsCommand() { - super("checkmappings"); - } - - @Override - public String getUsage() { - return " "; - } - - @Override - public boolean isValidArgument(int length) { - return length == 2; - } - - @Override - public void run(String... args) throws Exception { - Path fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)).toPath(); - Path fileMappings = getReadablePath(getArg(args, 1, "mappings file", true)); - - Enigma enigma = Enigma.create(); - - System.out.println("Reading JAR..."); - - EnigmaProject project = enigma.openJar(fileJarIn, ProgressListener.none()); - - System.out.println("Reading mappings..."); - - MappingFormat format = chooseEnigmaFormat(fileMappings); - MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters(); - - EntryTree mappings = format.read(fileMappings, ProgressListener.none(), saveParameters); - project.setMappings(mappings); - - JarIndex idx = project.getJarIndex(); - - boolean error = false; - - for (Set partition : idx.getPackageVisibilityIndex().getPartitions()) { - long packages = partition.stream() - .map(project.getMapper()::deobfuscate) - .map(ClassEntry::getPackageName) - .distinct() - .count(); - if (packages > 1) { - error = true; - System.err.println("ERROR: Must be in one package:\n" + partition.stream() - .map(project.getMapper()::deobfuscate) - .map(ClassEntry::toString) - .sorted() - .collect(Collectors.joining("\n")) - ); - } - } - - if (error) { - throw new IllegalStateException("Errors in package visibility detected, see SysErr above"); - } - } -} diff --git a/src/main/java/cuchaz/enigma/command/Command.java b/src/main/java/cuchaz/enigma/command/Command.java deleted file mode 100644 index 09dd3216..00000000 --- a/src/main/java/cuchaz/enigma/command/Command.java +++ /dev/null @@ -1,154 +0,0 @@ -package cuchaz.enigma.command; - -import cuchaz.enigma.Enigma; -import cuchaz.enigma.EnigmaProject; -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; -import cuchaz.enigma.translation.mapping.serde.MappingFormat; -import cuchaz.enigma.translation.mapping.tree.EntryTree; - -import java.io.File; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -import com.google.common.io.MoreFiles; - -public abstract class Command { - public final String name; - - protected Command(String name) { - this.name = name; - } - - public abstract String getUsage(); - - public abstract boolean isValidArgument(int length); - - public abstract void run(String... args) throws Exception; - - protected static EnigmaProject openProject(Path fileJarIn, Path fileMappings) throws Exception { - ProgressListener progress = new ConsoleProgressListener(); - - Enigma enigma = Enigma.create(); - - System.out.println("Reading jar..."); - EnigmaProject project = enigma.openJar(fileJarIn, progress); - - if (fileMappings != null) { - System.out.println("Reading mappings..."); - - MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters(); - EntryTree mappings = chooseEnigmaFormat(fileMappings).read(fileMappings, progress, saveParameters); - - project.setMappings(mappings); - } - - return project; - } - - protected static MappingFormat chooseEnigmaFormat(Path path) { - if (Files.isDirectory(path)) { - return MappingFormat.ENIGMA_DIRECTORY; - } else if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(path))) { - return MappingFormat.ENIGMA_ZIP; - } else { - return MappingFormat.ENIGMA_FILE; - } - } - - protected static File getWritableFile(String path) { - if (path == null) { - return null; - } - File file = new File(path).getAbsoluteFile(); - File dir = file.getParentFile(); - if (dir == null) { - throw new IllegalArgumentException("Cannot write file: " + path); - } - // quick fix to avoid stupid stuff in Gradle code - if (!dir.isDirectory()) { - dir.mkdirs(); - } - return file; - } - - protected static File getWritableFolder(String path) { - if (path == null) { - return null; - } - File dir = new File(path).getAbsoluteFile(); - if (!dir.exists()) { - throw new IllegalArgumentException("Cannot write to folder: " + dir); - } - return dir; - } - - protected static File getReadableFile(String path) { - if (path == null) { - return null; - } - File file = new File(path).getAbsoluteFile(); - if (!file.exists()) { - throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath()); - } - return file; - } - - protected static Path getReadablePath(String path) { - if (path == null) { - return null; - } - Path file = Paths.get(path).toAbsolutePath(); - if (!Files.exists(file)) { - throw new IllegalArgumentException("Cannot find file: " + file.toString()); - } - return file; - } - - protected static String getArg(String[] args, int i, String name, boolean required) { - if (i >= args.length) { - if (required) { - throw new IllegalArgumentException(name + " is required"); - } else { - return null; - } - } - return args[i]; - } - - public static class ConsoleProgressListener implements ProgressListener { - - private static final int ReportTime = 5000; // 5s - - private int totalWork; - private long startTime; - private long lastReportTime; - - @Override - public void init(int totalWork, String title) { - this.totalWork = totalWork; - this.startTime = System.currentTimeMillis(); - this.lastReportTime = this.startTime; - System.out.println(title); - } - - @Override - public void step(int numDone, String message) { - long now = System.currentTimeMillis(); - boolean isLastUpdate = numDone == this.totalWork; - boolean shouldReport = isLastUpdate || now - this.lastReportTime > ReportTime; - - if (shouldReport) { - int percent = numDone * 100 / this.totalWork; - System.out.println(String.format("\tProgress: %3d%%", percent)); - this.lastReportTime = now; - } - if (isLastUpdate) { - double elapsedSeconds = (now - this.startTime) / 1000.0; - System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds)); - } - } - } -} diff --git a/src/main/java/cuchaz/enigma/command/ComposeMappingsCommand.java b/src/main/java/cuchaz/enigma/command/ComposeMappingsCommand.java deleted file mode 100644 index f57f1fa6..00000000 --- a/src/main/java/cuchaz/enigma/command/ComposeMappingsCommand.java +++ /dev/null @@ -1,41 +0,0 @@ -package cuchaz.enigma.command; - -import cuchaz.enigma.throwables.MappingParseException; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.MappingFileNameFormat; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; -import cuchaz.enigma.translation.mapping.tree.EntryTree; -import cuchaz.enigma.utils.Utils; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; - -public class ComposeMappingsCommand extends Command { - public ComposeMappingsCommand() { - super("compose-mappings"); - } - - @Override - public String getUsage() { - return " "; - } - - @Override - public boolean isValidArgument(int length) { - return length == 7; - } - - @Override - public void run(String... args) throws IOException, MappingParseException { - MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); - - EntryTree left = MappingCommandsUtil.read(args[0], Paths.get(args[1]), saveParameters); - EntryTree right = MappingCommandsUtil.read(args[2], Paths.get(args[3]), saveParameters); - EntryTree result = MappingCommandsUtil.compose(left, right, args[6].equals("left") || args[6].equals("both"), args[6].equals("right") || args[6].equals("both")); - - Path output = Paths.get(args[5]); - Utils.delete(output); - MappingCommandsUtil.write(result, args[4], output, saveParameters); - } -} diff --git a/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java b/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java deleted file mode 100644 index 689df02d..00000000 --- a/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java +++ /dev/null @@ -1,39 +0,0 @@ -package cuchaz.enigma.command; - -import cuchaz.enigma.throwables.MappingParseException; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.MappingFileNameFormat; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; -import cuchaz.enigma.translation.mapping.tree.EntryTree; -import cuchaz.enigma.utils.Utils; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; - -public class ConvertMappingsCommand extends Command { - public ConvertMappingsCommand() { - super("convert-mappings"); - } - - @Override - public String getUsage() { - return " "; - } - - @Override - public boolean isValidArgument(int length) { - return length == 4; - } - - @Override - public void run(String... args) throws IOException, MappingParseException { - MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); - - EntryTree mappings = MappingCommandsUtil.read(args[0], Paths.get(args[1]), saveParameters); - - Path output = Paths.get(args[3]); - Utils.delete(output); - MappingCommandsUtil.write(mappings, args[2], output, saveParameters); - } -} diff --git a/src/main/java/cuchaz/enigma/command/DecompileCommand.java b/src/main/java/cuchaz/enigma/command/DecompileCommand.java deleted file mode 100644 index 3d15dac6..00000000 --- a/src/main/java/cuchaz/enigma/command/DecompileCommand.java +++ /dev/null @@ -1,54 +0,0 @@ -package cuchaz.enigma.command; - -import cuchaz.enigma.EnigmaProject; -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.source.DecompilerService; -import cuchaz.enigma.source.Decompilers; - -import java.lang.reflect.Field; -import java.nio.file.Path; -import java.util.Locale; - -public class DecompileCommand extends Command { - - public DecompileCommand() { - super("decompile"); - } - - @Override - public String getUsage() { - return " []"; - } - - @Override - public boolean isValidArgument(int length) { - return length == 2 || length == 3; - } - - @Override - public void run(String... args) throws Exception { - String decompilerName = getArg(args, 1, "decompiler", true); - Path fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)).toPath(); - Path fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true)).toPath(); - Path fileMappings = getReadablePath(getArg(args, 3, "mappings file", false)); - - DecompilerService decompilerService; - - try { - Field decompilerField = Decompilers.class.getField(decompilerName.toUpperCase(Locale.ROOT)); - decompilerService = (DecompilerService) decompilerField.get(null); - } catch (NoSuchFieldException e) { - System.err.println("Decompiler not found."); - return; - } - - EnigmaProject project = openProject(fileJarIn, fileMappings); - - ProgressListener progress = new ConsoleProgressListener(); - - EnigmaProject.JarExport jar = project.exportRemappedJar(progress); - EnigmaProject.SourceExport source = jar.decompile(progress, decompilerService); - - source.write(fileJarOut, progress); - } -} diff --git a/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java b/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java deleted file mode 100644 index b0d2a7d0..00000000 --- a/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java +++ /dev/null @@ -1,37 +0,0 @@ -package cuchaz.enigma.command; - -import cuchaz.enigma.EnigmaProject; -import cuchaz.enigma.ProgressListener; - -import java.nio.file.Path; - -public class DeobfuscateCommand extends Command { - - public DeobfuscateCommand() { - super("deobfuscate"); - } - - @Override - public String getUsage() { - return " []"; - } - - @Override - public boolean isValidArgument(int length) { - return length == 2 || length == 3; - } - - @Override - public void run(String... args) throws Exception { - Path fileJarIn = getReadablePath(getArg(args, 0, "in jar", true)); - Path fileJarOut = getWritableFile(getArg(args, 1, "out jar", true)).toPath(); - Path fileMappings = getReadablePath(getArg(args, 2, "mappings file", false)); - - EnigmaProject project = openProject(fileJarIn, fileMappings); - - ProgressListener progress = new ConsoleProgressListener(); - - EnigmaProject.JarExport jar = project.exportRemappedJar(progress); - jar.write(fileJarOut, progress); - } -} diff --git a/src/main/java/cuchaz/enigma/command/InvertMappingsCommand.java b/src/main/java/cuchaz/enigma/command/InvertMappingsCommand.java deleted file mode 100644 index cd11e2e6..00000000 --- a/src/main/java/cuchaz/enigma/command/InvertMappingsCommand.java +++ /dev/null @@ -1,40 +0,0 @@ -package cuchaz.enigma.command; - -import cuchaz.enigma.throwables.MappingParseException; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.MappingFileNameFormat; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; -import cuchaz.enigma.translation.mapping.tree.EntryTree; -import cuchaz.enigma.utils.Utils; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; - -public class InvertMappingsCommand extends Command { - public InvertMappingsCommand() { - super("invert-mappings"); - } - - @Override - public String getUsage() { - return " "; - } - - @Override - public boolean isValidArgument(int length) { - return length == 4; - } - - @Override - public void run(String... args) throws IOException, MappingParseException { - MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); - - EntryTree source = MappingCommandsUtil.read(args[0], Paths.get(args[1]), saveParameters); - EntryTree result = MappingCommandsUtil.invert(source); - - Path output = Paths.get(args[3]); - Utils.delete(output); - MappingCommandsUtil.write(result, args[2], output, saveParameters); - } -} diff --git a/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java b/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java deleted file mode 100644 index eb8d5dcc..00000000 --- a/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java +++ /dev/null @@ -1,69 +0,0 @@ -package cuchaz.enigma.command; - -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.analysis.ClassCache; -import cuchaz.enigma.analysis.index.BridgeMethodIndex; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.throwables.MappingParseException; -import cuchaz.enigma.translation.MappingTranslator; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.*; -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.MethodEntry; -import cuchaz.enigma.utils.Utils; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Map; - -public class MapSpecializedMethodsCommand extends Command { - public MapSpecializedMethodsCommand() { - super("map-specialized-methods"); - } - - @Override - public String getUsage() { - return " "; - } - - @Override - public boolean isValidArgument(int length) { - return length == 5; - } - - @Override - public void run(String... args) throws IOException, MappingParseException { - run(Paths.get(args[0]), args[1], Paths.get(args[2]), args[3], Paths.get(args[4])); - } - - public static void run(Path jar, String sourceFormat, Path sourcePath, String resultFormat, Path output) throws IOException, MappingParseException { - MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); - EntryTree source = MappingCommandsUtil.read(sourceFormat, sourcePath, saveParameters); - EntryTree result = new HashEntryTree<>(); - ClassCache classCache = ClassCache.of(jar); - JarIndex jarIndex = classCache.index(ProgressListener.none()); - BridgeMethodIndex bridgeMethodIndex = jarIndex.getBridgeMethodIndex(); - Translator translator = new MappingTranslator(source, jarIndex.getEntryResolver()); - - // Copy all non-specialized methods - for (EntryTreeNode node : source) { - if (!(node.getEntry() instanceof MethodEntry) || !bridgeMethodIndex.isSpecializedMethod((MethodEntry) node.getEntry())) { - result.insert(node.getEntry(), node.getValue()); - } - } - - // Add correct mappings for specialized methods - for (Map.Entry entry : bridgeMethodIndex.getBridgeToSpecialized().entrySet()) { - MethodEntry bridge = entry.getKey(); - MethodEntry specialized = entry.getValue(); - String name = translator.translate(bridge).getName(); - result.insert(specialized, new EntryMapping(name)); - } - - Utils.delete(output); - MappingCommandsUtil.write(result, resultFormat, output, saveParameters); - } -} diff --git a/src/main/java/cuchaz/enigma/command/MappingCommandsUtil.java b/src/main/java/cuchaz/enigma/command/MappingCommandsUtil.java deleted file mode 100644 index fc7afbc0..00000000 --- a/src/main/java/cuchaz/enigma/command/MappingCommandsUtil.java +++ /dev/null @@ -1,148 +0,0 @@ -package cuchaz.enigma.command; - -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.throwables.MappingParseException; -import cuchaz.enigma.translation.MappingTranslator; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; -import cuchaz.enigma.translation.mapping.VoidEntryResolver; -import cuchaz.enigma.translation.mapping.serde.*; -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.ClassEntry; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.translation.representation.entry.FieldEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.HashSet; -import java.util.Set; - -public final class MappingCommandsUtil { - private MappingCommandsUtil() {} - - public static EntryTree invert(EntryTree mappings) { - Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE); - EntryTree result = new HashEntryTree<>(); - - for (EntryTreeNode node : mappings) { - Entry leftEntry = node.getEntry(); - EntryMapping leftMapping = node.getValue(); - - if (!(leftEntry instanceof ClassEntry || leftEntry instanceof MethodEntry || leftEntry instanceof FieldEntry)) { - result.insert(translator.translate(leftEntry), leftMapping); - continue; - } - - Entry rightEntry = translator.translate(leftEntry); - - result.insert(rightEntry, leftMapping == null ? null : new EntryMapping(leftEntry.getName())); // TODO: leftMapping.withName once javadoc PR is merged - } - - return result; - } - - public static EntryTree compose(EntryTree left, EntryTree right, boolean keepLeftOnly, boolean keepRightOnly) { - Translator leftTranslator = new MappingTranslator(left, VoidEntryResolver.INSTANCE); - EntryTree result = new HashEntryTree<>(); - Set> addedMappings = new HashSet<>(); - - for (EntryTreeNode node : left) { - Entry leftEntry = node.getEntry(); - EntryMapping leftMapping = node.getValue(); - - Entry rightEntry = leftTranslator.translate(leftEntry); - - EntryMapping rightMapping = right.get(rightEntry); - if (rightMapping != null) { - result.insert(leftEntry, rightMapping); - addedMappings.add(rightEntry); - } else if (keepLeftOnly) { - result.insert(leftEntry, leftMapping); - } - } - - if (keepRightOnly) { - Translator leftInverseTranslator = new MappingTranslator(invert(left), VoidEntryResolver.INSTANCE); - for (EntryTreeNode node : right) { - Entry rightEntry = node.getEntry(); - EntryMapping rightMapping = node.getValue(); - - if (!addedMappings.contains(rightEntry)) { - result.insert(leftInverseTranslator.translate(rightEntry), rightMapping); - } - } - } - return result; - } - - public static EntryTree read(String type, Path path, MappingSaveParameters saveParameters) throws MappingParseException, IOException { - if (type.equals("enigma")) { - return (Files.isDirectory(path) ? EnigmaMappingsReader.DIRECTORY : EnigmaMappingsReader.ZIP).read(path, ProgressListener.none(), saveParameters); - } - - if (type.equals("tiny")) { - return TinyMappingsReader.INSTANCE.read(path, ProgressListener.none(), saveParameters); - } - - MappingFormat format = null; - try { - format = MappingFormat.valueOf(type.toUpperCase()); - } catch (IllegalArgumentException ignored) { - if (type.equals("tinyv2")) { - format = MappingFormat.TINY_V2; - } - } - - if (format != null) { - return format.getReader().read(path, ProgressListener.none(), saveParameters); - } - - throw new IllegalArgumentException("no reader for " + type); - } - - public static void write(EntryTree mappings, String type, Path path, MappingSaveParameters saveParameters) { - if (type.equals("enigma")) { - EnigmaMappingsWriter.DIRECTORY.write(mappings, path, ProgressListener.none(), saveParameters); - return; - } - - if (type.startsWith("tinyv2:") || type.startsWith("tiny_v2:")) { - String[] split = type.split(":"); - - if (split.length != 3) { - throw new IllegalArgumentException("specify column names as 'tinyv2:from_namespace:to_namespace'"); - } - - new TinyV2Writer(split[1], split[2]).write(mappings, path, ProgressListener.none(), saveParameters); - return; - } - - if (type.startsWith("tiny:")) { - String[] split = type.split(":"); - - if (split.length != 3) { - throw new IllegalArgumentException("specify column names as 'tiny:from_column:to_column'"); - } - - new TinyMappingsWriter(split[1], split[2]).write(mappings, path, ProgressListener.none(), saveParameters); - return; - } - - MappingFormat format = null; - try { - format = MappingFormat.valueOf(type.toUpperCase()); - } catch (IllegalArgumentException ignored) {} - - if (format != null) { - format.getWriter().write(mappings, path, ProgressListener.none(), saveParameters); - return; - } - - throw new IllegalArgumentException("no writer for " + type); - } -} diff --git a/src/main/java/cuchaz/enigma/config/Config.java b/src/main/java/cuchaz/enigma/config/Config.java deleted file mode 100644 index e116fce9..00000000 --- a/src/main/java/cuchaz/enigma/config/Config.java +++ /dev/null @@ -1,261 +0,0 @@ -package cuchaz.enigma.config; - -import com.bulenkov.darcula.DarculaLaf; -import com.google.common.io.Files; -import com.google.gson.*; -import cuchaz.enigma.source.DecompilerService; -import cuchaz.enigma.source.Decompilers; - -import cuchaz.enigma.utils.I18n; - -import javax.swing.*; -import javax.swing.plaf.metal.MetalLookAndFeel; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Type; -import java.nio.charset.Charset; - -public class Config { - public static class AlphaColorEntry { - public Integer rgb; - public float alpha = 1.0f; - - public AlphaColorEntry(Integer rgb, float alpha) { - this.rgb = rgb; - this.alpha = alpha; - } - - public Color get() { - if (rgb == null) { - return new Color(0, 0, 0, 0); - } - - Color baseColor = new Color(rgb); - return new Color(baseColor.getRed(), baseColor.getGreen(), baseColor.getBlue(), (int)(255 * alpha)); - } - } - - public enum LookAndFeel { - DEFAULT("Default"), - DARCULA("Darcula"), - SYSTEM("System"), - NONE("None (JVM default)"); - - // the "JVM default" look and feel, get it at the beginning and store it so we can set it later - private static javax.swing.LookAndFeel NONE_LAF = UIManager.getLookAndFeel(); - private final String name; - - LookAndFeel(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public void setGlobalLAF() { - try { - switch (this) { - case NONE: - UIManager.setLookAndFeel(NONE_LAF); - break; - case DEFAULT: - UIManager.setLookAndFeel(new MetalLookAndFeel()); - break; - case DARCULA: - UIManager.setLookAndFeel(new DarculaLaf()); - break; - case SYSTEM: - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } - } catch (Exception e){ - throw new Error("Failed to set global look and feel", e); - } - } - - public static boolean isDarkLaf() { - // a bit of a hack because swing doesn't give any API for that, and we need colors that aren't defined in look and feel - JPanel panel = new JPanel(); - panel.setSize(new Dimension(10, 10)); - panel.doLayout(); - - BufferedImage image = new BufferedImage(panel.getSize().width, panel.getSize().height, BufferedImage.TYPE_INT_RGB); - panel.printAll(image.getGraphics()); - - Color c = new Color(image.getRGB(0, 0)); - - // convert the color we got to grayscale - int b = (int) (0.3 * c.getRed() + 0.59 * c.getGreen() + 0.11 * c.getBlue()); - return b < 85; - } - - public void apply(Config config) { - boolean isDark = this == LookAndFeel.DARCULA || isDarkLaf(); - if (!isDark) {//Defaults found here: https://github.com/Sciss/SyntaxPane/blob/122da367ff7a5d31627a70c62a48a9f0f4f85a0a/src/main/resources/de/sciss/syntaxpane/defaultsyntaxkit/config.properties#L139 - config.lineNumbersForeground = 0x333300; - config.lineNumbersBackground = 0xEEEEFF; - config.lineNumbersSelected = 0xCCCCEE; - config.obfuscatedColor = new AlphaColorEntry(0xFFDCDC, 1.0f); - config.obfuscatedColorOutline = new AlphaColorEntry(0xA05050, 1.0f); - config.proposedColor = new AlphaColorEntry(0x000000, 0.075f); - config.proposedColorOutline = new AlphaColorEntry(0x000000, 0.15f); - config.deobfuscatedColor = new AlphaColorEntry(0xDCFFDC, 1.0f); - config.deobfuscatedColorOutline = new AlphaColorEntry(0x50A050, 1.0f); - config.editorBackground = 0xFFFFFF; - config.highlightColor = 0x3333EE; - config.caretColor = 0x000000; - config.selectionHighlightColor = 0x000000; - config.stringColor = 0xCC6600; - config.numberColor = 0x999933; - config.operatorColor = 0x000000; - config.delimiterColor = 0x000000; - config.typeColor = 0x000000; - config.identifierColor = 0x000000; - config.defaultTextColor = 0x000000; - } else {//Based off colors found here: https://github.com/dracula/dracula-theme/ - config.lineNumbersForeground = 0xA4A4A3; - config.lineNumbersBackground = 0x313335; - config.lineNumbersSelected = 0x606366; - config.obfuscatedColor = new AlphaColorEntry(0xFF5555, 0.3f); - config.obfuscatedColorOutline = new AlphaColorEntry(0xFF5555, 0.5f); - config.deobfuscatedColor = new AlphaColorEntry(0x50FA7B, 0.3f); - config.deobfuscatedColorOutline = new AlphaColorEntry(0x50FA7B, 0.5f); - config.proposedColor = new AlphaColorEntry(0x606366, 0.3f); - config.proposedColorOutline = new AlphaColorEntry(0x606366, 0.5f); - config.editorBackground = 0x282A36; - config.highlightColor = 0xFF79C6; - config.caretColor = 0xF8F8F2; - config.selectionHighlightColor = 0xF8F8F2; - config.stringColor = 0xF1FA8C; - config.numberColor = 0xBD93F9; - config.operatorColor = 0xF8F8F2; - config.delimiterColor = 0xF8F8F2; - config.typeColor = 0xF8F8F2; - config.identifierColor = 0xF8F8F2; - config.defaultTextColor = 0xF8F8F2; - } - } - } - - public enum Decompiler { - PROCYON("Procyon", Decompilers.PROCYON), - CFR("CFR", Decompilers.CFR); - - public final DecompilerService service; - public final String name; - - Decompiler(String name, DecompilerService service) { - this.name = name; - this.service = service; - } - } - - private static final File DIR_HOME = new File(System.getProperty("user.home")); - private static final File ENIGMA_DIR = new File(DIR_HOME, ".enigma"); - private static final File CONFIG_FILE = new File(ENIGMA_DIR, "config.json"); - private static final Config INSTANCE = new Config(); - - private final transient Gson gson; // transient to exclude it from being exposed - - public AlphaColorEntry obfuscatedColor; - public AlphaColorEntry obfuscatedColorOutline; - public AlphaColorEntry proposedColor; - public AlphaColorEntry proposedColorOutline; - public AlphaColorEntry deobfuscatedColor; - public AlphaColorEntry deobfuscatedColorOutline; - - public Integer editorBackground; - public Integer highlightColor; - public Integer caretColor; - public Integer selectionHighlightColor; - - public Integer stringColor; - public Integer numberColor; - public Integer operatorColor; - public Integer delimiterColor; - public Integer typeColor; - public Integer identifierColor; - public Integer defaultTextColor; - - public Integer lineNumbersBackground; - public Integer lineNumbersSelected; - public Integer lineNumbersForeground; - - public String language = I18n.DEFAULT_LANGUAGE; - - public LookAndFeel lookAndFeel = LookAndFeel.DEFAULT; - - public float scaleFactor = 1.0f; - - public Decompiler decompiler = Decompiler.PROCYON; - - private Config() { - gson = new GsonBuilder() - .registerTypeAdapter(Integer.class, new IntSerializer()) - .registerTypeAdapter(Integer.class, new IntDeserializer()) - .registerTypeAdapter(Config.class, (InstanceCreator) type -> this) - .setPrettyPrinting() - .create(); - try { - this.loadConfig(); - } catch (IOException ignored) { - try { - this.reset(); - } catch (IOException ignored1) { - } - } - } - - public void loadConfig() throws IOException { - if (!ENIGMA_DIR.exists()) ENIGMA_DIR.mkdirs(); - File configFile = new File(ENIGMA_DIR, "config.json"); - boolean loaded = false; - - if (configFile.exists()) { - try { - gson.fromJson(Files.asCharSource(configFile, Charset.defaultCharset()).read(), Config.class); - loaded = true; - } catch (Exception e) { - e.printStackTrace(); - } - } - - if (!loaded) { - this.reset(); - Files.touch(configFile); - } - saveConfig(); - } - - public void saveConfig() throws IOException { - Files.asCharSink(CONFIG_FILE, Charset.defaultCharset()).write(gson.toJson(this)); - } - - public void reset() throws IOException { - this.lookAndFeel = LookAndFeel.DEFAULT; - this.lookAndFeel.apply(this); - this.decompiler = Decompiler.PROCYON; - this.language = I18n.DEFAULT_LANGUAGE; - this.saveConfig(); - } - - private static class IntSerializer implements JsonSerializer { - @Override - public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) { - return new JsonPrimitive("#" + Integer.toHexString(src).toUpperCase()); - } - } - - private static class IntDeserializer implements JsonDeserializer { - @Override - public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { - return (int) Long.parseLong(json.getAsString().replace("#", ""), 16); - } - } - - public static Config getInstance() { - return INSTANCE; - } -} diff --git a/src/main/java/cuchaz/enigma/config/Themes.java b/src/main/java/cuchaz/enigma/config/Themes.java deleted file mode 100644 index 547a4202..00000000 --- a/src/main/java/cuchaz/enigma/config/Themes.java +++ /dev/null @@ -1,48 +0,0 @@ -package cuchaz.enigma.config; - -import java.awt.Font; -import java.io.IOException; -import java.lang.reflect.Field; - -import javax.swing.SwingUtilities; - -import com.github.swingdpi.UiDefaultsScaler; -import com.google.common.collect.ImmutableMap; -import cuchaz.enigma.gui.EnigmaSyntaxKit; -import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.gui.highlight.BoxHighlightPainter; -import cuchaz.enigma.gui.highlight.TokenHighlightType; -import cuchaz.enigma.gui.util.ScaleUtil; -import de.sciss.syntaxpane.DefaultSyntaxKit; - -public class Themes { - - public static void setLookAndFeel(Gui gui, Config.LookAndFeel lookAndFeel) { - Config.getInstance().lookAndFeel = lookAndFeel; - updateTheme(gui); - } - - public static void updateTheme(Gui gui) { - Config config = Config.getInstance(); - config.lookAndFeel.setGlobalLAF(); - config.lookAndFeel.apply(config); - try { - config.saveConfig(); - } catch (IOException e) { - e.printStackTrace(); - } - EnigmaSyntaxKit.invalidate(); - DefaultSyntaxKit.initKit(); - DefaultSyntaxKit.registerContentType("text/enigma-sources", EnigmaSyntaxKit.class.getName()); - gui.boxHighlightPainters = ImmutableMap.of( - TokenHighlightType.OBFUSCATED, BoxHighlightPainter.create(config.obfuscatedColor, config.obfuscatedColorOutline), - TokenHighlightType.PROPOSED, BoxHighlightPainter.create(config.proposedColor, config.proposedColorOutline), - TokenHighlightType.DEOBFUSCATED, BoxHighlightPainter.create(config.deobfuscatedColor, config.deobfuscatedColorOutline) - ); - gui.setEditorTheme(config.lookAndFeel); - SwingUtilities.updateComponentTreeUI(gui.getFrame()); - ScaleUtil.applyScaling(); - } - - -} diff --git a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java b/src/main/java/cuchaz/enigma/gui/BrowserCaret.java deleted file mode 100644 index af105dbd..00000000 --- a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java +++ /dev/null @@ -1,28 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.gui; - -import javax.swing.text.DefaultCaret; - -public class BrowserCaret extends DefaultCaret { - - @Override - public boolean isSelectionVisible() { - return true; - } - - @Override - public boolean isVisible() { - return true; - } - -} diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java deleted file mode 100644 index a23e24c2..00000000 --- a/src/main/java/cuchaz/enigma/gui/ClassSelector.java +++ /dev/null @@ -1,532 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.gui; - -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.*; - -import javax.annotation.Nullable; -import javax.swing.JOptionPane; -import javax.swing.JTree; -import javax.swing.event.CellEditorListener; -import javax.swing.event.ChangeEvent; -import javax.swing.tree.*; - -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; -import cuchaz.enigma.gui.node.ClassSelectorClassNode; -import cuchaz.enigma.gui.node.ClassSelectorPackageNode; -import cuchaz.enigma.throwables.IllegalNameException; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.representation.entry.ClassEntry; - -public class ClassSelector extends JTree { - - public static final Comparator DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getFullName); - - private final GuiController controller; - - private DefaultMutableTreeNode rootNodes; - private ClassSelectionListener selectionListener; - private RenameSelectionListener renameSelectionListener; - private Comparator comparator; - - private final Map displayedObfToDeobf = new HashMap<>(); - - public ClassSelector(Gui gui, Comparator comparator, boolean isRenamable) { - this.comparator = comparator; - this.controller = gui.getController(); - - // configure the tree control - setEditable(true); - setRootVisible(false); - setShowsRootHandles(false); - setModel(null); - - // hook events - addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent event) { - if (selectionListener != null && event.getClickCount() == 2) { - // get the selected node - TreePath path = getSelectionPath(); - if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) { - ClassSelectorClassNode node = (ClassSelectorClassNode) path.getLastPathComponent(); - selectionListener.onSelectClass(node.getObfEntry()); - } - } - } - }); - - final JTree tree = this; - - final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree, - (DefaultTreeCellRenderer) tree.getCellRenderer()) { - @Override - public boolean isCellEditable(EventObject event) { - return isRenamable && !(event instanceof MouseEvent) && super.isCellEditable(event); - } - }; - this.setCellEditor(editor); - editor.addCellEditorListener(new CellEditorListener() { - @Override - public void editingStopped(ChangeEvent e) { - String data = editor.getCellEditorValue().toString(); - TreePath path = getSelectionPath(); - - Object realPath = path.getLastPathComponent(); - if (realPath != null && realPath instanceof DefaultMutableTreeNode && data != null) { - DefaultMutableTreeNode node = (DefaultMutableTreeNode) realPath; - TreeNode parentNode = node.getParent(); - if (parentNode == null) - return; - boolean allowEdit = true; - for (int i = 0; i < parentNode.getChildCount(); i++) { - TreeNode childNode = parentNode.getChildAt(i); - if (childNode != null && childNode.toString().equals(data) && childNode != node) { - allowEdit = false; - break; - } - } - if (allowEdit && renameSelectionListener != null) { - Object prevData = node.getUserObject(); - Object objectData = node.getUserObject() instanceof ClassEntry ? new ClassEntry(((ClassEntry) prevData).getPackageName() + "/" + data) : data; - try { - renameSelectionListener.onSelectionRename(node.getUserObject(), objectData, node); - node.setUserObject(objectData); // Make sure that it's modified - } catch (IllegalNameException ex) { - JOptionPane.showOptionDialog(gui.getFrame(), ex.getMessage(), "Enigma - Error", JOptionPane.OK_OPTION, - JOptionPane.ERROR_MESSAGE, null, new String[]{"Ok"}, "OK"); - editor.cancelCellEditing(); - } - } else - editor.cancelCellEditing(); - } - - } - - @Override - public void editingCanceled(ChangeEvent e) { - // NOP - } - }); - // init defaults - this.selectionListener = null; - this.renameSelectionListener = null; - } - - public boolean isDuplicate(Object[] nodes, String data) { - int count = 0; - - for (Object node : nodes) { - if (node.toString().equals(data)) { - count++; - if (count == 2) - return true; - } - } - return false; - } - - public void setSelectionListener(ClassSelectionListener val) { - this.selectionListener = val; - } - - public void setRenameSelectionListener(RenameSelectionListener renameSelectionListener) { - this.renameSelectionListener = renameSelectionListener; - } - - public void setClasses(Collection classEntries) { - displayedObfToDeobf.clear(); - - List state = getExpansionState(this); - if (classEntries == null) { - setModel(null); - return; - } - - Translator translator = controller.project.getMapper().getDeobfuscator(); - - // build the package names - Map packages = Maps.newHashMap(); - for (ClassEntry obfClass : classEntries) { - ClassEntry deobfClass = translator.translate(obfClass); - packages.put(deobfClass.getPackageName(), null); - } - - // sort the packages - List sortedPackageNames = Lists.newArrayList(packages.keySet()); - sortedPackageNames.sort((a, b) -> - { - // I can never keep this rule straight when writing these damn things... - // a < b => -1, a == b => 0, a > b => +1 - - if (b == null || a == null) { - return 0; - } - - String[] aparts = a.split("/"); - String[] bparts = b.split("/"); - for (int i = 0; true; i++) { - if (i >= aparts.length) { - return -1; - } else if (i >= bparts.length) { - return 1; - } - - int result = aparts[i].compareTo(bparts[i]); - if (result != 0) { - return result; - } - } - }); - - // create the rootNodes node and the package nodes - rootNodes = new DefaultMutableTreeNode(); - for (String packageName : sortedPackageNames) { - ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName); - packages.put(packageName, node); - rootNodes.add(node); - } - - // put the classes into packages - Multimap packagedClassEntries = ArrayListMultimap.create(); - for (ClassEntry obfClass : classEntries) { - ClassEntry deobfClass = translator.translate(obfClass); - packagedClassEntries.put(deobfClass.getPackageName(), obfClass); - } - - // build the class nodes - for (String packageName : packagedClassEntries.keySet()) { - // sort the class entries - List classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName)); - classEntriesInPackage.sort((o1, o2) -> comparator.compare(translator.translate(o1), translator.translate(o2))); - - // create the nodes in order - for (ClassEntry obfClass : classEntriesInPackage) { - ClassEntry deobfClass = translator.translate(obfClass); - ClassSelectorPackageNode node = packages.get(packageName); - ClassSelectorClassNode classNode = new ClassSelectorClassNode(obfClass, deobfClass); - displayedObfToDeobf.put(obfClass, deobfClass); - node.add(classNode); - } - } - - // finally, update the tree control - setModel(new DefaultTreeModel(rootNodes)); - - restoreExpansionState(this, state); - } - - public ClassEntry getSelectedClass() { - if (!isSelectionEmpty()) { - Object selectedNode = getSelectionPath().getLastPathComponent(); - if (selectedNode instanceof ClassSelectorClassNode) { - ClassSelectorClassNode classNode = (ClassSelectorClassNode) selectedNode; - return classNode.getClassEntry(); - } - } - return null; - } - - public String getSelectedPackage() { - if (!isSelectionEmpty()) { - Object selectedNode = getSelectionPath().getLastPathComponent(); - if (selectedNode instanceof ClassSelectorPackageNode) { - ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode) selectedNode; - return packageNode.getPackageName(); - } else if (selectedNode instanceof ClassSelectorClassNode) { - ClassSelectorClassNode classNode = (ClassSelectorClassNode) selectedNode; - return classNode.getClassEntry().getPackageName(); - } - } - return null; - } - - public boolean isDescendant(TreePath path1, TreePath path2) { - int count1 = path1.getPathCount(); - int count2 = path2.getPathCount(); - if (count1 <= count2) { - return false; - } - while (count1 != count2) { - path1 = path1.getParentPath(); - count1--; - } - return path1.equals(path2); - } - - public enum State { - EXPANDED, - SELECTED - } - - public static class StateEntry { - public final State state; - public final TreePath path; - - public StateEntry(State state, TreePath path) { - this.state = state; - this.path = path; - } - } - - public List getExpansionState(JTree tree) { - List state = new ArrayList<>(); - int rowCount = tree.getRowCount(); - for (int i = 0; i < rowCount; i++) { - TreePath path = tree.getPathForRow(i); - if (tree.isPathSelected(path)) { - state.add(new StateEntry(State.SELECTED, path)); - } - if (tree.isExpanded(path)) { - state.add(new StateEntry(State.EXPANDED, path)); - } - } - return state; - } - - public void restoreExpansionState(JTree tree, List expansionState) { - tree.clearSelection(); - - for (StateEntry entry : expansionState) { - switch (entry.state) { - case SELECTED: - tree.addSelectionPath(entry.path); - break; - case EXPANDED: - tree.expandPath(entry.path); - break; - } - } - } - - public List packageNodes() { - List nodes = Lists.newArrayList(); - DefaultMutableTreeNode root = (DefaultMutableTreeNode) getModel().getRoot(); - Enumeration children = root.children(); - while (children.hasMoreElements()) { - ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode) children.nextElement(); - nodes.add(packageNode); - } - return nodes; - } - - public List classNodes(ClassSelectorPackageNode packageNode) { - List nodes = Lists.newArrayList(); - Enumeration children = packageNode.children(); - while (children.hasMoreElements()) { - ClassSelectorClassNode classNode = (ClassSelectorClassNode) children.nextElement(); - nodes.add(classNode); - } - return nodes; - } - - public void expandPackage(String packageName) { - if (packageName == null) { - return; - } - for (ClassSelectorPackageNode packageNode : packageNodes()) { - if (packageNode.getPackageName().equals(packageName)) { - expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode})); - return; - } - } - } - - public void expandAll() { - for (ClassSelectorPackageNode packageNode : packageNodes()) { - expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode})); - } - } - - public ClassEntry getFirstClass() { - ClassSelectorPackageNode packageNode = packageNodes().get(0); - if (packageNode != null) { - ClassSelectorClassNode classNode = classNodes(packageNode).get(0); - if (classNode != null) { - return classNode.getClassEntry(); - } - } - return null; - } - - public ClassSelectorPackageNode getPackageNode(ClassEntry entry) { - String packageName = entry.getPackageName(); - if (packageName == null) { - packageName = "(none)"; - } - for (ClassSelectorPackageNode packageNode : packageNodes()) { - if (packageNode.getPackageName().equals(packageName)) { - return packageNode; - } - } - return null; - } - - @Nullable - public ClassEntry getDisplayedDeobf(ClassEntry obfEntry) { - return displayedObfToDeobf.get(obfEntry); - } - - public ClassSelectorPackageNode getPackageNode(ClassSelector selector, ClassEntry entry) { - ClassSelectorPackageNode packageNode = getPackageNode(entry); - - if (selector != null && packageNode == null && selector.getPackageNode(entry) != null) - return selector.getPackageNode(entry); - return packageNode; - } - - public ClassEntry getNextClass(ClassEntry entry) { - boolean foundIt = false; - for (ClassSelectorPackageNode packageNode : packageNodes()) { - if (!foundIt) { - // skip to the package with our target in it - if (packageNode.getPackageName().equals(entry.getPackageName())) { - for (ClassSelectorClassNode classNode : classNodes(packageNode)) { - if (!foundIt) { - if (classNode.getClassEntry().equals(entry)) { - foundIt = true; - } - } else { - // return the next class - return classNode.getClassEntry(); - } - } - } - } else { - // return the next class - ClassSelectorClassNode classNode = classNodes(packageNode).get(0); - if (classNode != null) { - return classNode.getClassEntry(); - } - } - } - return null; - } - - public void setSelectionClass(ClassEntry classEntry) { - expandPackage(classEntry.getPackageName()); - for (ClassSelectorPackageNode packageNode : packageNodes()) { - for (ClassSelectorClassNode classNode : classNodes(packageNode)) { - if (classNode.getClassEntry().equals(classEntry)) { - TreePath path = new TreePath(new Object[]{getModel().getRoot(), packageNode, classNode}); - setSelectionPath(path); - scrollPathToVisible(path); - } - } - } - } - - public void removeNode(ClassSelectorPackageNode packageNode, ClassEntry entry) { - DefaultTreeModel model = (DefaultTreeModel) getModel(); - - if (packageNode == null) - return; - - for (int i = 0; i < packageNode.getChildCount(); i++) { - DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) packageNode.getChildAt(i); - if (childNode.getUserObject() instanceof ClassEntry && childNode.getUserObject().equals(entry)) { - model.removeNodeFromParent(childNode); - if (childNode instanceof ClassSelectorClassNode) { - displayedObfToDeobf.remove(((ClassSelectorClassNode) childNode).getObfEntry()); - } - break; - } - } - } - - public void removeNodeIfEmpty(ClassSelectorPackageNode packageNode) { - if (packageNode != null && packageNode.getChildCount() == 0) - ((DefaultTreeModel) getModel()).removeNodeFromParent(packageNode); - } - - public void moveClassIn(ClassEntry classEntry) { - removeEntry(classEntry); - insertNode(classEntry); - } - - public void moveClassOut(ClassEntry classEntry) { - removeEntry(classEntry); - } - - private void removeEntry(ClassEntry classEntry) { - ClassEntry previousDeobf = displayedObfToDeobf.get(classEntry); - if (previousDeobf != null) { - ClassSelectorPackageNode packageNode = getPackageNode(previousDeobf); - removeNode(packageNode, previousDeobf); - removeNodeIfEmpty(packageNode); - } - } - - public ClassSelectorPackageNode getOrCreatePackage(ClassEntry entry) { - DefaultTreeModel model = (DefaultTreeModel) getModel(); - ClassSelectorPackageNode newPackageNode = getPackageNode(entry); - if (newPackageNode == null) { - newPackageNode = new ClassSelectorPackageNode(entry.getPackageName()); - model.insertNodeInto(newPackageNode, (MutableTreeNode) model.getRoot(), getPlacementIndex(newPackageNode)); - } - return newPackageNode; - } - - public void insertNode(ClassEntry obfEntry) { - ClassEntry deobfEntry = controller.project.getMapper().deobfuscate(obfEntry); - ClassSelectorPackageNode packageNode = getOrCreatePackage(deobfEntry); - - DefaultTreeModel model = (DefaultTreeModel) getModel(); - ClassSelectorClassNode classNode = new ClassSelectorClassNode(obfEntry, deobfEntry); - model.insertNodeInto(classNode, packageNode, getPlacementIndex(packageNode, classNode)); - - displayedObfToDeobf.put(obfEntry, deobfEntry); - } - - public void reload() { - DefaultTreeModel model = (DefaultTreeModel) getModel(); - model.reload(rootNodes); - } - - private int getPlacementIndex(ClassSelectorPackageNode newPackageNode, ClassSelectorClassNode classNode) { - List classNodes = classNodes(newPackageNode); - classNodes.add(classNode); - classNodes.sort((a, b) -> comparator.compare(a.getClassEntry(), b.getClassEntry())); - for (int i = 0; i < classNodes.size(); i++) - if (classNodes.get(i) == classNode) - return i; - - return 0; - } - - private int getPlacementIndex(ClassSelectorPackageNode newPackageNode) { - List packageNodes = packageNodes(); - if (!packageNodes.contains(newPackageNode)) { - packageNodes.add(newPackageNode); - packageNodes.sort(Comparator.comparing(ClassSelectorPackageNode::toString)); - } - - for (int i = 0; i < packageNodes.size(); i++) - if (packageNodes.get(i) == newPackageNode) - return i; - - return 0; - } - - public interface ClassSelectionListener { - void onSelectClass(ClassEntry classEntry); - } - - public interface RenameSelectionListener { - void onSelectionRename(Object prevData, Object data, DefaultMutableTreeNode node); - } -} diff --git a/src/main/java/cuchaz/enigma/gui/CodeReader.java b/src/main/java/cuchaz/enigma/gui/CodeReader.java deleted file mode 100644 index e119640a..00000000 --- a/src/main/java/cuchaz/enigma/gui/CodeReader.java +++ /dev/null @@ -1,73 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.gui; - -import cuchaz.enigma.analysis.Token; - -import javax.swing.*; -import javax.swing.text.BadLocationException; -import javax.swing.text.Document; -import javax.swing.text.Highlighter.HighlightPainter; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -public class CodeReader extends JEditorPane { - private static final long serialVersionUID = 3673180950485748810L; - - // HACKHACK: someday we can update the main GUI to use this code reader - public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) { - - // set the caret position to the token - Document document = editor.getDocument(); - int clampedPosition = Math.min(Math.max(token.start, 0), document.getLength()); - - editor.setCaretPosition(clampedPosition); - editor.grabFocus(); - - try { - // make sure the token is visible in the scroll window - Rectangle start = editor.modelToView(token.start); - Rectangle end = editor.modelToView(token.end); - final Rectangle show = start.union(end); - show.grow(start.width * 10, start.height * 6); - SwingUtilities.invokeLater(() -> editor.scrollRectToVisible(show)); - } catch (BadLocationException ex) { - throw new Error(ex); - } - - // highlight the token momentarily - final Timer timer = new Timer(200, new ActionListener() { - private int counter = 0; - private Object highlight = null; - - @Override - public void actionPerformed(ActionEvent event) { - if (counter % 2 == 0) { - try { - highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter); - } catch (BadLocationException ex) { - // don't care - } - } else if (highlight != null) { - editor.getHighlighter().removeHighlight(highlight); - } - - if (counter++ > 6) { - Timer timer = (Timer) event.getSource(); - timer.stop(); - } - } - }); - timer.start(); - } -} diff --git a/src/main/java/cuchaz/enigma/gui/ConnectionState.java b/src/main/java/cuchaz/enigma/gui/ConnectionState.java deleted file mode 100644 index db6590de..00000000 --- a/src/main/java/cuchaz/enigma/gui/ConnectionState.java +++ /dev/null @@ -1,7 +0,0 @@ -package cuchaz.enigma.gui; - -public enum ConnectionState { - NOT_CONNECTED, - HOSTING, - CONNECTED, -} diff --git a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java b/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java deleted file mode 100644 index 08df3e75..00000000 --- a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java +++ /dev/null @@ -1,159 +0,0 @@ -package cuchaz.enigma.gui; - -import cuchaz.enigma.EnigmaProject; -import cuchaz.enigma.EnigmaServices; -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.analysis.Token; -import cuchaz.enigma.api.service.NameProposalService; -import cuchaz.enigma.gui.highlight.TokenHighlightType; -import cuchaz.enigma.source.SourceIndex; -import cuchaz.enigma.translation.LocalNameGenerator; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryRemapper; -import cuchaz.enigma.translation.mapping.ResolutionStrategy; -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.LocalVariableDefEntry; - -import javax.annotation.Nullable; -import java.util.*; - -public class DecompiledClassSource { - private final ClassEntry classEntry; - - private final SourceIndex obfuscatedIndex; - private SourceIndex remappedIndex; - - private final Map> highlightedTokens = new EnumMap<>(TokenHighlightType.class); - - public DecompiledClassSource(ClassEntry classEntry, SourceIndex index) { - this.classEntry = classEntry; - this.obfuscatedIndex = index; - this.remappedIndex = index; - } - - public static DecompiledClassSource text(ClassEntry classEntry, String text) { - return new DecompiledClassSource(classEntry, new SourceIndex(text)); - } - - public void remapSource(EnigmaProject project, Translator translator) { - highlightedTokens.clear(); - - SourceRemapper remapper = new SourceRemapper(obfuscatedIndex.getSource(), obfuscatedIndex.referenceTokens()); - - SourceRemapper.Result remapResult = remapper.remap((token, movedToken) -> remapToken(project, token, movedToken, translator)); - remappedIndex = obfuscatedIndex.remapTo(remapResult); - } - - private String remapToken(EnigmaProject project, Token token, Token movedToken, Translator translator) { - EntryReference, Entry> reference = obfuscatedIndex.getReference(token); - - Entry entry = reference.getNameableEntry(); - Entry translatedEntry = translator.translate(entry); - - if (project.isRenamable(reference)) { - if (isDeobfuscated(entry, translatedEntry)) { - highlightToken(movedToken, TokenHighlightType.DEOBFUSCATED); - return translatedEntry.getSourceRemapName(); - } else { - Optional proposedName = proposeName(project, entry); - if (proposedName.isPresent()) { - highlightToken(movedToken, TokenHighlightType.PROPOSED); - return proposedName.get(); - } - - highlightToken(movedToken, TokenHighlightType.OBFUSCATED); - } - } - - String defaultName = generateDefaultName(translatedEntry); - if (defaultName != null) { - return defaultName; - } - - return null; - } - - private Optional proposeName(EnigmaProject project, Entry entry) { - EnigmaServices services = project.getEnigma().getServices(); - - return services.get(NameProposalService.TYPE).stream().flatMap(nameProposalService -> { - EntryRemapper mapper = project.getMapper(); - Collection> resolved = mapper.getObfResolver().resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT); - - return resolved.stream() - .map(e -> nameProposalService.proposeName(e, mapper)) - .filter(Optional::isPresent) - .map(Optional::get); - }).findFirst(); - } - - @Nullable - private String generateDefaultName(Entry entry) { - if (entry instanceof LocalVariableDefEntry) { - LocalVariableDefEntry localVariable = (LocalVariableDefEntry) entry; - - int index = localVariable.getIndex(); - if (localVariable.isArgument()) { - List arguments = localVariable.getParent().getDesc().getArgumentDescs(); - return LocalNameGenerator.generateArgumentName(index, localVariable.getDesc(), arguments); - } else { - return LocalNameGenerator.generateLocalVariableName(index, localVariable.getDesc()); - } - } - - return null; - } - - private boolean isDeobfuscated(Entry entry, Entry translatedEntry) { - return !entry.getName().equals(translatedEntry.getName()); - } - - public ClassEntry getEntry() { - return classEntry; - } - - public SourceIndex getIndex() { - return remappedIndex; - } - - public Map> getHighlightedTokens() { - return highlightedTokens; - } - - private void highlightToken(Token token, TokenHighlightType highlightType) { - highlightedTokens.computeIfAbsent(highlightType, t -> new ArrayList<>()).add(token); - } - - public int getObfuscatedOffset(int deobfOffset) { - return getOffset(remappedIndex, obfuscatedIndex, deobfOffset); - } - - public int getDeobfuscatedOffset(int obfOffset) { - return getOffset(obfuscatedIndex, remappedIndex, obfOffset); - } - - private static int getOffset(SourceIndex fromIndex, SourceIndex toIndex, int fromOffset) { - int relativeOffset = 0; - - Iterator fromTokenItr = fromIndex.referenceTokens().iterator(); - Iterator toTokenItr = toIndex.referenceTokens().iterator(); - while (fromTokenItr.hasNext() && toTokenItr.hasNext()) { - Token fromToken = fromTokenItr.next(); - Token toToken = toTokenItr.next(); - if (fromToken.end > fromOffset) { - break; - } - - relativeOffset = toToken.end - fromToken.end; - } - - return fromOffset + relativeOffset; - } - - @Override - public String toString() { - return remappedIndex.getSource(); - } -} diff --git a/src/main/java/cuchaz/enigma/gui/EnigmaQuickFindDialog.java b/src/main/java/cuchaz/enigma/gui/EnigmaQuickFindDialog.java deleted file mode 100644 index c912be3a..00000000 --- a/src/main/java/cuchaz/enigma/gui/EnigmaQuickFindDialog.java +++ /dev/null @@ -1,90 +0,0 @@ -package cuchaz.enigma.gui; - -import de.sciss.syntaxpane.actions.DocumentSearchData; -import de.sciss.syntaxpane.actions.gui.QuickFindDialog; - -import javax.swing.*; -import javax.swing.text.JTextComponent; -import java.awt.*; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -public class EnigmaQuickFindDialog extends QuickFindDialog { - public EnigmaQuickFindDialog(JTextComponent target) { - super(target, DocumentSearchData.getFromEditor(target)); - - JToolBar toolBar = getToolBar(); - JTextField textField = getTextField(toolBar); - - textField.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - super.keyPressed(e); - if (e.getKeyCode() == KeyEvent.VK_ENTER) { - JToolBar toolBar = getToolBar(); - boolean next = !e.isShiftDown(); - JButton button = next ? getNextButton(toolBar) : getPrevButton(toolBar); - button.doClick(); - } - } - }); - } - - @Override - public void showFor(JTextComponent target) { - String selectedText = target.getSelectedText(); - - try { - super.showFor(target); - } catch (Exception e) { - e.printStackTrace(); - return; - } - - Container view = target.getParent(); - Point loc = new Point(0, view.getHeight() - getSize().height); - setLocationRelativeTo(view); - SwingUtilities.convertPointToScreen(loc, view); - setLocation(loc); - - JToolBar toolBar = getToolBar(); - JTextField textField = getTextField(toolBar); - - if (selectedText != null) { - textField.setText(selectedText); - } - - textField.selectAll(); - } - - private JToolBar getToolBar() { - return components(getContentPane(), JToolBar.class).findFirst().orElse(null); - } - - private JTextField getTextField(JToolBar toolBar) { - return components(toolBar, JTextField.class).findFirst().orElse(null); - } - - private JButton getNextButton(JToolBar toolBar) { - Stream buttons = components(toolBar, JButton.class); - return buttons.skip(1).findFirst().orElse(null); - } - - private JButton getPrevButton(JToolBar toolBar) { - Stream buttons = components(toolBar, JButton.class); - return buttons.findFirst().orElse(null); - } - - private static Stream components(Container container) { - return IntStream.range(0, container.getComponentCount()) - .mapToObj(container::getComponent); - } - - private static Stream components(Container container, Class type) { - return components(container) - .filter(type::isInstance) - .map(type::cast); - } -} diff --git a/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java b/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java deleted file mode 100644 index 42eaa605..00000000 --- a/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java +++ /dev/null @@ -1,44 +0,0 @@ -package cuchaz.enigma.gui; - -import cuchaz.enigma.config.Config; -import de.sciss.syntaxpane.components.LineNumbersRuler; -import de.sciss.syntaxpane.syntaxkits.JavaSyntaxKit; -import de.sciss.syntaxpane.util.Configuration; - -public class EnigmaSyntaxKit extends JavaSyntaxKit { - private static Configuration configuration = null; - - @Override - public Configuration getConfig() { - if(configuration == null){ - initConfig(super.getConfig(JavaSyntaxKit.class)); - } - return configuration; - } - - public void initConfig(Configuration baseConfig){ - configuration = baseConfig; - //See de.sciss.syntaxpane.TokenType - configuration.put("Style.KEYWORD", Config.getInstance().highlightColor + ", 0"); - configuration.put("Style.KEYWORD2", Config.getInstance().highlightColor + ", 3"); - configuration.put("Style.STRING", Config.getInstance().stringColor + ", 0"); - configuration.put("Style.STRING2", Config.getInstance().stringColor + ", 1"); - configuration.put("Style.NUMBER", Config.getInstance().numberColor + ", 1"); - configuration.put("Style.OPERATOR", Config.getInstance().operatorColor + ", 0"); - configuration.put("Style.DELIMITER", Config.getInstance().delimiterColor + ", 1"); - configuration.put("Style.TYPE", Config.getInstance().typeColor + ", 2"); - configuration.put("Style.TYPE2", Config.getInstance().typeColor + ", 1"); - configuration.put("Style.IDENTIFIER", Config.getInstance().identifierColor + ", 0"); - configuration.put("Style.DEFAULT", Config.getInstance().defaultTextColor + ", 0"); - configuration.put(LineNumbersRuler.PROPERTY_BACKGROUND, Config.getInstance().lineNumbersBackground + ""); - configuration.put(LineNumbersRuler.PROPERTY_FOREGROUND, Config.getInstance().lineNumbersForeground + ""); - configuration.put(LineNumbersRuler.PROPERTY_CURRENT_BACK, Config.getInstance().lineNumbersSelected + ""); - configuration.put("RightMarginColumn", "999"); //No need to have a right margin, if someone wants it add a config - - configuration.put("Action.quick-find", "cuchaz.enigma.gui.QuickFindAction, menu F"); - } - - public static void invalidate(){ - configuration = null; - } -} diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java deleted file mode 100644 index ed32469e..00000000 --- a/src/main/java/cuchaz/enigma/gui/Gui.java +++ /dev/null @@ -1,1058 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.gui; - -import java.awt.*; -import java.awt.event.*; -import java.nio.file.Path; -import java.util.List; -import java.util.*; -import java.util.function.Function; - -import javax.swing.*; -import javax.swing.text.BadLocationException; -import javax.swing.text.Highlighter; -import javax.swing.tree.*; - -import com.google.common.base.Strings; -import com.google.common.collect.Lists; -import cuchaz.enigma.Constants; -import cuchaz.enigma.EnigmaProfile; -import cuchaz.enigma.ExceptionIgnorer; -import cuchaz.enigma.analysis.*; -import cuchaz.enigma.config.Config; -import cuchaz.enigma.config.Themes; -import cuchaz.enigma.gui.dialog.CrashDialog; -import cuchaz.enigma.gui.dialog.JavadocDialog; -import cuchaz.enigma.gui.dialog.SearchDialog; -import cuchaz.enigma.gui.elements.CollapsibleTabbedPane; -import cuchaz.enigma.gui.elements.MenuBar; -import cuchaz.enigma.gui.elements.PopupMenuBar; -import cuchaz.enigma.gui.filechooser.FileChooserAny; -import cuchaz.enigma.gui.filechooser.FileChooserFolder; -import cuchaz.enigma.gui.highlight.BoxHighlightPainter; -import cuchaz.enigma.gui.highlight.SelectionHighlightPainter; -import cuchaz.enigma.gui.highlight.TokenHighlightType; -import cuchaz.enigma.gui.panels.PanelDeobf; -import cuchaz.enigma.gui.panels.PanelEditor; -import cuchaz.enigma.gui.panels.PanelIdentifier; -import cuchaz.enigma.gui.panels.PanelObf; -import cuchaz.enigma.gui.util.History; -import cuchaz.enigma.network.packet.*; -import cuchaz.enigma.throwables.IllegalNameException; -import cuchaz.enigma.translation.mapping.*; -import cuchaz.enigma.translation.representation.entry.*; -import cuchaz.enigma.utils.I18n; -import cuchaz.enigma.utils.Message; -import cuchaz.enigma.gui.util.ScaleUtil; -import cuchaz.enigma.utils.Utils; -import de.sciss.syntaxpane.DefaultSyntaxKit; - -public class Gui { - - public final PopupMenuBar popupMenu; - private final PanelObf obfPanel; - private final PanelDeobf deobfPanel; - - private final MenuBar menuBar; - // state - public History, Entry>> referenceHistory; - public EntryReference, Entry> renamingReference; - public EntryReference, Entry> cursorReference; - private boolean shouldNavigateOnClick; - private ConnectionState connectionState; - private boolean isJarOpen; - - public FileDialog jarFileChooser; - public FileDialog tinyMappingsFileChooser; - public SearchDialog searchDialog; - public JFileChooser enigmaMappingsFileChooser; - public JFileChooser exportSourceFileChooser; - public FileDialog exportJarFileChooser; - private GuiController controller; - private JFrame frame; - public Config.LookAndFeel editorFeel; - public PanelEditor editor; - public JScrollPane sourceScroller; - private JPanel classesPanel; - private JSplitPane splitClasses; - private PanelIdentifier infoPanel; - public Map boxHighlightPainters; - private SelectionHighlightPainter selectionHighlightPainter; - private JTree inheritanceTree; - private JTree implementationsTree; - private JTree callsTree; - private JList tokens; - private JTabbedPane tabs; - - private JSplitPane splitRight; - private JSplitPane logSplit; - private CollapsibleTabbedPane logTabs; - private JList users; - private DefaultListModel userModel; - private JScrollPane messageScrollPane; - private JList messages; - private DefaultListModel messageModel; - private JTextField chatBox; - - private JPanel statusBar; - private JLabel connectionStatusLabel; - private JLabel statusLabel; - - public JTextField renameTextField; - public JTextArea javadocTextArea; - - public void setEditorTheme(Config.LookAndFeel feel) { - if (editor != null && (editorFeel == null || editorFeel != feel)) { - editor.updateUI(); - editor.setBackground(new Color(Config.getInstance().editorBackground)); - if (editorFeel != null) { - getController().refreshCurrentClass(); - } - - editorFeel = feel; - } - } - - public Gui(EnigmaProfile profile) { - Config.getInstance().lookAndFeel.setGlobalLAF(); - - // init frame - this.frame = new JFrame(Constants.NAME); - final Container pane = this.frame.getContentPane(); - pane.setLayout(new BorderLayout()); - - if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) { - // install a global exception handler to the event thread - CrashDialog.init(this.frame); - Thread.setDefaultUncaughtExceptionHandler((thread, t) -> { - t.printStackTrace(System.err); - if (!ExceptionIgnorer.shouldIgnore(t)) { - CrashDialog.show(t); - } - }); - } - - this.controller = new GuiController(this, profile); - - Themes.updateTheme(this); - - // init file choosers - this.jarFileChooser = new FileDialog(getFrame(), I18n.translate("menu.file.jar.open"), FileDialog.LOAD); - - this.tinyMappingsFileChooser = new FileDialog(getFrame(), "Open tiny Mappings", FileDialog.LOAD); - this.enigmaMappingsFileChooser = new FileChooserAny(); - this.exportSourceFileChooser = new FileChooserFolder(); - this.exportJarFileChooser = new FileDialog(getFrame(), I18n.translate("menu.file.export.jar"), FileDialog.SAVE); - - this.obfPanel = new PanelObf(this); - this.deobfPanel = new PanelDeobf(this); - - // set up classes panel (don't add the splitter yet) - splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, this.obfPanel, this.deobfPanel); - splitClasses.setResizeWeight(0.3); - this.classesPanel = new JPanel(); - this.classesPanel.setLayout(new BorderLayout()); - this.classesPanel.setPreferredSize(ScaleUtil.getDimension(250, 0)); - - // init info panel - infoPanel = new PanelIdentifier(this); - infoPanel.clearReference(); - - // init editor - selectionHighlightPainter = new SelectionHighlightPainter(); - this.editor = new PanelEditor(this); - this.sourceScroller = new JScrollPane(this.editor); - this.editor.setContentType("text/enigma-sources"); - this.editor.setBackground(new Color(Config.getInstance().editorBackground)); - DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit(); - kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker"); - - // init editor popup menu - this.popupMenu = new PopupMenuBar(this); - this.editor.setComponentPopupMenu(this.popupMenu); - - // init inheritance panel - inheritanceTree = new JTree(); - inheritanceTree.setModel(null); - inheritanceTree.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent event) { - if (event.getClickCount() >= 2) { - // get the selected node - TreePath path = inheritanceTree.getSelectionPath(); - if (path == null) { - return; - } - - Object node = path.getLastPathComponent(); - if (node instanceof ClassInheritanceTreeNode) { - ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode) node; - controller.navigateTo(new ClassEntry(classNode.getObfClassName())); - } else if (node instanceof MethodInheritanceTreeNode) { - MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode) node; - if (methodNode.isImplemented()) { - controller.navigateTo(methodNode.getMethodEntry()); - } - } - } - } - }); - TreeCellRenderer cellRenderer = inheritanceTree.getCellRenderer(); - inheritanceTree.setCellRenderer(new MethodTreeCellRenderer(cellRenderer)); - - JPanel inheritancePanel = new JPanel(); - inheritancePanel.setLayout(new BorderLayout()); - inheritancePanel.add(new JScrollPane(inheritanceTree)); - - // init implementations panel - implementationsTree = new JTree(); - implementationsTree.setModel(null); - implementationsTree.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent event) { - if (event.getClickCount() >= 2) { - // get the selected node - TreePath path = implementationsTree.getSelectionPath(); - if (path == null) { - return; - } - - Object node = path.getLastPathComponent(); - if (node instanceof ClassImplementationsTreeNode) { - ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode) node; - controller.navigateTo(classNode.getClassEntry()); - } else if (node instanceof MethodImplementationsTreeNode) { - MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode) node; - controller.navigateTo(methodNode.getMethodEntry()); - } - } - } - }); - JPanel implementationsPanel = new JPanel(); - implementationsPanel.setLayout(new BorderLayout()); - implementationsPanel.add(new JScrollPane(implementationsTree)); - - // init call panel - callsTree = new JTree(); - callsTree.setModel(null); - callsTree.addMouseListener(new MouseAdapter() { - @SuppressWarnings("unchecked") - @Override - public void mouseClicked(MouseEvent event) { - if (event.getClickCount() >= 2) { - // get the selected node - TreePath path = callsTree.getSelectionPath(); - if (path == null) { - return; - } - - Object node = path.getLastPathComponent(); - if (node instanceof ReferenceTreeNode) { - ReferenceTreeNode, Entry> referenceNode = ((ReferenceTreeNode, Entry>) node); - if (referenceNode.getReference() != null) { - controller.navigateTo(referenceNode.getReference()); - } else { - controller.navigateTo(referenceNode.getEntry()); - } - } - } - } - }); - tokens = new JList<>(); - tokens.setCellRenderer(new TokenListCellRenderer(this.controller)); - tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - tokens.setLayoutOrientation(JList.VERTICAL); - tokens.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent event) { - if (event.getClickCount() == 2) { - Token selected = tokens.getSelectedValue(); - if (selected != null) { - showToken(selected); - } - } - } - }); - tokens.setPreferredSize(ScaleUtil.getDimension(0, 200)); - tokens.setMinimumSize(ScaleUtil.getDimension(0, 200)); - JSplitPane callPanel = new JSplitPane( - JSplitPane.VERTICAL_SPLIT, - true, - new JScrollPane(callsTree), - new JScrollPane(tokens) - ); - callPanel.setResizeWeight(1); // let the top side take all the slack - callPanel.resetToPreferredSizes(); - - // layout controls - JPanel centerPanel = new JPanel(); - centerPanel.setLayout(new BorderLayout()); - centerPanel.add(infoPanel, BorderLayout.NORTH); - centerPanel.add(sourceScroller, BorderLayout.CENTER); - tabs = new JTabbedPane(); - tabs.setPreferredSize(ScaleUtil.getDimension(250, 0)); - tabs.addTab(I18n.translate("info_panel.tree.inheritance"), inheritancePanel); - tabs.addTab(I18n.translate("info_panel.tree.implementations"), implementationsPanel); - tabs.addTab(I18n.translate("info_panel.tree.calls"), callPanel); - logTabs = new CollapsibleTabbedPane(JTabbedPane.BOTTOM); - userModel = new DefaultListModel<>(); - users = new JList<>(userModel); - messageModel = new DefaultListModel<>(); - messages = new JList<>(messageModel); - messages.setCellRenderer(new MessageListCellRenderer()); - JPanel messagePanel = new JPanel(new BorderLayout()); - messageScrollPane = new JScrollPane(this.messages); - messagePanel.add(messageScrollPane, BorderLayout.CENTER); - JPanel chatPanel = new JPanel(new BorderLayout()); - chatBox = new JTextField(); - AbstractAction sendListener = new AbstractAction("Send") { - @Override - public void actionPerformed(ActionEvent e) { - sendMessage(); - } - }; - chatBox.addActionListener(sendListener); - JButton chatSendButton = new JButton(sendListener); - chatPanel.add(chatBox, BorderLayout.CENTER); - chatPanel.add(chatSendButton, BorderLayout.EAST); - messagePanel.add(chatPanel, BorderLayout.SOUTH); - logTabs.addTab(I18n.translate("log_panel.users"), new JScrollPane(this.users)); - logTabs.addTab(I18n.translate("log_panel.messages"), messagePanel); - logSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, tabs, logTabs); - logSplit.setResizeWeight(0.5); - logSplit.resetToPreferredSizes(); - splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, this.logSplit); - splitRight.setResizeWeight(1); // let the left side take all the slack - splitRight.resetToPreferredSizes(); - JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight); - splitCenter.setResizeWeight(0); // let the right side take all the slack - pane.add(splitCenter, BorderLayout.CENTER); - - // init menus - this.menuBar = new MenuBar(this); - this.frame.setJMenuBar(this.menuBar); - - // init status bar - statusBar = new JPanel(new BorderLayout()); - statusBar.setBorder(BorderFactory.createLoweredBevelBorder()); - connectionStatusLabel = new JLabel(); - statusLabel = new JLabel(); - statusBar.add(statusLabel, BorderLayout.CENTER); - statusBar.add(connectionStatusLabel, BorderLayout.EAST); - pane.add(statusBar, BorderLayout.SOUTH); - - // init state - setConnectionState(ConnectionState.NOT_CONNECTED); - onCloseJar(); - - this.frame.addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent event) { - close(); - } - }); - - // show the frame - pane.doLayout(); - this.frame.setSize(ScaleUtil.getDimension(1024, 576)); - this.frame.setMinimumSize(ScaleUtil.getDimension(640, 480)); - this.frame.setVisible(true); - this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - this.frame.setLocationRelativeTo(null); - } - - public JFrame getFrame() { - return this.frame; - } - - public GuiController getController() { - return this.controller; - } - - public void onStartOpenJar() { - this.classesPanel.removeAll(); - redraw(); - } - - public void onFinishOpenJar(String jarName) { - // update gui - this.frame.setTitle(Constants.NAME + " - " + jarName); - this.classesPanel.removeAll(); - this.classesPanel.add(splitClasses); - setEditorText(null); - - // update menu - isJarOpen = true; - - updateUiState(); - redraw(); - } - - public void onCloseJar() { - - // update gui - this.frame.setTitle(Constants.NAME); - setObfClasses(null); - setDeobfClasses(null); - setEditorText(null); - this.classesPanel.removeAll(); - - // update menu - isJarOpen = false; - setMappingsFile(null); - - updateUiState(); - redraw(); - } - - public void setObfClasses(Collection obfClasses) { - this.obfPanel.obfClasses.setClasses(obfClasses); - } - - public void setDeobfClasses(Collection deobfClasses) { - this.deobfPanel.deobfClasses.setClasses(deobfClasses); - } - - public void setMappingsFile(Path path) { - this.enigmaMappingsFileChooser.setSelectedFile(path != null ? path.toFile() : null); - updateUiState(); - } - - public void setEditorText(String source) { - this.editor.getHighlighter().removeAllHighlights(); - this.editor.setText(source); - } - - public void setSource(DecompiledClassSource source) { - editor.setText(source.toString()); - setHighlightedTokens(source.getHighlightedTokens()); - } - - public void showToken(final Token token) { - if (token == null) { - throw new IllegalArgumentException("Token cannot be null!"); - } - CodeReader.navigateToToken(this.editor, token, selectionHighlightPainter); - redraw(); - } - - public void showTokens(Collection tokens) { - Vector sortedTokens = new Vector<>(tokens); - Collections.sort(sortedTokens); - if (sortedTokens.size() > 1) { - // sort the tokens and update the tokens panel - this.tokens.setListData(sortedTokens); - this.tokens.setSelectedIndex(0); - } else { - this.tokens.setListData(new Vector<>()); - } - - // show the first token - showToken(sortedTokens.get(0)); - } - - public void setHighlightedTokens(Map> tokens) { - // remove any old highlighters - this.editor.getHighlighter().removeAllHighlights(); - - if (boxHighlightPainters != null) { - for (TokenHighlightType type : tokens.keySet()) { - BoxHighlightPainter painter = boxHighlightPainters.get(type); - if (painter != null) { - setHighlightedTokens(tokens.get(type), painter); - } - } - } - - redraw(); - } - - private void setHighlightedTokens(Iterable tokens, Highlighter.HighlightPainter painter) { - for (Token token : tokens) { - try { - this.editor.getHighlighter().addHighlight(token.start, token.end, painter); - } catch (BadLocationException ex) { - throw new IllegalArgumentException(ex); - } - } - } - - private void showCursorReference(EntryReference, Entry> reference) { - if (reference == null) { - infoPanel.clearReference(); - return; - } - - this.cursorReference = reference; - - EntryReference, Entry> translatedReference = controller.project.getMapper().deobfuscate(reference); - - infoPanel.removeAll(); - if (translatedReference.entry instanceof ClassEntry) { - showClassEntry((ClassEntry) translatedReference.entry); - } else if (translatedReference.entry instanceof FieldEntry) { - showFieldEntry((FieldEntry) translatedReference.entry); - } else if (translatedReference.entry instanceof MethodEntry) { - showMethodEntry((MethodEntry) translatedReference.entry); - } else if (translatedReference.entry instanceof LocalVariableEntry) { - showLocalVariableEntry((LocalVariableEntry) translatedReference.entry); - } else { - throw new Error("Unknown entry desc: " + translatedReference.entry.getClass().getName()); - } - - redraw(); - } - - private void showLocalVariableEntry(LocalVariableEntry entry) { - addNameValue(infoPanel, I18n.translate("info_panel.identifier.variable"), entry.getName()); - addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getContainingClass().getFullName()); - addNameValue(infoPanel, I18n.translate("info_panel.identifier.method"), entry.getParent().getName()); - addNameValue(infoPanel, I18n.translate("info_panel.identifier.index"), Integer.toString(entry.getIndex())); - } - - private void showClassEntry(ClassEntry entry) { - addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getFullName()); - addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry); - } - - private void showFieldEntry(FieldEntry entry) { - addNameValue(infoPanel, I18n.translate("info_panel.identifier.field"), entry.getName()); - addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getParent().getFullName()); - addNameValue(infoPanel, I18n.translate("info_panel.identifier.type_descriptor"), entry.getDesc().toString()); - addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry); - } - - private void showMethodEntry(MethodEntry entry) { - if (entry.isConstructor()) { - addNameValue(infoPanel, I18n.translate("info_panel.identifier.constructor"), entry.getParent().getFullName()); - } else { - addNameValue(infoPanel, I18n.translate("info_panel.identifier.method"), entry.getName()); - addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getParent().getFullName()); - } - addNameValue(infoPanel, I18n.translate("info_panel.identifier.method_descriptor"), entry.getDesc().toString()); - addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry); - } - - private void addNameValue(JPanel container, String name, String value) { - JPanel panel = new JPanel(); - panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); - - JLabel label = new JLabel(name + ":", JLabel.RIGHT); - label.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height))); - panel.add(label); - - panel.add(Utils.unboldLabel(new JLabel(value, JLabel.LEFT))); - - container.add(panel); - } - - private JComboBox addModifierComboBox(JPanel container, String name, Entry entry) { - if (!getController().project.isRenamable(entry)) - return null; - JPanel panel = new JPanel(); - panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); - JLabel label = new JLabel(name + ":", JLabel.RIGHT); - label.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height))); - panel.add(label); - JComboBox combo = new JComboBox<>(AccessModifier.values()); - ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT); - combo.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height))); - - EntryMapping mapping = controller.project.getMapper().getDeobfMapping(entry); - if (mapping != null) { - combo.setSelectedIndex(mapping.getAccessModifier().ordinal()); - } else { - combo.setSelectedIndex(AccessModifier.UNCHANGED.ordinal()); - } - - combo.addItemListener(controller::modifierChange); - - panel.add(combo); - - container.add(panel); - - return combo; - } - - public void onCaretMove(int pos, boolean fromClick) { - if (controller.project == null) - return; - EntryRemapper mapper = controller.project.getMapper(); - Token token = this.controller.getToken(pos); - boolean isToken = token != null; - - cursorReference = this.controller.getReference(token); - Entry referenceEntry = cursorReference != null ? cursorReference.entry : null; - - if (referenceEntry != null && shouldNavigateOnClick && fromClick) { - shouldNavigateOnClick = false; - Entry navigationEntry = referenceEntry; - if (cursorReference.context == null) { - EntryResolver resolver = mapper.getObfResolver(); - navigationEntry = resolver.resolveFirstEntry(referenceEntry, ResolutionStrategy.RESOLVE_ROOT); - } - controller.navigateTo(navigationEntry); - return; - } - - boolean isClassEntry = isToken && referenceEntry instanceof ClassEntry; - boolean isFieldEntry = isToken && referenceEntry instanceof FieldEntry; - boolean isMethodEntry = isToken && referenceEntry instanceof MethodEntry && !((MethodEntry) referenceEntry).isConstructor(); - boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor(); - boolean isRenamable = isToken && this.controller.project.isRenamable(cursorReference); - - if (!isRenaming()) { - if (isToken) { - showCursorReference(cursorReference); - } else { - infoPanel.clearReference(); - } - } - - this.popupMenu.renameMenu.setEnabled(isRenamable); - this.popupMenu.editJavadocMenu.setEnabled(isRenamable); - this.popupMenu.showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry); - this.popupMenu.showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry); - this.popupMenu.showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry); - this.popupMenu.showCallsSpecificMenu.setEnabled(isMethodEntry); - this.popupMenu.openEntryMenu.setEnabled(isRenamable && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry)); - this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousReference()); - this.popupMenu.openNextMenu.setEnabled(this.controller.hasNextReference()); - this.popupMenu.toggleMappingMenu.setEnabled(isRenamable); - - if (isToken && !Objects.equals(referenceEntry, mapper.deobfuscate(referenceEntry))) { - this.popupMenu.toggleMappingMenu.setText(I18n.translate("popup_menu.reset_obfuscated")); - } else { - this.popupMenu.toggleMappingMenu.setText(I18n.translate("popup_menu.mark_deobfuscated")); - } - } - - public void startDocChange() { - EntryReference, Entry> curReference = cursorReference; - if (isRenaming()) { - finishRename(false); - } - renamingReference = curReference; - - // init the text box - javadocTextArea = new JTextArea(10, 40); - - EntryReference, Entry> translatedReference = controller.project.getMapper().deobfuscate(cursorReference); - javadocTextArea.setText(Strings.nullToEmpty(translatedReference.entry.getJavadocs())); - - JavadocDialog.init(frame, javadocTextArea, this::finishDocChange); - javadocTextArea.grabFocus(); - - redraw(); - } - - private void finishDocChange(JFrame ui, boolean saveName) { - String newName = javadocTextArea.getText(); - if (saveName) { - try { - this.controller.changeDocs(renamingReference, newName); - this.controller.sendPacket(new ChangeDocsC2SPacket(renamingReference.getNameableEntry(), newName)); - } catch (IllegalNameException ex) { - javadocTextArea.setBorder(BorderFactory.createLineBorder(Color.red, 1)); - javadocTextArea.setToolTipText(ex.getReason()); - Utils.showToolTipNow(javadocTextArea); - return; - } - - ui.setVisible(false); - showCursorReference(cursorReference); - return; - } - - // abort the jd change - javadocTextArea = null; - ui.setVisible(false); - showCursorReference(cursorReference); - - this.editor.grabFocus(); - - redraw(); - } - - public void startRename() { - - // init the text box - renameTextField = new JTextField(); - - EntryReference, Entry> translatedReference = controller.project.getMapper().deobfuscate(cursorReference); - renameTextField.setText(translatedReference.getNameableName()); - - renameTextField.setPreferredSize(ScaleUtil.getDimension(360, ScaleUtil.invert(renameTextField.getPreferredSize().height))); - renameTextField.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent event) { - switch (event.getKeyCode()) { - case KeyEvent.VK_ENTER: - finishRename(true); - break; - - case KeyEvent.VK_ESCAPE: - finishRename(false); - break; - default: - break; - } - } - }); - - // find the label with the name and replace it with the text box - JPanel panel = (JPanel) infoPanel.getComponent(0); - panel.remove(panel.getComponentCount() - 1); - panel.add(renameTextField); - renameTextField.grabFocus(); - - int offset = renameTextField.getText().lastIndexOf('/') + 1; - // If it's a class and isn't in the default package, assume that it's deobfuscated. - if (translatedReference.getNameableEntry() instanceof ClassEntry && renameTextField.getText().contains("/") && offset != 0) - renameTextField.select(offset, renameTextField.getText().length()); - else - renameTextField.selectAll(); - - renamingReference = cursorReference; - - redraw(); - } - - private void finishRename(boolean saveName) { - String newName = renameTextField.getText(); - - if (saveName && newName != null && !newName.isEmpty()) { - try { - this.controller.rename(renamingReference, newName, true); - this.controller.sendPacket(new RenameC2SPacket(renamingReference.getNameableEntry(), newName, true)); - renameTextField = null; - } catch (IllegalNameException ex) { - renameTextField.setBorder(BorderFactory.createLineBorder(Color.red, 1)); - renameTextField.setToolTipText(ex.getReason()); - Utils.showToolTipNow(renameTextField); - } - return; - } - - renameTextField = null; - - // abort the rename - showCursorReference(cursorReference); - - this.editor.grabFocus(); - - redraw(); - } - - private boolean isRenaming() { - return renameTextField != null; - } - - public void showInheritance() { - - if (cursorReference == null) { - return; - } - - inheritanceTree.setModel(null); - - if (cursorReference.entry instanceof ClassEntry) { - // get the class inheritance - ClassInheritanceTreeNode classNode = this.controller.getClassInheritance((ClassEntry) cursorReference.entry); - - // show the tree at the root - TreePath path = getPathToRoot(classNode); - inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); - inheritanceTree.expandPath(path); - inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); - } else if (cursorReference.entry instanceof MethodEntry) { - // get the method inheritance - MethodInheritanceTreeNode classNode = this.controller.getMethodInheritance((MethodEntry) cursorReference.entry); - - // show the tree at the root - TreePath path = getPathToRoot(classNode); - inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); - inheritanceTree.expandPath(path); - inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); - } - - tabs.setSelectedIndex(0); - - redraw(); - } - - public void showImplementations() { - - if (cursorReference == null) { - return; - } - - implementationsTree.setModel(null); - - DefaultMutableTreeNode node = null; - - // get the class implementations - if (cursorReference.entry instanceof ClassEntry) - node = this.controller.getClassImplementations((ClassEntry) cursorReference.entry); - else // get the method implementations - if (cursorReference.entry instanceof MethodEntry) - node = this.controller.getMethodImplementations((MethodEntry) cursorReference.entry); - - if (node != null) { - // show the tree at the root - TreePath path = getPathToRoot(node); - implementationsTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0))); - implementationsTree.expandPath(path); - implementationsTree.setSelectionRow(implementationsTree.getRowForPath(path)); - } - - tabs.setSelectedIndex(1); - - redraw(); - } - - public void showCalls(boolean recurse) { - if (cursorReference == null) { - return; - } - - if (cursorReference.entry instanceof ClassEntry) { - ClassReferenceTreeNode node = this.controller.getClassReferences((ClassEntry) cursorReference.entry); - callsTree.setModel(new DefaultTreeModel(node)); - } else if (cursorReference.entry instanceof FieldEntry) { - FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) cursorReference.entry); - callsTree.setModel(new DefaultTreeModel(node)); - } else if (cursorReference.entry instanceof MethodEntry) { - MethodReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) cursorReference.entry, recurse); - callsTree.setModel(new DefaultTreeModel(node)); - } - - tabs.setSelectedIndex(2); - - redraw(); - } - - public void toggleMapping() { - Entry obfEntry = cursorReference.entry; - Entry deobfEntry = controller.project.getMapper().deobfuscate(obfEntry); - - if (!Objects.equals(obfEntry, deobfEntry)) { - this.controller.removeMapping(cursorReference); - this.controller.sendPacket(new RemoveMappingC2SPacket(cursorReference.getNameableEntry())); - } else { - this.controller.markAsDeobfuscated(cursorReference); - this.controller.sendPacket(new MarkDeobfuscatedC2SPacket(cursorReference.getNameableEntry())); - } - } - - private TreePath getPathToRoot(TreeNode node) { - List nodes = Lists.newArrayList(); - TreeNode n = node; - do { - nodes.add(n); - n = n.getParent(); - } while (n != null); - Collections.reverse(nodes); - return new TreePath(nodes.toArray()); - } - - public void showDiscardDiag(Function callback, String... options) { - int response = JOptionPane.showOptionDialog(this.frame, I18n.translate("prompt.close.summary"), I18n.translate("prompt.close.title"), JOptionPane.YES_NO_CANCEL_OPTION, - JOptionPane.QUESTION_MESSAGE, null, options, options[2]); - callback.apply(response); - } - - public void saveMapping() { - if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION) - this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath()); - } - - public void close() { - if (!this.controller.isDirty()) { - // everything is saved, we can exit safely - exit(); - } else { - // ask to save before closing - showDiscardDiag((response) -> { - if (response == JOptionPane.YES_OPTION) { - this.saveMapping(); - exit(); - } else if (response == JOptionPane.NO_OPTION) { - exit(); - } - - return null; - }, I18n.translate("prompt.close.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.close.cancel")); - } - } - - private void exit() { - if (searchDialog != null) { - searchDialog.dispose(); - } - this.frame.dispose(); - System.exit(0); - } - - public void redraw() { - this.frame.validate(); - this.frame.repaint(); - } - - public void onPanelRename(Object prevData, Object data, DefaultMutableTreeNode node) throws IllegalNameException { - // package rename - if (data instanceof String) { - for (int i = 0; i < node.getChildCount(); i++) { - DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i); - ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); - ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); - this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getFullName()), dataChild.getFullName(), false); - this.controller.sendPacket(new RenameC2SPacket(prevDataChild, dataChild.getFullName(), false)); - childNode.setUserObject(dataChild); - } - node.setUserObject(data); - // Ob package will never be modified, just reload deob view - this.deobfPanel.deobfClasses.reload(); - } - // class rename - else if (data instanceof ClassEntry) { - this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getFullName()), ((ClassEntry) data).getFullName(), false); - this.controller.sendPacket(new RenameC2SPacket((ClassEntry) prevData, ((ClassEntry) data).getFullName(), false)); - } - } - - public void moveClassTree(EntryReference, Entry> obfReference, String newName) { - String oldEntry = obfReference.entry.getContainingClass().getPackageName(); - String newEntry = new ClassEntry(newName).getPackageName(); - moveClassTree(obfReference, oldEntry == null, newEntry == null); - } - - // TODO: getExpansionState will *not* actually update itself based on name changes! - public void moveClassTree(EntryReference, Entry> obfReference, boolean isOldOb, boolean isNewOb) { - ClassEntry classEntry = obfReference.entry.getContainingClass(); - - List stateDeobf = this.deobfPanel.deobfClasses.getExpansionState(this.deobfPanel.deobfClasses); - List stateObf = this.obfPanel.obfClasses.getExpansionState(this.obfPanel.obfClasses); - - // Ob -> deob - if (!isNewOb) { - this.deobfPanel.deobfClasses.moveClassIn(classEntry); - this.obfPanel.obfClasses.moveClassOut(classEntry); - this.deobfPanel.deobfClasses.reload(); - this.obfPanel.obfClasses.reload(); - } - // Deob -> ob - else if (!isOldOb) { - this.obfPanel.obfClasses.moveClassIn(classEntry); - this.deobfPanel.deobfClasses.moveClassOut(classEntry); - this.deobfPanel.deobfClasses.reload(); - this.obfPanel.obfClasses.reload(); - } - // Local move - else if (isOldOb) { - this.obfPanel.obfClasses.moveClassIn(classEntry); - this.obfPanel.obfClasses.reload(); - } else { - this.deobfPanel.deobfClasses.moveClassIn(classEntry); - this.deobfPanel.deobfClasses.reload(); - } - - this.deobfPanel.deobfClasses.restoreExpansionState(this.deobfPanel.deobfClasses, stateDeobf); - this.obfPanel.obfClasses.restoreExpansionState(this.obfPanel.obfClasses, stateObf); - } - - public PanelObf getObfPanel() { - return obfPanel; - } - - public PanelDeobf getDeobfPanel() { - return deobfPanel; - } - - public void setShouldNavigateOnClick(boolean shouldNavigateOnClick) { - this.shouldNavigateOnClick = shouldNavigateOnClick; - } - - public SearchDialog getSearchDialog() { - if (searchDialog == null) { - searchDialog = new SearchDialog(this); - } - return searchDialog; - } - - - public MenuBar getMenuBar() { - return menuBar; - } - - public void addMessage(Message message) { - JScrollBar verticalScrollBar = messageScrollPane.getVerticalScrollBar(); - boolean isAtBottom = verticalScrollBar.getValue() >= verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent(); - messageModel.addElement(message); - if (isAtBottom) { - SwingUtilities.invokeLater(() -> verticalScrollBar.setValue(verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent())); - } - statusLabel.setText(message.translate()); - } - - public void setUserList(List users) { - userModel.clear(); - users.forEach(userModel::addElement); - connectionStatusLabel.setText(String.format(I18n.translate("status.connected_user_count"), users.size())); - } - - private void sendMessage() { - String text = chatBox.getText().trim(); - if (!text.isEmpty()) { - getController().sendPacket(new MessageC2SPacket(text)); - } - chatBox.setText(""); - } - - /** - * Updates the state of the UI elements (button text, enabled state, ...) to reflect the current program state. - * This is a central place to update the UI state to prevent multiple code paths from changing the same state, - * causing inconsistencies. - */ - public void updateUiState() { - menuBar.connectToServerMenu.setEnabled(isJarOpen && connectionState != ConnectionState.HOSTING); - menuBar.connectToServerMenu.setText(I18n.translate(connectionState != ConnectionState.CONNECTED ? "menu.collab.connect" : "menu.collab.disconnect")); - menuBar.startServerMenu.setEnabled(isJarOpen && connectionState != ConnectionState.CONNECTED); - menuBar.startServerMenu.setText(I18n.translate(connectionState != ConnectionState.HOSTING ? "menu.collab.server.start" : "menu.collab.server.stop")); - - menuBar.closeJarMenu.setEnabled(isJarOpen); - menuBar.openMappingsMenus.forEach(item -> item.setEnabled(isJarOpen)); - menuBar.saveMappingsMenu.setEnabled(isJarOpen && enigmaMappingsFileChooser.getSelectedFile() != null && connectionState != ConnectionState.CONNECTED); - menuBar.saveMappingsMenus.forEach(item -> item.setEnabled(isJarOpen)); - menuBar.closeMappingsMenu.setEnabled(isJarOpen); - menuBar.exportSourceMenu.setEnabled(isJarOpen); - menuBar.exportJarMenu.setEnabled(isJarOpen); - - connectionStatusLabel.setText(I18n.translate(connectionState == ConnectionState.NOT_CONNECTED ? "status.disconnected" : "status.connected")); - - if (connectionState == ConnectionState.NOT_CONNECTED) { - logSplit.setLeftComponent(null); - splitRight.setRightComponent(tabs); - } else { - splitRight.setRightComponent(logSplit); - logSplit.setLeftComponent(tabs); - } - } - - public void setConnectionState(ConnectionState state) { - connectionState = state; - statusLabel.setText(I18n.translate("status.ready")); - updateUiState(); - } - -} diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java deleted file mode 100644 index cccc9e8a..00000000 --- a/src/main/java/cuchaz/enigma/gui/GuiController.java +++ /dev/null @@ -1,729 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.gui; - -import com.google.common.collect.Lists; -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import cuchaz.enigma.Enigma; -import cuchaz.enigma.EnigmaProfile; -import cuchaz.enigma.EnigmaProject; -import cuchaz.enigma.analysis.*; -import cuchaz.enigma.api.service.ObfuscationTestService; -import cuchaz.enigma.bytecode.translators.SourceFixVisitor; -import cuchaz.enigma.config.Config; -import cuchaz.enigma.gui.dialog.ProgressDialog; -import cuchaz.enigma.gui.stats.StatsGenerator; -import cuchaz.enigma.gui.stats.StatsMember; -import cuchaz.enigma.gui.util.History; -import cuchaz.enigma.network.EnigmaClient; -import cuchaz.enigma.network.EnigmaServer; -import cuchaz.enigma.network.IntegratedEnigmaServer; -import cuchaz.enigma.network.ServerPacketHandler; -import cuchaz.enigma.network.packet.LoginC2SPacket; -import cuchaz.enigma.network.packet.Packet; -import cuchaz.enigma.source.*; -import cuchaz.enigma.throwables.MappingParseException; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.*; -import cuchaz.enigma.translation.mapping.serde.MappingFormat; -import cuchaz.enigma.translation.mapping.tree.EntryTree; -import cuchaz.enigma.translation.mapping.tree.HashEntryTree; -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.MethodEntry; -import cuchaz.enigma.utils.I18n; -import cuchaz.enigma.utils.Message; -import cuchaz.enigma.utils.ReadableToken; -import cuchaz.enigma.utils.Utils; -import org.objectweb.asm.tree.ClassNode; - -import javax.annotation.Nullable; -import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; -import java.awt.*; -import java.awt.event.ItemEvent; -import java.io.*; -import java.nio.file.Path; -import java.util.Collection; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class GuiController { - private static final ExecutorService DECOMPILER_SERVICE = Executors.newSingleThreadExecutor( - new ThreadFactoryBuilder() - .setDaemon(true) - .setNameFormat("decompiler-thread") - .build() - ); - - private final Gui gui; - public final Enigma enigma; - - public EnigmaProject project; - private DecompilerService decompilerService; - private Decompiler decompiler; - private IndexTreeBuilder indexTreeBuilder; - - private Path loadedMappingPath; - private MappingFormat loadedMappingFormat; - - private DecompiledClassSource currentSource; - private Source uncommentedSource; - - private EnigmaClient client; - private EnigmaServer server; - - public GuiController(Gui gui, EnigmaProfile profile) { - this.gui = gui; - this.enigma = Enigma.builder() - .setProfile(profile) - .build(); - - decompilerService = Config.getInstance().decompiler.service; - } - - public boolean isDirty() { - return project != null && project.getMapper().isDirty(); - } - - public CompletableFuture openJar(final Path jarPath) { - this.gui.onStartOpenJar(); - - return ProgressDialog.runOffThread(gui.getFrame(), progress -> { - project = enigma.openJar(jarPath, progress); - indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex()); - decompiler = createDecompiler(); - gui.onFinishOpenJar(jarPath.getFileName().toString()); - refreshClasses(); - }); - } - - private Decompiler createDecompiler() { - return decompilerService.create(name -> { - ClassNode node = project.getClassCache().getClassNode(name); - - if (node == null) { - return null; - } - - ClassNode fixedNode = new ClassNode(); - node.accept(new SourceFixVisitor(Utils.ASM_VERSION, fixedNode, project.getJarIndex())); - return fixedNode; - }, new SourceSettings(true, true)); - } - - public void closeJar() { - this.project = null; - this.gui.onCloseJar(); - } - - public CompletableFuture openMappings(MappingFormat format, Path path) { - if (project == null) return CompletableFuture.completedFuture(null); - - gui.setMappingsFile(path); - - return ProgressDialog.runOffThread(gui.getFrame(), progress -> { - try { - MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters(); - - EntryTree mappings = format.read(path, progress, saveParameters); - project.setMappings(mappings); - - loadedMappingFormat = format; - loadedMappingPath = path; - - refreshClasses(); - refreshCurrentClass(); - } catch (MappingParseException e) { - JOptionPane.showMessageDialog(gui.getFrame(), e.getMessage()); - } - }); - } - - public void openMappings(EntryTree mappings) { - if (project == null) return; - - project.setMappings(mappings); - refreshClasses(); - refreshCurrentClass(); - } - - public CompletableFuture saveMappings(Path path) { - return saveMappings(path, loadedMappingFormat); - } - - public CompletableFuture saveMappings(Path path, MappingFormat format) { - if (project == null) return CompletableFuture.completedFuture(null); - - return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { - EntryRemapper mapper = project.getMapper(); - MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters(); - - MappingDelta delta = mapper.takeMappingDelta(); - boolean saveAll = !path.equals(loadedMappingPath); - - loadedMappingFormat = format; - loadedMappingPath = path; - - if (saveAll) { - format.write(mapper.getObfToDeobf(), path, progress, saveParameters); - } else { - format.write(mapper.getObfToDeobf(), delta, path, progress, saveParameters); - } - }); - } - - public void closeMappings() { - if (project == null) return; - - project.setMappings(null); - - this.gui.setMappingsFile(null); - refreshClasses(); - refreshCurrentClass(); - } - - public CompletableFuture dropMappings() { - if (project == null) return CompletableFuture.completedFuture(null); - - return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> project.dropMappings(progress)); - } - - public CompletableFuture exportSource(final Path path) { - if (project == null) return CompletableFuture.completedFuture(null); - - return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { - EnigmaProject.JarExport jar = project.exportRemappedJar(progress); - EnigmaProject.SourceExport source = jar.decompile(progress, decompilerService); - - source.write(path, progress); - }); - } - - public CompletableFuture exportJar(final Path path) { - if (project == null) return CompletableFuture.completedFuture(null); - - return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { - EnigmaProject.JarExport jar = project.exportRemappedJar(progress); - jar.write(path, progress); - }); - } - - public Token getToken(int pos) { - if (this.currentSource == null) { - return null; - } - return this.currentSource.getIndex().getReferenceToken(pos); - } - - @Nullable - public EntryReference, Entry> getReference(Token token) { - if (this.currentSource == null) { - return null; - } - return this.currentSource.getIndex().getReference(token); - } - - public ReadableToken getReadableToken(Token token) { - if (this.currentSource == null) { - return null; - } - - SourceIndex index = this.currentSource.getIndex(); - return new ReadableToken( - index.getLineNumber(token.start), - index.getColumnNumber(token.start), - index.getColumnNumber(token.end) - ); - } - - /** - * Navigates to the declaration with respect to navigation history - * - * @param entry the entry whose declaration will be navigated to - */ - public void openDeclaration(Entry entry) { - if (entry == null) { - throw new IllegalArgumentException("Entry cannot be null!"); - } - openReference(new EntryReference<>(entry, entry.getName())); - } - - /** - * Navigates to the reference with respect to navigation history - * - * @param reference the reference - */ - public void openReference(EntryReference, Entry> reference) { - if (reference == null) { - throw new IllegalArgumentException("Reference cannot be null!"); - } - if (this.gui.referenceHistory == null) { - this.gui.referenceHistory = new History<>(reference); - } else { - if (!reference.equals(this.gui.referenceHistory.getCurrent())) { - this.gui.referenceHistory.push(reference); - } - } - setReference(reference); - } - - /** - * Navigates to the reference without modifying history. If the class is not currently loaded, it will be loaded. - * - * @param reference the reference - */ - private void setReference(EntryReference, Entry> reference) { - // get the reference target class - ClassEntry classEntry = reference.getLocationClassEntry(); - if (!project.isRenamable(classEntry)) { - throw new IllegalArgumentException("Obfuscated class " + classEntry + " was not found in the jar!"); - } - - if (this.currentSource == null || !this.currentSource.getEntry().equals(classEntry)) { - // deobfuscate the class, then navigate to the reference - loadClass(classEntry, () -> showReference(reference)); - } else { - showReference(reference); - } - } - - /** - * Navigates to the reference without modifying history. Assumes the class is loaded. - * - * @param reference - */ - private void showReference(EntryReference, Entry> reference) { - Collection tokens = getTokensForReference(reference); - if (tokens.isEmpty()) { - // DEBUG - System.err.println(String.format("WARNING: no tokens found for %s in %s", reference, this.currentSource.getEntry())); - } else { - this.gui.showTokens(tokens); - } - } - - public Collection getTokensForReference(EntryReference, Entry> reference) { - EntryRemapper mapper = this.project.getMapper(); - - SourceIndex index = this.currentSource.getIndex(); - return mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_CLOSEST) - .stream() - .flatMap(r -> index.getReferenceTokens(r).stream()) - .collect(Collectors.toList()); - } - - public void openPreviousReference() { - if (hasPreviousReference()) { - setReference(gui.referenceHistory.goBack()); - } - } - - public boolean hasPreviousReference() { - return gui.referenceHistory != null && gui.referenceHistory.canGoBack(); - } - - public void openNextReference() { - if (hasNextReference()) { - setReference(gui.referenceHistory.goForward()); - } - } - - public boolean hasNextReference() { - return gui.referenceHistory != null && gui.referenceHistory.canGoForward(); - } - - public void navigateTo(Entry entry) { - if (!project.isRenamable(entry)) { - // entry is not in the jar. Ignore it - return; - } - openDeclaration(entry); - } - - public void navigateTo(EntryReference, Entry> reference) { - if (!project.isRenamable(reference.getLocationClassEntry())) { - return; - } - openReference(reference); - } - - private void refreshClasses() { - List obfClasses = Lists.newArrayList(); - List deobfClasses = Lists.newArrayList(); - this.addSeparatedClasses(obfClasses, deobfClasses); - this.gui.setObfClasses(obfClasses); - this.gui.setDeobfClasses(deobfClasses); - } - - public void addSeparatedClasses(List obfClasses, List deobfClasses) { - EntryRemapper mapper = project.getMapper(); - - Collection classes = project.getJarIndex().getEntryIndex().getClasses(); - Stream visibleClasses = classes.stream() - .filter(entry -> !entry.isInnerClass()); - - visibleClasses.forEach(entry -> { - ClassEntry deobfEntry = mapper.deobfuscate(entry); - - List obfService = enigma.getServices().get(ObfuscationTestService.TYPE); - boolean obfuscated = deobfEntry.equals(entry); - - if (obfuscated && !obfService.isEmpty()) { - if (obfService.stream().anyMatch(service -> service.testDeobfuscated(entry))) { - obfuscated = false; - } - } - - if (obfuscated) { - obfClasses.add(entry); - } else { - deobfClasses.add(entry); - } - }); - } - - public void refreshCurrentClass() { - refreshCurrentClass(null); - } - - private void refreshCurrentClass(EntryReference, Entry> reference) { - refreshCurrentClass(reference, RefreshMode.MINIMAL); - } - - private void refreshCurrentClass(EntryReference, Entry> reference, RefreshMode mode) { - if (currentSource != null) { - if (reference == null) { - int obfSelectionStart = currentSource.getObfuscatedOffset(gui.editor.getSelectionStart()); - int obfSelectionEnd = currentSource.getObfuscatedOffset(gui.editor.getSelectionEnd()); - - Rectangle viewportBounds = gui.sourceScroller.getViewport().getViewRect(); - // Here we pick an "anchor position", which we want to stay in the same vertical location on the screen after the new text has been set - int anchorModelPos = gui.editor.getSelectionStart(); - Rectangle anchorViewPos = Utils.safeModelToView(gui.editor, anchorModelPos); - if (anchorViewPos.y < viewportBounds.y || anchorViewPos.y >= viewportBounds.y + viewportBounds.height) { - anchorModelPos = gui.editor.viewToModel(new Point(0, viewportBounds.y)); - anchorViewPos = Utils.safeModelToView(gui.editor, anchorModelPos); - } - int obfAnchorPos = currentSource.getObfuscatedOffset(anchorModelPos); - Rectangle anchorViewPos_f = anchorViewPos; - int scrollX = gui.sourceScroller.getHorizontalScrollBar().getValue(); - - loadClass(currentSource.getEntry(), () -> SwingUtilities.invokeLater(() -> { - int newAnchorModelPos = currentSource.getDeobfuscatedOffset(obfAnchorPos); - Rectangle newAnchorViewPos = Utils.safeModelToView(gui.editor, newAnchorModelPos); - int newScrollY = newAnchorViewPos.y - (anchorViewPos_f.y - viewportBounds.y); - - gui.editor.select(currentSource.getDeobfuscatedOffset(obfSelectionStart), currentSource.getDeobfuscatedOffset(obfSelectionEnd)); - // Changing the selection scrolls to the caret position inside a SwingUtilities.invokeLater call, so - // we need to wrap our change to the scroll position inside another invokeLater so it happens after - // the caret's own scrolling. - SwingUtilities.invokeLater(() -> { - gui.sourceScroller.getHorizontalScrollBar().setValue(Math.min(scrollX, gui.sourceScroller.getHorizontalScrollBar().getMaximum())); - gui.sourceScroller.getVerticalScrollBar().setValue(Math.min(newScrollY, gui.sourceScroller.getVerticalScrollBar().getMaximum())); - }); - }), mode); - } else { - loadClass(currentSource.getEntry(), () -> showReference(reference), mode); - } - } - } - - private void loadClass(ClassEntry classEntry, Runnable callback) { - loadClass(classEntry, callback, RefreshMode.MINIMAL); - } - - private void loadClass(ClassEntry classEntry, Runnable callback, RefreshMode mode) { - ClassEntry targetClass = classEntry.getOutermostClass(); - - boolean requiresDecompile = mode == RefreshMode.FULL || currentSource == null || !currentSource.getEntry().equals(targetClass); - if (requiresDecompile) { - currentSource = null; // Or the GUI may try to find a nonexistent token - gui.setEditorText(I18n.translate("info_panel.editor.class.decompiling")); - } - - DECOMPILER_SERVICE.submit(() -> { - try { - if (requiresDecompile || mode == RefreshMode.JAVADOCS) { - currentSource = decompileSource(targetClass, mode == RefreshMode.JAVADOCS); - } - - remapSource(project.getMapper().getDeobfuscator()); - callback.run(); - } catch (Throwable t) { - System.err.println("An exception was thrown while decompiling class " + classEntry.getFullName()); - t.printStackTrace(System.err); - } - }); - } - - private DecompiledClassSource decompileSource(ClassEntry targetClass, boolean onlyRefreshJavadocs) { - try { - if (!onlyRefreshJavadocs || currentSource == null || !currentSource.getEntry().equals(targetClass)) { - uncommentedSource = decompiler.getSource(targetClass.getFullName()); - } - - Source source = uncommentedSource.addJavadocs(project.getMapper()); - - if (source == null) { - gui.setEditorText(I18n.translate("info_panel.editor.class.not_found") + " " + targetClass); - return DecompiledClassSource.text(targetClass, "Unable to find class"); - } - - SourceIndex index = source.index(); - index.resolveReferences(project.getMapper().getObfResolver()); - - return new DecompiledClassSource(targetClass, index); - } catch (Throwable t) { - StringWriter traceWriter = new StringWriter(); - t.printStackTrace(new PrintWriter(traceWriter)); - - return DecompiledClassSource.text(targetClass, traceWriter.toString()); - } - } - - private void remapSource(Translator translator) { - if (currentSource == null) { - return; - } - - currentSource.remapSource(project, translator); - - gui.setEditorTheme(Config.getInstance().lookAndFeel); - gui.setSource(currentSource); - } - - public void modifierChange(ItemEvent event) { - if (event.getStateChange() == ItemEvent.SELECTED) { - EntryRemapper mapper = project.getMapper(); - Entry entry = gui.cursorReference.entry; - AccessModifier modifier = (AccessModifier) event.getItem(); - - EntryMapping mapping = mapper.getDeobfMapping(entry); - if (mapping != null) { - mapper.mapFromObf(entry, new EntryMapping(mapping.getTargetName(), modifier)); - } else { - mapper.mapFromObf(entry, new EntryMapping(entry.getName(), modifier)); - } - - refreshCurrentClass(); - } - } - - public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) { - Translator translator = project.getMapper().getDeobfuscator(); - ClassInheritanceTreeNode rootNode = indexTreeBuilder.buildClassInheritance(translator, entry); - return ClassInheritanceTreeNode.findNode(rootNode, entry); - } - - public ClassImplementationsTreeNode getClassImplementations(ClassEntry entry) { - Translator translator = project.getMapper().getDeobfuscator(); - return this.indexTreeBuilder.buildClassImplementations(translator, entry); - } - - public MethodInheritanceTreeNode getMethodInheritance(MethodEntry entry) { - Translator translator = project.getMapper().getDeobfuscator(); - MethodInheritanceTreeNode rootNode = indexTreeBuilder.buildMethodInheritance(translator, entry); - return MethodInheritanceTreeNode.findNode(rootNode, entry); - } - - public MethodImplementationsTreeNode getMethodImplementations(MethodEntry entry) { - Translator translator = project.getMapper().getDeobfuscator(); - List rootNodes = indexTreeBuilder.buildMethodImplementations(translator, entry); - if (rootNodes.isEmpty()) { - return null; - } - if (rootNodes.size() > 1) { - System.err.println("WARNING: Method " + entry + " implements multiple interfaces. Only showing first one."); - } - return MethodImplementationsTreeNode.findNode(rootNodes.get(0), entry); - } - - public ClassReferenceTreeNode getClassReferences(ClassEntry entry) { - Translator deobfuscator = project.getMapper().getDeobfuscator(); - ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, entry); - rootNode.load(project.getJarIndex(), true); - return rootNode; - } - - public FieldReferenceTreeNode getFieldReferences(FieldEntry entry) { - Translator translator = project.getMapper().getDeobfuscator(); - FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, entry); - rootNode.load(project.getJarIndex(), true); - return rootNode; - } - - public MethodReferenceTreeNode getMethodReferences(MethodEntry entry, boolean recursive) { - Translator translator = project.getMapper().getDeobfuscator(); - MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, entry); - rootNode.load(project.getJarIndex(), true, recursive); - return rootNode; - } - - public void rename(EntryReference, Entry> reference, String newName, boolean refreshClassTree) { - rename(reference, newName, refreshClassTree, true); - } - - public void rename(EntryReference, Entry> reference, String newName, boolean refreshClassTree, boolean jumpToReference) { - Entry entry = reference.getNameableEntry(); - project.getMapper().mapFromObf(entry, new EntryMapping(newName)); - - if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) - this.gui.moveClassTree(reference, newName); - - refreshCurrentClass(jumpToReference ? reference : null); - } - - public void removeMapping(EntryReference, Entry> reference) { - removeMapping(reference, true); - } - - public void removeMapping(EntryReference, Entry> reference, boolean jumpToReference) { - project.getMapper().removeByObf(reference.getNameableEntry()); - - if (reference.entry instanceof ClassEntry) - this.gui.moveClassTree(reference, false, true); - refreshCurrentClass(jumpToReference ? reference : null); - } - - public void changeDocs(EntryReference, Entry> reference, String updatedDocs) { - changeDocs(reference, updatedDocs, true); - } - - public void changeDocs(EntryReference, Entry> reference, String updatedDocs, boolean jumpToReference) { - changeDoc(reference.entry, Utils.isBlank(updatedDocs) ? null : updatedDocs); - - refreshCurrentClass(jumpToReference ? reference : null, RefreshMode.JAVADOCS); - } - - private void changeDoc(Entry obfEntry, String newDoc) { - EntryRemapper mapper = project.getMapper(); - if (mapper.getDeobfMapping(obfEntry) == null) { - markAsDeobfuscated(obfEntry, false); // NPE - } - mapper.mapFromObf(obfEntry, mapper.getDeobfMapping(obfEntry).withDocs(newDoc), false); - } - - private void markAsDeobfuscated(Entry obfEntry, boolean renaming) { - EntryRemapper mapper = project.getMapper(); - mapper.mapFromObf(obfEntry, new EntryMapping(mapper.deobfuscate(obfEntry).getName()), renaming); - } - - public void markAsDeobfuscated(EntryReference, Entry> reference) { - markAsDeobfuscated(reference, true); - } - - public void markAsDeobfuscated(EntryReference, Entry> reference, boolean jumpToReference) { - EntryRemapper mapper = project.getMapper(); - Entry entry = reference.getNameableEntry(); - mapper.mapFromObf(entry, new EntryMapping(mapper.deobfuscate(entry).getName())); - - if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) - this.gui.moveClassTree(reference, true, false); - - refreshCurrentClass(jumpToReference ? reference : null); - } - - public void openStats(Set includedMembers) { - ProgressDialog.runOffThread(gui.getFrame(), progress -> { - String data = new StatsGenerator(project).generate(progress, includedMembers); - - try { - File statsFile = File.createTempFile("stats", ".html"); - - try (FileWriter w = new FileWriter(statsFile)) { - w.write( - Utils.readResourceToString("/stats.html") - .replace("/*data*/", data) - ); - } - - Desktop.getDesktop().open(statsFile); - } catch (IOException e) { - throw new Error(e); - } - }); - } - - public void setDecompiler(DecompilerService service) { - uncommentedSource = null; - decompilerService = service; - decompiler = createDecompiler(); - refreshCurrentClass(null, RefreshMode.FULL); - } - - public EnigmaClient getClient() { - return client; - } - - public EnigmaServer getServer() { - return server; - } - - public void createClient(String username, String ip, int port, char[] password) throws IOException { - client = new EnigmaClient(this, ip, port); - client.connect(); - client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, username)); - gui.setConnectionState(ConnectionState.CONNECTED); - } - - public void createServer(int port, char[] password) throws IOException { - server = new IntegratedEnigmaServer(project.getJarChecksum(), password, EntryRemapper.mapped(project.getJarIndex(), new HashEntryTree<>(project.getMapper().getObfToDeobf())), port); - server.start(); - client = new EnigmaClient(this, "127.0.0.1", port); - client.connect(); - client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, EnigmaServer.OWNER_USERNAME)); - gui.setConnectionState(ConnectionState.HOSTING); - } - - public synchronized void disconnectIfConnected(String reason) { - if (client == null && server == null) { - return; - } - - if (client != null) { - client.disconnect(); - } - if (server != null) { - server.stop(); - } - client = null; - server = null; - SwingUtilities.invokeLater(() -> { - if (reason != null) { - JOptionPane.showMessageDialog(gui.getFrame(), I18n.translate(reason), I18n.translate("disconnect.disconnected"), JOptionPane.INFORMATION_MESSAGE); - } - gui.setConnectionState(ConnectionState.NOT_CONNECTED); - }); - } - - public void sendPacket(Packet packet) { - if (client != null) { - client.sendPacket(packet); - } - } - - public void addMessage(Message message) { - gui.addMessage(message); - } - - public void updateUserList(List users) { - gui.setUserList(users); - } - -} diff --git a/src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java deleted file mode 100644 index c9e38cbf..00000000 --- a/src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java +++ /dev/null @@ -1,24 +0,0 @@ -package cuchaz.enigma.gui; - -import java.awt.Component; - -import javax.swing.DefaultListCellRenderer; -import javax.swing.JList; - -import cuchaz.enigma.utils.Message; - -// For now, just render the translated text. -// TODO: Icons or something later? -public class MessageListCellRenderer extends DefaultListCellRenderer { - - @Override - public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { - super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - Message message = (Message) value; - if (message != null) { - setText(message.translate()); - } - return this; - } - -} diff --git a/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java b/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java deleted file mode 100644 index 05d90a97..00000000 --- a/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java +++ /dev/null @@ -1,42 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.gui; - -import cuchaz.enigma.analysis.MethodInheritanceTreeNode; -import cuchaz.enigma.config.Config; - -import javax.swing.*; -import javax.swing.tree.TreeCellRenderer; -import java.awt.*; - -class MethodTreeCellRenderer implements TreeCellRenderer { - - private final TreeCellRenderer parent; - - MethodTreeCellRenderer(TreeCellRenderer parent) { - this.parent = parent; - } - - @Override - public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { - Component ret = parent.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); - Config config = Config.getInstance(); - if (!(value instanceof MethodInheritanceTreeNode) || ((MethodInheritanceTreeNode) value).isImplemented()) { - ret.setForeground(new Color(config.defaultTextColor)); - ret.setFont(ret.getFont().deriveFont(Font.PLAIN)); - } else { - ret.setForeground(new Color(config.numberColor)); - ret.setFont(ret.getFont().deriveFont(Font.ITALIC)); - } - return ret; - } -} diff --git a/src/main/java/cuchaz/enigma/gui/QuickFindAction.java b/src/main/java/cuchaz/enigma/gui/QuickFindAction.java deleted file mode 100644 index b7fa2eba..00000000 --- a/src/main/java/cuchaz/enigma/gui/QuickFindAction.java +++ /dev/null @@ -1,45 +0,0 @@ -package cuchaz.enigma.gui; - -import de.sciss.syntaxpane.SyntaxDocument; -import de.sciss.syntaxpane.actions.DefaultSyntaxAction; - -import javax.swing.text.JTextComponent; -import java.awt.event.ActionEvent; - -public final class QuickFindAction extends DefaultSyntaxAction { - public QuickFindAction() { - super("quick-find"); - } - - @Override - public void actionPerformed(JTextComponent target, SyntaxDocument document, int dot, ActionEvent event) { - Data data = Data.get(target); - data.showFindDialog(target); - } - - private static class Data { - private static final String KEY = "enigma-find-data"; - private EnigmaQuickFindDialog findDialog; - - private Data() { - } - - public static Data get(JTextComponent target) { - Object o = target.getDocument().getProperty(KEY); - if (o instanceof Data) { - return (Data) o; - } - - Data data = new Data(); - target.getDocument().putProperty(KEY, data); - return data; - } - - public void showFindDialog(JTextComponent target) { - if (findDialog == null) { - findDialog = new EnigmaQuickFindDialog(target); - } - findDialog.showFor(target); - } - } -} diff --git a/src/main/java/cuchaz/enigma/gui/RefreshMode.java b/src/main/java/cuchaz/enigma/gui/RefreshMode.java deleted file mode 100644 index 87cb83b2..00000000 --- a/src/main/java/cuchaz/enigma/gui/RefreshMode.java +++ /dev/null @@ -1,7 +0,0 @@ -package cuchaz.enigma.gui; - -public enum RefreshMode { - MINIMAL, - JAVADOCS, - FULL -} diff --git a/src/main/java/cuchaz/enigma/gui/SourceRemapper.java b/src/main/java/cuchaz/enigma/gui/SourceRemapper.java deleted file mode 100644 index f38f44ea..00000000 --- a/src/main/java/cuchaz/enigma/gui/SourceRemapper.java +++ /dev/null @@ -1,64 +0,0 @@ -package cuchaz.enigma.gui; - -import cuchaz.enigma.analysis.Token; - -import java.util.HashMap; -import java.util.Map; - -public class SourceRemapper { - private final String source; - private final Iterable tokens; - - public SourceRemapper(String source, Iterable tokens) { - this.source = source; - this.tokens = tokens; - } - - public Result remap(Remapper remapper) { - StringBuffer remappedSource = new StringBuffer(source); - Map remappedTokens = new HashMap<>(); - - int accumulatedOffset = 0; - for (Token token : tokens) { - Token movedToken = token.move(accumulatedOffset); - - String remappedName = remapper.remap(token, movedToken); - if (remappedName != null) { - accumulatedOffset += movedToken.getRenameOffset(remappedName); - movedToken.rename(remappedSource, remappedName); - } - - if (!token.equals(movedToken)) { - remappedTokens.put(token, movedToken); - } - } - - return new Result(remappedSource.toString(), remappedTokens); - } - - public static class Result { - private final String remappedSource; - private final Map remappedTokens; - - Result(String remappedSource, Map remappedTokens) { - this.remappedSource = remappedSource; - this.remappedTokens = remappedTokens; - } - - public String getSource() { - return remappedSource; - } - - public Token getRemappedToken(Token token) { - return remappedTokens.getOrDefault(token, token); - } - - public boolean isEmpty() { - return remappedTokens.isEmpty(); - } - } - - public interface Remapper { - String remap(Token token, Token movedToken); - } -} diff --git a/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java deleted file mode 100644 index 7375111e..00000000 --- a/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java +++ /dev/null @@ -1,35 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.gui; - -import cuchaz.enigma.analysis.Token; - -import javax.swing.*; -import java.awt.*; - -public class TokenListCellRenderer implements ListCellRenderer { - - private GuiController controller; - private DefaultListCellRenderer defaultRenderer; - - public TokenListCellRenderer(GuiController controller) { - this.controller = controller; - this.defaultRenderer = new DefaultListCellRenderer(); - } - - @Override - public Component getListCellRendererComponent(JList list, Token token, int index, boolean isSelected, boolean hasFocus) { - JLabel label = (JLabel) this.defaultRenderer.getListCellRendererComponent(list, token, index, isSelected, hasFocus); - label.setText(this.controller.getReadableToken(token).toString()); - return label; - } -} diff --git a/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java deleted file mode 100644 index 43b82651..00000000 --- a/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java +++ /dev/null @@ -1,69 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.gui.dialog; - -import cuchaz.enigma.Constants; -import cuchaz.enigma.utils.I18n; -import cuchaz.enigma.gui.util.ScaleUtil; -import cuchaz.enigma.utils.Utils; - -import javax.swing.*; -import java.awt.*; -import java.io.IOException; - -public class AboutDialog { - - public static void show(JFrame parent) { - // init frame - final JFrame frame = new JFrame(String.format(I18n.translate("menu.help.about.title"), Constants.NAME)); - final Container pane = frame.getContentPane(); - pane.setLayout(new FlowLayout()); - - // load the content - try { - String html = Utils.readResourceToString("/about.html"); - html = String.format(html, Constants.NAME, Constants.VERSION); - JLabel label = new JLabel(html); - label.setHorizontalAlignment(JLabel.CENTER); - pane.add(label); - } catch (IOException ex) { - throw new Error(ex); - } - - // show the link - String html = "%s"; - html = String.format(html, Constants.URL, Constants.URL); - JButton link = new JButton(html); - link.addActionListener(event -> Utils.openUrl(Constants.URL)); - link.setBorderPainted(false); - link.setOpaque(false); - link.setBackground(Color.WHITE); - link.setCursor(new Cursor(Cursor.HAND_CURSOR)); - link.setFocusable(false); - JPanel linkPanel = new JPanel(); - linkPanel.add(link); - pane.add(linkPanel); - - // show ok button - JButton okButton = new JButton(I18n.translate("menu.help.about.ok")); - pane.add(okButton); - okButton.addActionListener(arg0 -> frame.dispose()); - - // show the frame - pane.doLayout(); - frame.setSize(ScaleUtil.getDimension(400, 220)); - frame.setResizable(false); - frame.setLocationRelativeTo(parent); - frame.setVisible(true); - frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - } -} diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java deleted file mode 100644 index 64219ab8..00000000 --- a/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java +++ /dev/null @@ -1,50 +0,0 @@ -package cuchaz.enigma.gui.dialog; - -import java.awt.BorderLayout; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; - -import javax.swing.JButton; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; - -import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.utils.I18n; - -public class ChangeDialog { - - public static void show(Gui gui) { - // init frame - JFrame frame = new JFrame(I18n.translate("menu.view.change.title")); - JPanel textPanel = new JPanel(); - JPanel buttonPanel = new JPanel(); - frame.setLayout(new BorderLayout()); - frame.add(BorderLayout.NORTH, textPanel); - frame.add(BorderLayout.SOUTH, buttonPanel); - - // show text - JLabel text = new JLabel((I18n.translate("menu.view.change.summary"))); - text.setHorizontalAlignment(JLabel.CENTER); - textPanel.add(text); - - // show ok button - JButton okButton = new JButton(I18n.translate("menu.view.change.ok")); - buttonPanel.add(okButton); - okButton.addActionListener(event -> frame.dispose()); - okButton.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { - frame.dispose(); - } - } - }); - - // show the frame - frame.pack(); - frame.setVisible(true); - frame.setResizable(false); - frame.setLocationRelativeTo(gui.getFrame()); - } -} diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java deleted file mode 100644 index c5f505cf..00000000 --- a/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java +++ /dev/null @@ -1,82 +0,0 @@ -package cuchaz.enigma.gui.dialog; - -import cuchaz.enigma.network.EnigmaServer; -import cuchaz.enigma.utils.I18n; - -import javax.swing.*; -import java.awt.Frame; - -public class ConnectToServerDialog { - - public static Result show(Frame parentComponent) { - JTextField usernameField = new JTextField(System.getProperty("user.name"), 20); - JPanel usernameRow = new JPanel(); - usernameRow.add(new JLabel(I18n.translate("prompt.connect.username"))); - usernameRow.add(usernameField); - JTextField ipField = new JTextField(20); - JPanel ipRow = new JPanel(); - ipRow.add(new JLabel(I18n.translate("prompt.connect.ip"))); - ipRow.add(ipField); - JTextField portField = new JTextField(String.valueOf(EnigmaServer.DEFAULT_PORT), 10); - JPanel portRow = new JPanel(); - portRow.add(new JLabel(I18n.translate("prompt.port"))); - portRow.add(portField); - JPasswordField passwordField = new JPasswordField(20); - JPanel passwordRow = new JPanel(); - passwordRow.add(new JLabel(I18n.translate("prompt.password"))); - passwordRow.add(passwordField); - - int response = JOptionPane.showConfirmDialog(parentComponent, new Object[]{usernameRow, ipRow, portRow, passwordRow}, I18n.translate("prompt.connect.title"), JOptionPane.OK_CANCEL_OPTION); - if (response != JOptionPane.OK_OPTION) { - return null; - } - - String username = usernameField.getText(); - String ip = ipField.getText(); - int port; - try { - port = Integer.parseInt(portField.getText()); - } catch (NumberFormatException e) { - JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.nan"), I18n.translate("prompt.connect.title"), JOptionPane.ERROR_MESSAGE); - return null; - } - if (port < 0 || port >= 65536) { - JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.invalid"), I18n.translate("prompt.connect.title"), JOptionPane.ERROR_MESSAGE); - return null; - } - char[] password = passwordField.getPassword(); - - return new Result(username, ip, port, password); - } - - public static class Result { - private final String username; - private final String ip; - private final int port; - private final char[] password; - - public Result(String username, String ip, int port, char[] password) { - this.username = username; - this.ip = ip; - this.port = port; - this.password = password; - } - - public String getUsername() { - return username; - } - - public String getIp() { - return ip; - } - - public int getPort() { - return port; - } - - public char[] getPassword() { - return password; - } - } - -} diff --git a/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java deleted file mode 100644 index 908b42e9..00000000 --- a/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java +++ /dev/null @@ -1,105 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.gui.dialog; - -import cuchaz.enigma.Constants; -import cuchaz.enigma.utils.I18n; -import cuchaz.enigma.gui.util.ScaleUtil; -import cuchaz.enigma.utils.Utils; - -import javax.swing.*; -import java.awt.*; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.io.FileWriter; -import java.io.File; -import java.io.IOException; - -public class CrashDialog { - - private static CrashDialog instance = null; - - private JFrame frame; - private JTextArea text; - - private CrashDialog(JFrame parent) { - // init frame - frame = new JFrame(String.format(I18n.translate("crash.title"), Constants.NAME)); - final Container pane = frame.getContentPane(); - pane.setLayout(new BorderLayout()); - - JLabel label = new JLabel(String.format(I18n.translate("crash.summary"), Constants.NAME)); - label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); - pane.add(label, BorderLayout.NORTH); - - // report panel - text = new JTextArea(); - text.setTabSize(2); - pane.add(new JScrollPane(text), BorderLayout.CENTER); - - // buttons panel - JPanel buttonsPanel = new JPanel(); - buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.LINE_AXIS)); - JButton exportButton = new JButton(I18n.translate("crash.export")); - exportButton.addActionListener(event -> { - JFileChooser chooser = new JFileChooser(); - chooser.setSelectedFile(new File("enigma_crash.log")); - if (chooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) { - try { - File file = chooser.getSelectedFile(); - FileWriter writer = new FileWriter(file); - writer.write(instance.text.getText()); - writer.close(); - } catch (IOException ex) { - ex.printStackTrace(); - } - } - }); - buttonsPanel.add(exportButton); - buttonsPanel.add(Box.createHorizontalGlue()); - buttonsPanel.add(Utils.unboldLabel(new JLabel(I18n.translate("crash.exit.warning")))); - JButton ignoreButton = new JButton(I18n.translate("crash.ignore")); - ignoreButton.addActionListener(event -> { - // close (hide) the dialog - frame.setVisible(false); - }); - buttonsPanel.add(ignoreButton); - JButton exitButton = new JButton(I18n.translate("crash.exit")); - exitButton.addActionListener(event -> { - // exit enigma - System.exit(1); - }); - buttonsPanel.add(exitButton); - pane.add(buttonsPanel, BorderLayout.SOUTH); - - // show the frame - frame.setSize(ScaleUtil.getDimension(600, 400)); - frame.setLocationRelativeTo(parent); - frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - } - - public static void init(JFrame parent) { - instance = new CrashDialog(parent); - } - - public static void show(Throwable ex) { - // get the error report - StringWriter buf = new StringWriter(); - ex.printStackTrace(new PrintWriter(buf)); - String report = buf.toString(); - - // show it! - instance.text.setText(report); - instance.frame.doLayout(); - instance.frame.setVisible(true); - } -} diff --git a/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java deleted file mode 100644 index eea1dff1..00000000 --- a/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java +++ /dev/null @@ -1,65 +0,0 @@ -package cuchaz.enigma.gui.dialog; - -import cuchaz.enigma.network.EnigmaServer; -import cuchaz.enigma.utils.I18n; - -import javax.swing.*; -import java.awt.*; - -public class CreateServerDialog { - - public static Result show(Frame parentComponent) { - JTextField portField = new JTextField(String.valueOf(EnigmaServer.DEFAULT_PORT), 10); - JPanel portRow = new JPanel(); - portRow.add(new JLabel(I18n.translate("prompt.port"))); - portRow.add(portField); - JPasswordField passwordField = new JPasswordField(20); - JPanel passwordRow = new JPanel(); - passwordRow.add(new JLabel(I18n.translate("prompt.password"))); - passwordRow.add(passwordField); - - int response = JOptionPane.showConfirmDialog(parentComponent, new Object[]{portRow, passwordRow}, I18n.translate("prompt.create_server.title"), JOptionPane.OK_CANCEL_OPTION); - if (response != JOptionPane.OK_OPTION) { - return null; - } - - int port; - try { - port = Integer.parseInt(portField.getText()); - } catch (NumberFormatException e) { - JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.nan"), I18n.translate("prompt.create_server.title"), JOptionPane.ERROR_MESSAGE); - return null; - } - if (port < 0 || port >= 65536) { - JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.invalid"), I18n.translate("prompt.create_server.title"), JOptionPane.ERROR_MESSAGE); - return null; - } - - char[] password = passwordField.getPassword(); - if (password.length > EnigmaServer.MAX_PASSWORD_LENGTH) { - JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.password.too_long"), I18n.translate("prompt.create_server.title"), JOptionPane.ERROR_MESSAGE); - return null; - } - - return new Result(port, password); - } - - public static class Result { - private final int port; - private final char[] password; - - public Result(int port, char[] password) { - this.port = port; - this.password = password; - } - - public int getPort() { - return port; - } - - public char[] getPassword() { - return password; - } - } - -} diff --git a/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java deleted file mode 100644 index 7e414419..00000000 --- a/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java +++ /dev/null @@ -1,159 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.gui.dialog; - -import cuchaz.enigma.utils.I18n; -import cuchaz.enigma.gui.util.ScaleUtil; -import cuchaz.enigma.utils.Utils; - -import javax.swing.*; -import javax.swing.text.html.HTML; - -import java.awt.*; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; - -public class JavadocDialog { - - private static JavadocDialog instance = null; - - private JFrame frame; - - private JavadocDialog(JFrame parent, JTextArea text, Callback callback) { - // init frame - frame = new JFrame(I18n.translate("javadocs.edit")); - final Container pane = frame.getContentPane(); - pane.setLayout(new BorderLayout()); - - // editor panel - text.setTabSize(2); - pane.add(new JScrollPane(text), BorderLayout.CENTER); - text.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent event) { - switch (event.getKeyCode()) { - case KeyEvent.VK_ENTER: - if (event.isControlDown()) - callback.closeUi(frame, true); - break; - case KeyEvent.VK_ESCAPE: - callback.closeUi(frame, false); - break; - default: - break; - } - } - }); - - // buttons panel - JPanel buttonsPanel = new JPanel(); - FlowLayout buttonsLayout = new FlowLayout(); - buttonsLayout.setAlignment(FlowLayout.RIGHT); - buttonsPanel.setLayout(buttonsLayout); - buttonsPanel.add(Utils.unboldLabel(new JLabel(I18n.translate("javadocs.instruction")))); - JButton cancelButton = new JButton(I18n.translate("javadocs.cancel")); - cancelButton.addActionListener(event -> { - // close (hide) the dialog - callback.closeUi(frame, false); - }); - buttonsPanel.add(cancelButton); - JButton saveButton = new JButton(I18n.translate("javadocs.save")); - saveButton.addActionListener(event -> { - // exit enigma - callback.closeUi(frame, true); - }); - buttonsPanel.add(saveButton); - pane.add(buttonsPanel, BorderLayout.SOUTH); - - // tags panel - JMenuBar tagsMenu = new JMenuBar(); - - // add javadoc tags - for (JavadocTag tag : JavadocTag.values()) { - JButton tagButton = new JButton(tag.getText()); - tagButton.addActionListener(action -> { - boolean textSelected = text.getSelectedText() != null; - String tagText = tag.isInline() ? "{" + tag.getText() + " }" : tag.getText() + " "; - - if (textSelected) { - if (tag.isInline()) { - tagText = "{" + tag.getText() + " " + text.getSelectedText() + "}"; - } else { - tagText = tag.getText() + " " + text.getSelectedText(); - } - text.replaceSelection(tagText); - } else { - text.insert(tagText, text.getCaretPosition()); - } - - if (tag.isInline()) { - text.setCaretPosition(text.getCaretPosition() - 1); - } - text.grabFocus(); - }); - tagsMenu.add(tagButton); - } - - // add html tags - JComboBox htmlList = new JComboBox(); - htmlList.setPreferredSize(new Dimension()); - for (HTML.Tag htmlTag : HTML.getAllTags()) { - htmlList.addItem(htmlTag.toString()); - } - htmlList.addActionListener(action -> { - String tagText = "<" + htmlList.getSelectedItem().toString() + ">"; - text.insert(tagText, text.getCaretPosition()); - text.grabFocus(); - }); - tagsMenu.add(htmlList); - - pane.add(tagsMenu, BorderLayout.NORTH); - - // show the frame - frame.setSize(ScaleUtil.getDimension(600, 400)); - frame.setLocationRelativeTo(parent); - frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - } - - public static void init(JFrame parent, JTextArea area, Callback callback) { - instance = new JavadocDialog(parent, area, callback); - instance.frame.doLayout(); - instance.frame.setVisible(true); - } - - public interface Callback { - void closeUi(JFrame frame, boolean save); - } - - private enum JavadocTag { - CODE(true), - LINK(true), - LINKPLAIN(true), - RETURN(false), - SEE(false), - THROWS(false); - - private boolean inline; - - private JavadocTag(boolean inline) { - this.inline = inline; - } - - public String getText() { - return "@" + this.name().toLowerCase(); - } - - public boolean isInline() { - return this.inline; - } - } -} diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java deleted file mode 100644 index e33ae821..00000000 --- a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java +++ /dev/null @@ -1,109 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.gui.dialog; - -import cuchaz.enigma.Constants; -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.utils.I18n; -import cuchaz.enigma.gui.util.ScaleUtil; -import cuchaz.enigma.utils.Utils; - -import javax.swing.*; -import java.awt.*; -import java.util.concurrent.CompletableFuture; - -public class ProgressDialog implements ProgressListener, AutoCloseable { - - private JFrame frame; - private JLabel labelTitle; - private JLabel labelText; - private JProgressBar progress; - - public ProgressDialog(JFrame parent) { - - // init frame - this.frame = new JFrame(String.format(I18n.translate("progress.operation"), Constants.NAME)); - final Container pane = this.frame.getContentPane(); - FlowLayout layout = new FlowLayout(); - layout.setAlignment(FlowLayout.LEFT); - pane.setLayout(layout); - - this.labelTitle = new JLabel(); - pane.add(this.labelTitle); - - // set up the progress bar - JPanel panel = new JPanel(); - pane.add(panel); - panel.setLayout(new BorderLayout()); - this.labelText = Utils.unboldLabel(new JLabel()); - this.progress = new JProgressBar(); - this.labelText.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); - panel.add(this.labelText, BorderLayout.NORTH); - panel.add(this.progress, BorderLayout.CENTER); - panel.setPreferredSize(ScaleUtil.getDimension(360, 50)); - - // show the frame - pane.doLayout(); - this.frame.setSize(ScaleUtil.getDimension(400, 120)); - this.frame.setResizable(false); - this.frame.setLocationRelativeTo(parent); - this.frame.setVisible(true); - this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - } - - public static CompletableFuture runOffThread(final JFrame parent, final ProgressRunnable runnable) { - CompletableFuture future = new CompletableFuture<>(); - new Thread(() -> - { - try (ProgressDialog progress = new ProgressDialog(parent)) { - runnable.run(progress); - future.complete(null); - } catch (Exception ex) { - future.completeExceptionally(ex); - throw new Error(ex); - } - }).start(); - return future; - } - - @Override - public void close() { - this.frame.dispose(); - } - - @Override - public void init(int totalWork, String title) { - this.labelTitle.setText(title); - this.progress.setMinimum(0); - this.progress.setMaximum(totalWork); - this.progress.setValue(0); - } - - @Override - public void step(int numDone, String message) { - this.labelText.setText(message); - if (numDone != -1) { - this.progress.setValue(numDone); - this.progress.setIndeterminate(false); - } else { - this.progress.setIndeterminate(true); - } - - // update the frame - this.frame.validate(); - this.frame.repaint(); - } - - public interface ProgressRunnable { - void run(ProgressListener listener) throws Exception; - } -} diff --git a/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java deleted file mode 100644 index b283a377..00000000 --- a/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java +++ /dev/null @@ -1,261 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.gui.dialog; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.FlowLayout; -import java.awt.Font; -import java.awt.event.*; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import javax.swing.*; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; - -import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.gui.GuiController; -import cuchaz.enigma.gui.util.AbstractListCellRenderer; -import cuchaz.enigma.gui.util.ScaleUtil; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.utils.I18n; -import cuchaz.enigma.utils.search.SearchEntry; -import cuchaz.enigma.utils.search.SearchUtil; - -public class SearchDialog { - - private final JTextField searchField; - private DefaultListModel classListModel; - private final JList classList; - private final JDialog dialog; - - private final Gui parent; - private final SearchUtil su; - private SearchUtil.SearchControl currentSearch; - - public SearchDialog(Gui parent) { - this.parent = parent; - - su = new SearchUtil<>(); - - dialog = new JDialog(parent.getFrame(), I18n.translate("menu.view.search"), true); - JPanel contentPane = new JPanel(); - contentPane.setBorder(ScaleUtil.createEmptyBorder(4, 4, 4, 4)); - contentPane.setLayout(new BorderLayout(ScaleUtil.scale(4), ScaleUtil.scale(4))); - - searchField = new JTextField(); - searchField.getDocument().addDocumentListener(new DocumentListener() { - - @Override - public void insertUpdate(DocumentEvent e) { - updateList(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - updateList(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - updateList(); - } - - }); - searchField.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_DOWN) { - int next = classList.isSelectionEmpty() ? 0 : classList.getSelectedIndex() + 1; - classList.setSelectedIndex(next); - classList.ensureIndexIsVisible(next); - } else if (e.getKeyCode() == KeyEvent.VK_UP) { - int prev = classList.isSelectionEmpty() ? classList.getModel().getSize() : classList.getSelectedIndex() - 1; - classList.setSelectedIndex(prev); - classList.ensureIndexIsVisible(prev); - } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { - close(); - } - } - }); - searchField.addActionListener(e -> openSelected()); - contentPane.add(searchField, BorderLayout.NORTH); - - classListModel = new DefaultListModel<>(); - classList = new JList<>(); - classList.setModel(classListModel); - classList.setCellRenderer(new ListCellRendererImpl()); - classList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - classList.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent mouseEvent) { - if (mouseEvent.getClickCount() >= 2) { - int idx = classList.locationToIndex(mouseEvent.getPoint()); - SearchEntryImpl entry = classList.getModel().getElementAt(idx); - openEntry(entry); - } - } - }); - contentPane.add(new JScrollPane(classList, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), BorderLayout.CENTER); - - JPanel buttonBar = new JPanel(); - buttonBar.setLayout(new FlowLayout(FlowLayout.RIGHT)); - JButton open = new JButton(I18n.translate("prompt.open")); - open.addActionListener(event -> openSelected()); - buttonBar.add(open); - JButton cancel = new JButton(I18n.translate("prompt.cancel")); - cancel.addActionListener(event -> close()); - buttonBar.add(cancel); - contentPane.add(buttonBar, BorderLayout.SOUTH); - - // apparently the class list doesn't update by itself when the list - // state changes and the dialog is hidden - dialog.addComponentListener(new ComponentAdapter() { - @Override - public void componentShown(ComponentEvent e) { - classList.updateUI(); - } - }); - - dialog.setContentPane(contentPane); - dialog.setSize(ScaleUtil.getDimension(400, 500)); - dialog.setLocationRelativeTo(parent.getFrame()); - } - - public void show() { - su.clear(); - parent.getController().project.getJarIndex().getEntryIndex().getClasses().parallelStream() - .filter(e -> !e.isInnerClass()) - .map(e -> SearchEntryImpl.from(e, parent.getController())) - .map(SearchUtil.Entry::from) - .sequential() - .forEach(su::add); - - updateList(); - - searchField.requestFocus(); - searchField.selectAll(); - - dialog.setVisible(true); - } - - private void openSelected() { - SearchEntryImpl selectedValue = classList.getSelectedValue(); - if (selectedValue != null) { - openEntry(selectedValue); - } - } - - private void openEntry(SearchEntryImpl e) { - close(); - su.hit(e); - parent.getController().navigateTo(e.obf); - if (e.deobf != null) { - parent.getDeobfPanel().deobfClasses.setSelectionClass(e.deobf); - } else { - parent.getObfPanel().obfClasses.setSelectionClass(e.obf); - } - } - - private void close() { - dialog.setVisible(false); - } - - // Updates the list of class names - private void updateList() { - if (currentSearch != null) currentSearch.stop(); - - DefaultListModel classListModel = new DefaultListModel<>(); - this.classListModel = classListModel; - classList.setModel(classListModel); - - currentSearch = su.asyncSearch(searchField.getText(), (idx, e) -> SwingUtilities.invokeLater(() -> classListModel.insertElementAt(e, idx))); - } - - public void dispose() { - dialog.dispose(); - } - - private static final class SearchEntryImpl implements SearchEntry { - - public final ClassEntry obf; - public final ClassEntry deobf; - - private SearchEntryImpl(ClassEntry obf, ClassEntry deobf) { - this.obf = obf; - this.deobf = deobf; - } - - @Override - public List getSearchableNames() { - if (deobf != null) { - return Arrays.asList(obf.getSimpleName(), deobf.getSimpleName()); - } else { - return Collections.singletonList(obf.getSimpleName()); - } - } - - @Override - public String getIdentifier() { - return obf.getFullName(); - } - - @Override - public String toString() { - return String.format("SearchEntryImpl { obf: %s, deobf: %s }", obf, deobf); - } - - public static SearchEntryImpl from(ClassEntry e, GuiController controller) { - ClassEntry deobf = controller.project.getMapper().deobfuscate(e); - if (deobf.equals(e)) deobf = null; - return new SearchEntryImpl(e, deobf); - } - - } - - private static final class ListCellRendererImpl extends AbstractListCellRenderer { - - private final JLabel mainName; - private final JLabel secondaryName; - - public ListCellRendererImpl() { - this.setLayout(new BorderLayout()); - - mainName = new JLabel(); - this.add(mainName, BorderLayout.WEST); - - secondaryName = new JLabel(); - secondaryName.setFont(secondaryName.getFont().deriveFont(Font.ITALIC)); - secondaryName.setForeground(Color.GRAY); - this.add(secondaryName, BorderLayout.EAST); - } - - @Override - public void updateUiForEntry(JList list, SearchEntryImpl value, int index, boolean isSelected, boolean cellHasFocus) { - if (value.deobf == null) { - mainName.setText(value.obf.getSimpleName()); - mainName.setToolTipText(value.obf.getFullName()); - secondaryName.setText(""); - secondaryName.setToolTipText(""); - } else { - mainName.setText(value.deobf.getSimpleName()); - mainName.setToolTipText(value.deobf.getFullName()); - secondaryName.setText(value.obf.getSimpleName()); - secondaryName.setToolTipText(value.obf.getFullName()); - } - } - - } - -} diff --git a/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java deleted file mode 100644 index 868eba79..00000000 --- a/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java +++ /dev/null @@ -1,82 +0,0 @@ -package cuchaz.enigma.gui.dialog; - -import java.awt.BorderLayout; -import java.util.Arrays; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JFrame; -import javax.swing.JPanel; - -import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.gui.stats.StatsMember; -import cuchaz.enigma.gui.util.ScaleUtil; -import cuchaz.enigma.utils.I18n; - -public class StatsDialog { - - public static void show(Gui gui) { - // init frame - JFrame frame = new JFrame(I18n.translate("menu.file.stats.title")); - JPanel checkboxesPanel = new JPanel(); - JPanel buttonPanel = new JPanel(); - frame.setLayout(new BorderLayout()); - frame.add(BorderLayout.NORTH, checkboxesPanel); - frame.add(BorderLayout.SOUTH, buttonPanel); - - // show checkboxes - Map checkboxes = Arrays - .stream(StatsMember.values()) - .collect(Collectors.toMap(m -> m, m -> { - JCheckBox checkbox = new JCheckBox(I18n.translate("type." + m.name().toLowerCase(Locale.ROOT))); - checkboxesPanel.add(checkbox); - return checkbox; - })); - - // show generate button - JButton button = new JButton(I18n.translate("menu.file.stats.generate")); - buttonPanel.add(button); - button.setEnabled(false); - button.addActionListener(action -> { - frame.dispose(); - generateStats(gui, checkboxes); - }); - - // add action listener to each checkbox - checkboxes.entrySet().forEach(checkbox -> { - checkbox.getValue().addActionListener(action -> { - if (!button.isEnabled()) { - button.setEnabled(true); - } else if (checkboxes.entrySet().stream().allMatch(entry -> !entry.getValue().isSelected())) { - button.setEnabled(false); - } - }); - }); - - // show the frame - frame.pack(); - frame.setVisible(true); - frame.setSize(ScaleUtil.getDimension(500, 120)); - frame.setResizable(false); - frame.setLocationRelativeTo(gui.getFrame()); - } - - private static void generateStats(Gui gui, Map checkboxes) { - // get members from selected checkboxes - Set includedMembers = checkboxes - .entrySet() - .stream() - .filter(entry -> entry.getValue().isSelected()) - .map(Map.Entry::getKey) - .collect(Collectors.toSet()); - - // checks if a projet is open - if (gui.getController().project != null) { - gui.getController().openStats(includedMembers); - } - } -} diff --git a/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java b/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java deleted file mode 100644 index fb497b11..00000000 --- a/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java +++ /dev/null @@ -1,40 +0,0 @@ -package cuchaz.enigma.gui.elements; - -import java.awt.event.MouseEvent; - -import javax.swing.JTabbedPane; - -public class CollapsibleTabbedPane extends JTabbedPane { - - public CollapsibleTabbedPane() { - } - - public CollapsibleTabbedPane(int tabPlacement) { - super(tabPlacement); - } - - public CollapsibleTabbedPane(int tabPlacement, int tabLayoutPolicy) { - super(tabPlacement, tabLayoutPolicy); - } - - @Override - protected void processMouseEvent(MouseEvent e) { - int id = e.getID(); - if (id == MouseEvent.MOUSE_PRESSED) { - if (!isEnabled()) return; - int tabIndex = getUI().tabForCoordinate(this, e.getX(), e.getY()); - if (tabIndex >= 0 && isEnabledAt(tabIndex)) { - if (tabIndex == getSelectedIndex()) { - if (isFocusOwner() && isRequestFocusEnabled()) { - requestFocus(); - } else { - setSelectedIndex(-1); - } - return; - } - } - } - super.processMouseEvent(e); - } - -} diff --git a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java deleted file mode 100644 index dc2cf8fd..00000000 --- a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java +++ /dev/null @@ -1,386 +0,0 @@ -package cuchaz.enigma.gui.elements; - -import cuchaz.enigma.config.Config; -import cuchaz.enigma.config.Themes; -import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.gui.dialog.AboutDialog; -import cuchaz.enigma.gui.dialog.ChangeDialog; -import cuchaz.enigma.gui.dialog.ConnectToServerDialog; -import cuchaz.enigma.gui.dialog.CreateServerDialog; -import cuchaz.enigma.gui.dialog.StatsDialog; -import cuchaz.enigma.gui.util.ScaleUtil; -import cuchaz.enigma.translation.mapping.serde.MappingFormat; -import cuchaz.enigma.utils.I18n; -import cuchaz.enigma.utils.Pair; - -import java.awt.Desktop; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; -import java.io.File; -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import javax.swing.*; - -public class MenuBar extends JMenuBar { - - public final JMenuItem closeJarMenu; - public final List openMappingsMenus; - public final JMenuItem saveMappingsMenu; - public final List saveMappingsMenus; - public final JMenuItem closeMappingsMenu; - public final JMenuItem dropMappingsMenu; - public final JMenuItem exportSourceMenu; - public final JMenuItem exportJarMenu; - public final JMenuItem connectToServerMenu; - public final JMenuItem startServerMenu; - private final Gui gui; - - public MenuBar(Gui gui) { - this.gui = gui; - - /* - * File menu - */ - { - JMenu menu = new JMenu(I18n.translate("menu.file")); - this.add(menu); - { - JMenuItem item = new JMenuItem(I18n.translate("menu.file.jar.open")); - menu.add(item); - item.addActionListener(event -> { - this.gui.jarFileChooser.setVisible(true); - String file = this.gui.jarFileChooser.getFile(); - // checks if the file name is not empty - if (file != null) { - Path path = Paths.get(this.gui.jarFileChooser.getDirectory()).resolve(file); - // checks if the file name corresponds to an existing file - if (Files.exists(path)) { - gui.getController().openJar(path); - } - } - }); - } - { - JMenuItem item = new JMenuItem(I18n.translate("menu.file.jar.close")); - menu.add(item); - item.addActionListener(event -> this.gui.getController().closeJar()); - this.closeJarMenu = item; - } - menu.addSeparator(); - JMenu openMenu = new JMenu(I18n.translate("menu.file.mappings.open")); - menu.add(openMenu); - { - openMappingsMenus = new ArrayList<>(); - for (MappingFormat format : MappingFormat.values()) { - if (format.getReader() != null) { - JMenuItem item = new JMenuItem(I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT))); - openMenu.add(item); - item.addActionListener(event -> { - if (this.gui.enigmaMappingsFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - File selectedFile = this.gui.enigmaMappingsFileChooser.getSelectedFile(); - this.gui.getController().openMappings(format, selectedFile.toPath()); - } - }); - openMappingsMenus.add(item); - } - } - } - { - JMenuItem item = new JMenuItem(I18n.translate("menu.file.mappings.save")); - menu.add(item); - item.addActionListener(event -> { - this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath()); - }); - item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); - this.saveMappingsMenu = item; - } - JMenu saveMenu = new JMenu(I18n.translate("menu.file.mappings.save_as")); - menu.add(saveMenu); - { - saveMappingsMenus = new ArrayList<>(); - for (MappingFormat format : MappingFormat.values()) { - if (format.getWriter() != null) { - JMenuItem item = new JMenuItem(I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT))); - saveMenu.add(item); - item.addActionListener(event -> { - // TODO: Use a specific file chooser for it - if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), format); - this.saveMappingsMenu.setEnabled(true); - } - }); - saveMappingsMenus.add(item); - } - } - } - { - JMenuItem item = new JMenuItem(I18n.translate("menu.file.mappings.close")); - menu.add(item); - item.addActionListener(event -> { - if (this.gui.getController().isDirty()) { - this.gui.showDiscardDiag((response -> { - if (response == JOptionPane.YES_OPTION) { - gui.saveMapping(); - this.gui.getController().closeMappings(); - } else if (response == JOptionPane.NO_OPTION) - this.gui.getController().closeMappings(); - return null; - }), I18n.translate("prompt.close.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.close.cancel")); - } else - this.gui.getController().closeMappings(); - - }); - this.closeMappingsMenu = item; - } - { - JMenuItem item = new JMenuItem(I18n.translate("menu.file.mappings.drop")); - menu.add(item); - item.addActionListener(event -> this.gui.getController().dropMappings()); - this.dropMappingsMenu = item; - } - menu.addSeparator(); - { - JMenuItem item = new JMenuItem(I18n.translate("menu.file.export.source")); - menu.add(item); - item.addActionListener(event -> { - if (this.gui.exportSourceFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) { - this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile().toPath()); - } - }); - this.exportSourceMenu = item; - } - { - JMenuItem item = new JMenuItem(I18n.translate("menu.file.export.jar")); - menu.add(item); - item.addActionListener(event -> { - this.gui.exportJarFileChooser.setVisible(true); - if (this.gui.exportJarFileChooser.getFile() != null) { - Path path = Paths.get(this.gui.exportJarFileChooser.getDirectory(), this.gui.exportJarFileChooser.getFile()); - this.gui.getController().exportJar(path); - } - }); - this.exportJarMenu = item; - } - menu.addSeparator(); - { - JMenuItem stats = new JMenuItem(I18n.translate("menu.file.stats")); - menu.add(stats); - stats.addActionListener(event -> StatsDialog.show(this.gui)); - } - menu.addSeparator(); - { - JMenuItem item = new JMenuItem(I18n.translate("menu.file.exit")); - menu.add(item); - item.addActionListener(event -> this.gui.close()); - } - } - - /* - * Decompiler menu - */ - { - JMenu menu = new JMenu(I18n.translate("menu.decompiler")); - this.add(menu); - - ButtonGroup decompilerGroup = new ButtonGroup(); - - for (Config.Decompiler decompiler : Config.Decompiler.values()) { - JRadioButtonMenuItem decompilerButton = new JRadioButtonMenuItem(decompiler.name); - decompilerGroup.add(decompilerButton); - if (decompiler.equals(Config.getInstance().decompiler)) { - decompilerButton.setSelected(true); - } - menu.add(decompilerButton); - decompilerButton.addActionListener(event -> { - gui.getController().setDecompiler(decompiler.service); - - try { - Config.getInstance().decompiler = decompiler; - Config.getInstance().saveConfig(); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - } - } - - /* - * View menu - */ - { - JMenu menu = new JMenu(I18n.translate("menu.view")); - this.add(menu); - { - JMenu themes = new JMenu(I18n.translate("menu.view.themes")); - menu.add(themes); - ButtonGroup themeGroup = new ButtonGroup(); - for (Config.LookAndFeel lookAndFeel : Config.LookAndFeel.values()) { - JRadioButtonMenuItem themeButton = new JRadioButtonMenuItem(I18n.translate("menu.view.themes." + lookAndFeel.name().toLowerCase(Locale.ROOT))); - themeGroup.add(themeButton); - if (lookAndFeel.equals(Config.getInstance().lookAndFeel)) { - themeButton.setSelected(true); - } - themes.add(themeButton); - themeButton.addActionListener(event -> Themes.setLookAndFeel(gui, lookAndFeel)); - } - } - { - JMenu languages = new JMenu(I18n.translate("menu.view.languages")); - menu.add(languages); - ButtonGroup languageGroup = new ButtonGroup(); - for (String lang : I18n.getAvailableLanguages()) { - JRadioButtonMenuItem languageButton = new JRadioButtonMenuItem(I18n.getLanguageName(lang)); - languageGroup.add(languageButton); - if (lang.equals(Config.getInstance().language)) { - languageButton.setSelected(true); - } - languages.add(languageButton); - languageButton.addActionListener(event -> { - I18n.setLanguage(lang); - ChangeDialog.show(this.gui); - }); - } - } - { - JMenu scale = new JMenu(I18n.translate("menu.view.scale")); - { - ButtonGroup scaleGroup = new ButtonGroup(); - Map map = IntStream.of(100, 125, 150, 175, 200) - .mapToObj(scaleFactor -> { - float realScaleFactor = scaleFactor / 100f; - JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(String.format("%d%%", scaleFactor)); - menuItem.addActionListener(event -> ScaleUtil.setScaleFactor(realScaleFactor)); - menuItem.addActionListener(event -> ChangeDialog.show(this.gui)); - scaleGroup.add(menuItem); - scale.add(menuItem); - return new Pair<>(realScaleFactor, menuItem); - }) - .collect(Collectors.toMap(x -> x.a, x -> x.b)); - - JMenuItem customScale = new JMenuItem(I18n.translate("menu.view.scale.custom")); - customScale.addActionListener(event -> { - String answer = (String) JOptionPane.showInputDialog(gui.getFrame(), I18n.translate("menu.view.scale.custom.title"), I18n.translate("menu.view.scale.custom.title"), - JOptionPane.QUESTION_MESSAGE, null, null, Float.toString(ScaleUtil.getScaleFactor() * 100)); - if (answer == null) return; - float newScale = 1.0f; - try { - newScale = Float.parseFloat(answer) / 100f; - } catch (NumberFormatException ignored) { - } - ScaleUtil.setScaleFactor(newScale); - ChangeDialog.show(this.gui); - }); - scale.add(customScale); - ScaleUtil.addListener((newScale, _oldScale) -> { - JRadioButtonMenuItem mi = map.get(newScale); - if (mi != null) { - mi.setSelected(true); - } else { - scaleGroup.clearSelection(); - } - }); - JRadioButtonMenuItem mi = map.get(ScaleUtil.getScaleFactor()); - if (mi != null) { - mi.setSelected(true); - } - } - menu.add(scale); - } - menu.addSeparator(); - { - JMenuItem search = new JMenuItem(I18n.translate("menu.view.search")); - search.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.SHIFT_MASK)); - menu.add(search); - search.addActionListener(event -> { - if (this.gui.getController().project != null) { - this.gui.getSearchDialog().show(); - } - }); - } - } - - /* - * Collab menu - */ - { - JMenu menu = new JMenu(I18n.translate("menu.collab")); - this.add(menu); - { - JMenuItem item = new JMenuItem(I18n.translate("menu.collab.connect")); - menu.add(item); - item.addActionListener(event -> { - if (this.gui.getController().getClient() != null) { - this.gui.getController().disconnectIfConnected(null); - return; - } - ConnectToServerDialog.Result result = ConnectToServerDialog.show(this.gui.getFrame()); - if (result == null) { - return; - } - this.gui.getController().disconnectIfConnected(null); - try { - this.gui.getController().createClient(result.getUsername(), result.getIp(), result.getPort(), result.getPassword()); - } catch (IOException e) { - JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.connect.error"), JOptionPane.ERROR_MESSAGE); - this.gui.getController().disconnectIfConnected(null); - } - Arrays.fill(result.getPassword(), (char)0); - }); - this.connectToServerMenu = item; - } - { - JMenuItem item = new JMenuItem(I18n.translate("menu.collab.server.start")); - menu.add(item); - item.addActionListener(event -> { - if (this.gui.getController().getServer() != null) { - this.gui.getController().disconnectIfConnected(null); - return; - } - CreateServerDialog.Result result = CreateServerDialog.show(this.gui.getFrame()); - if (result == null) { - return; - } - this.gui.getController().disconnectIfConnected(null); - try { - this.gui.getController().createServer(result.getPort(), result.getPassword()); - } catch (IOException e) { - JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.server.start.error"), JOptionPane.ERROR_MESSAGE); - this.gui.getController().disconnectIfConnected(null); - } - }); - this.startServerMenu = item; - } - } - - /* - * Help menu - */ - { - JMenu menu = new JMenu(I18n.translate("menu.help")); - this.add(menu); - { - JMenuItem item = new JMenuItem(I18n.translate("menu.help.about")); - menu.add(item); - item.addActionListener(event -> AboutDialog.show(this.gui.getFrame())); - } - { - JMenuItem item = new JMenuItem(I18n.translate("menu.help.github")); - menu.add(item); - item.addActionListener(event -> { - try { - Desktop.getDesktop().browse(new URL("https://github.com/FabricMC/Enigma").toURI()); - } catch (URISyntaxException | IOException ignored) { - } - }); - } - } - } -} diff --git a/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java deleted file mode 100644 index b92041c3..00000000 --- a/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java +++ /dev/null @@ -1,125 +0,0 @@ -package cuchaz.enigma.gui.elements; - -import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.utils.I18n; - -import javax.swing.*; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; - -public class PopupMenuBar extends JPopupMenu { - - public final JMenuItem renameMenu; - public final JMenuItem editJavadocMenu; - public final JMenuItem showInheritanceMenu; - public final JMenuItem showImplementationsMenu; - public final JMenuItem showCallsMenu; - public final JMenuItem showCallsSpecificMenu; - public final JMenuItem openEntryMenu; - public final JMenuItem openPreviousMenu; - public final JMenuItem openNextMenu; - public final JMenuItem toggleMappingMenu; - - public PopupMenuBar(Gui gui) { - { - JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.rename")); - menu.addActionListener(event -> gui.startRename()); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_DOWN_MASK)); - menu.setEnabled(false); - this.add(menu); - this.renameMenu = menu; - } - { - JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.javadoc")); - menu.addActionListener(event -> gui.startDocChange()); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, InputEvent.CTRL_DOWN_MASK)); - menu.setEnabled(false); - this.add(menu); - this.editJavadocMenu = menu; - } - { - JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.inheritance")); - menu.addActionListener(event -> gui.showInheritance()); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, InputEvent.CTRL_DOWN_MASK)); - menu.setEnabled(false); - this.add(menu); - this.showInheritanceMenu = menu; - } - { - JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.implementations")); - menu.addActionListener(event -> gui.showImplementations()); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, InputEvent.CTRL_DOWN_MASK)); - menu.setEnabled(false); - this.add(menu); - this.showImplementationsMenu = menu; - } - { - JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.calls")); - menu.addActionListener(event -> gui.showCalls(true)); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK)); - menu.setEnabled(false); - this.add(menu); - this.showCallsMenu = menu; - } - { - JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.calls.specific")); - menu.addActionListener(event -> gui.showCalls(false)); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK + InputEvent.SHIFT_DOWN_MASK)); - menu.setEnabled(false); - this.add(menu); - this.showCallsSpecificMenu = menu; - } - { - JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.declaration")); - menu.addActionListener(event -> gui.getController().navigateTo(gui.cursorReference.entry)); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK)); - menu.setEnabled(false); - this.add(menu); - this.openEntryMenu = menu; - } - { - JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.back")); - menu.addActionListener(event -> gui.getController().openPreviousReference()); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, InputEvent.CTRL_DOWN_MASK)); - menu.setEnabled(false); - this.add(menu); - this.openPreviousMenu = menu; - } - { - JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.forward")); - menu.addActionListener(event -> gui.getController().openNextReference()); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, InputEvent.CTRL_DOWN_MASK)); - menu.setEnabled(false); - this.add(menu); - this.openNextMenu = menu; - } - { - JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.mark_deobfuscated")); - menu.addActionListener(event -> gui.toggleMapping()); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK)); - menu.setEnabled(false); - this.add(menu); - this.toggleMappingMenu = menu; - } - { - this.add(new JSeparator()); - } - { - JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.in")); - menu.addActionListener(event -> gui.editor.offsetEditorZoom(2)); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, InputEvent.CTRL_DOWN_MASK)); - this.add(menu); - } - { - JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.out")); - menu.addActionListener(event -> gui.editor.offsetEditorZoom(-2)); - menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_DOWN_MASK)); - this.add(menu); - } - { - JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.reset")); - menu.addActionListener(event -> gui.editor.resetEditorZoom()); - this.add(menu); - } - } -} diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java deleted file mode 100644 index f5f66287..00000000 --- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java +++ /dev/null @@ -1,10 +0,0 @@ -package cuchaz.enigma.gui.filechooser; - -import javax.swing.*; - -public class FileChooserAny extends JFileChooser { - public FileChooserAny() { - this.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); - this.setAcceptAllFileFilterUsed(false); - } -} \ No newline at end of file diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java deleted file mode 100644 index cea11a68..00000000 --- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java +++ /dev/null @@ -1,8 +0,0 @@ -package cuchaz.enigma.gui.filechooser; - -import javax.swing.*; - -public class FileChooserFile extends JFileChooser { - public FileChooserFile() { - } -} diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java deleted file mode 100644 index c16e0afc..00000000 --- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java +++ /dev/null @@ -1,11 +0,0 @@ -package cuchaz.enigma.gui.filechooser; - -import javax.swing.*; - -public class FileChooserFolder extends JFileChooser { - - public FileChooserFolder() { - this.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - this.setAcceptAllFileFilterUsed(false); - } -} diff --git a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java deleted file mode 100644 index cef64943..00000000 --- a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java +++ /dev/null @@ -1,69 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.gui.highlight; - -import cuchaz.enigma.config.Config; - -import javax.swing.text.BadLocationException; -import javax.swing.text.Highlighter; -import javax.swing.text.JTextComponent; -import java.awt.*; - -public class BoxHighlightPainter implements Highlighter.HighlightPainter { - private Color fillColor; - private Color borderColor; - - protected BoxHighlightPainter(Color fillColor, Color borderColor) { - this.fillColor = fillColor; - this.borderColor = borderColor; - } - - public static BoxHighlightPainter create(Config.AlphaColorEntry entry, Config.AlphaColorEntry entryOutline) { - return new BoxHighlightPainter(entry != null ? entry.get() : null, entryOutline != null ? entryOutline.get() : null); - } - - public static Rectangle getBounds(JTextComponent text, int start, int end) { - try { - // determine the bounds of the text - Rectangle startRect = text.modelToView(start); - Rectangle endRect = text.modelToView(end); - Rectangle bounds = startRect.union(endRect); - - // adjust the box so it looks nice - bounds.x -= 2; - bounds.width += 2; - bounds.y += 1; - bounds.height -= 2; - - return bounds; - } catch (BadLocationException ex) { - // don't care... just return something - return new Rectangle(0, 0, 0, 0); - } - } - - @Override - public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { - Rectangle bounds = getBounds(text, start, end); - - // fill the area - if (this.fillColor != null) { - g.setColor(this.fillColor); - g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); - } - - // draw a box around the area - g.setColor(this.borderColor); - g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); - } - -} diff --git a/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java deleted file mode 100644 index 81a70a92..00000000 --- a/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java +++ /dev/null @@ -1,31 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.gui.highlight; - -import cuchaz.enigma.config.Config; - -import javax.swing.text.Highlighter; -import javax.swing.text.JTextComponent; -import java.awt.*; - -public class SelectionHighlightPainter implements Highlighter.HighlightPainter { - - @Override - public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { - // draw a thick border - Graphics2D g2d = (Graphics2D) g; - Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end); - g2d.setColor(new Color(Config.getInstance().selectionHighlightColor)); - g2d.setStroke(new BasicStroke(2.0f)); - g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); - } -} diff --git a/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java b/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java deleted file mode 100644 index ae23f324..00000000 --- a/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java +++ /dev/null @@ -1,7 +0,0 @@ -package cuchaz.enigma.gui.highlight; - -public enum TokenHighlightType { - OBFUSCATED, - DEOBFUSCATED, - PROPOSED -} diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java deleted file mode 100644 index 922f8f24..00000000 --- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java +++ /dev/null @@ -1,72 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.gui.node; - -import cuchaz.enigma.translation.representation.entry.ClassEntry; - -import javax.swing.tree.DefaultMutableTreeNode; - -public class ClassSelectorClassNode extends DefaultMutableTreeNode { - - private final ClassEntry obfEntry; - private ClassEntry classEntry; - - public ClassSelectorClassNode(ClassEntry obfEntry, ClassEntry classEntry) { - this.obfEntry = obfEntry; - this.classEntry = classEntry; - this.setUserObject(classEntry); - } - - public ClassEntry getObfEntry() { - return obfEntry; - } - - public ClassEntry getClassEntry() { - return this.classEntry; - } - - @Override - public String toString() { - return this.classEntry.getSimpleName(); - } - - @Override - public boolean equals(Object other) { - return other instanceof ClassSelectorClassNode && equals((ClassSelectorClassNode) other); - } - - @Override - public int hashCode() { - return 17 + (classEntry != null ? classEntry.hashCode() : 0); - } - - @Override - public Object getUserObject() { - return classEntry; - } - - @Override - public void setUserObject(Object userObject) { - String packageName = ""; - if (classEntry.getPackageName() != null) - packageName = classEntry.getPackageName() + "/"; - if (userObject instanceof String) - this.classEntry = new ClassEntry(packageName + userObject); - else if (userObject instanceof ClassEntry) - this.classEntry = (ClassEntry) userObject; - super.setUserObject(classEntry); - } - - public boolean equals(ClassSelectorClassNode other) { - return this.classEntry.equals(other.classEntry); - } -} diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java deleted file mode 100644 index caa985c9..00000000 --- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java +++ /dev/null @@ -1,58 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.gui.node; - -import javax.swing.tree.DefaultMutableTreeNode; - -public class ClassSelectorPackageNode extends DefaultMutableTreeNode { - - private String packageName; - - public ClassSelectorPackageNode(String packageName) { - this.packageName = packageName != null ? packageName : "(none)"; - } - - public String getPackageName() { - return packageName; - } - - @Override - public Object getUserObject() { - return packageName; - } - - @Override - public void setUserObject(Object userObject) { - if (userObject instanceof String) - this.packageName = (String) userObject; - super.setUserObject(userObject); - } - - @Override - public String toString() { - return !packageName.equals("(none)") ? this.packageName : "(none)"; - } - - @Override - public boolean equals(Object other) { - return other instanceof ClassSelectorPackageNode && equals((ClassSelectorPackageNode) other); - } - - @Override - public int hashCode() { - return packageName.hashCode(); - } - - public boolean equals(ClassSelectorPackageNode other) { - return other != null && this.packageName.equals(other.packageName); - } -} diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java b/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java deleted file mode 100644 index c24226b3..00000000 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java +++ /dev/null @@ -1,26 +0,0 @@ -package cuchaz.enigma.gui.panels; - -import cuchaz.enigma.gui.ClassSelector; -import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.utils.I18n; - -import javax.swing.*; -import java.awt.*; - -public class PanelDeobf extends JPanel { - - public final ClassSelector deobfClasses; - private final Gui gui; - - public PanelDeobf(Gui gui) { - this.gui = gui; - - this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true); - this.deobfClasses.setSelectionListener(gui.getController()::navigateTo); - this.deobfClasses.setRenameSelectionListener(gui::onPanelRename); - - this.setLayout(new BorderLayout()); - this.add(new JLabel(I18n.translate("info_panel.classes.deobfuscated")), BorderLayout.NORTH); - this.add(new JScrollPane(this.deobfClasses), BorderLayout.CENTER); - } -} diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java b/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java deleted file mode 100644 index 8637afd9..00000000 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java +++ /dev/null @@ -1,171 +0,0 @@ -package cuchaz.enigma.gui.panels; - -import cuchaz.enigma.EnigmaProject; -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.config.Config; -import cuchaz.enigma.gui.BrowserCaret; -import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.gui.util.ScaleUtil; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; - -public class PanelEditor extends JEditorPane { - private boolean mouseIsPressed = false; - public int fontSize = 12; - - public PanelEditor(Gui gui) { - this.setEditable(false); - this.setSelectionColor(new Color(31, 46, 90)); - this.setCaret(new BrowserCaret()); - this.setFont(ScaleUtil.getFont(this.getFont().getFontName(), Font.PLAIN, this.fontSize)); - this.addCaretListener(event -> gui.onCaretMove(event.getDot(), mouseIsPressed)); - final PanelEditor self = this; - this.addMouseListener(new MouseAdapter() { - @Override - public void mousePressed(MouseEvent mouseEvent) { - mouseIsPressed = true; - } - - @Override - public void mouseReleased(MouseEvent e) { - switch (e.getButton()) { - case MouseEvent.BUTTON3: // Right click - self.setCaretPosition(self.viewToModel(e.getPoint())); - break; - - case 4: // Back navigation - gui.getController().openPreviousReference(); - break; - - case 5: // Forward navigation - gui.getController().openNextReference(); - break; - } - mouseIsPressed = false; - } - }); - this.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent event) { - if (event.isControlDown()) { - gui.setShouldNavigateOnClick(false); - switch (event.getKeyCode()) { - case KeyEvent.VK_I: - gui.popupMenu.showInheritanceMenu.doClick(); - break; - - case KeyEvent.VK_M: - gui.popupMenu.showImplementationsMenu.doClick(); - break; - - case KeyEvent.VK_N: - gui.popupMenu.openEntryMenu.doClick(); - break; - - case KeyEvent.VK_P: - gui.popupMenu.openPreviousMenu.doClick(); - break; - - case KeyEvent.VK_E: - gui.popupMenu.openNextMenu.doClick(); - break; - - case KeyEvent.VK_C: - if (event.isShiftDown()) { - gui.popupMenu.showCallsSpecificMenu.doClick(); - } else { - gui.popupMenu.showCallsMenu.doClick(); - } - break; - - case KeyEvent.VK_O: - gui.popupMenu.toggleMappingMenu.doClick(); - break; - - case KeyEvent.VK_R: - gui.popupMenu.renameMenu.doClick(); - break; - - case KeyEvent.VK_D: - gui.popupMenu.editJavadocMenu.doClick(); - break; - - case KeyEvent.VK_F5: - gui.getController().refreshCurrentClass(); - break; - - case KeyEvent.VK_F: - // prevent navigating on click when quick find activated - break; - - case KeyEvent.VK_ADD: - case KeyEvent.VK_EQUALS: - case KeyEvent.VK_PLUS: - self.offsetEditorZoom(2); - break; - case KeyEvent.VK_SUBTRACT: - case KeyEvent.VK_MINUS: - self.offsetEditorZoom(-2); - break; - - default: - gui.setShouldNavigateOnClick(true); // CTRL - break; - } - } - } - - @Override - public void keyTyped(KeyEvent event) { - if (!gui.popupMenu.renameMenu.isEnabled()) return; - - if (!event.isControlDown() && !event.isAltDown() && Character.isJavaIdentifierPart(event.getKeyChar())) { - EnigmaProject project = gui.getController().project; - EntryReference, Entry> reference = project.getMapper().deobfuscate(gui.cursorReference); - Entry entry = reference.getNameableEntry(); - - String name = String.valueOf(event.getKeyChar()); - if (entry instanceof ClassEntry && ((ClassEntry) entry).getParent() == null) { - String packageName = ((ClassEntry) entry).getPackageName(); - if (packageName != null) { - name = packageName + "/" + name; - } - } - - gui.popupMenu.renameMenu.doClick(); - gui.renameTextField.setText(name); - } - } - - @Override - public void keyReleased(KeyEvent event) { - gui.setShouldNavigateOnClick(event.isControlDown()); - } - }); - } - - public void offsetEditorZoom(int zoomAmount) { - int newResult = this.fontSize + zoomAmount; - if (newResult > 8 && newResult < 72) { - this.fontSize = newResult; - this.setFont(ScaleUtil.getFont(this.getFont().getFontName(), Font.PLAIN, this.fontSize)); - } - } - - public void resetEditorZoom() { - this.fontSize = 12; - this.setFont(ScaleUtil.getFont(this.getFont().getFontName(), Font.PLAIN, this.fontSize)); - } - - @Override - public Color getCaretColor() { - return new Color(Config.getInstance().caretColor); - } -} diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java b/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java deleted file mode 100644 index de069bc6..00000000 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java +++ /dev/null @@ -1,32 +0,0 @@ -package cuchaz.enigma.gui.panels; - -import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.utils.I18n; -import cuchaz.enigma.gui.util.ScaleUtil; -import cuchaz.enigma.utils.Utils; - -import javax.swing.*; -import java.awt.*; - -public class PanelIdentifier extends JPanel { - - private final Gui gui; - - public PanelIdentifier(Gui gui) { - this.gui = gui; - - this.setLayout(new GridLayout(4, 1, 0, 0)); - this.setPreferredSize(ScaleUtil.getDimension(0, 100)); - this.setBorder(BorderFactory.createTitledBorder(I18n.translate("info_panel.identifier"))); - } - - public void clearReference() { - this.removeAll(); - JLabel label = new JLabel(I18n.translate("info_panel.identifier.none")); - Utils.unboldLabel(label); - label.setHorizontalAlignment(JLabel.CENTER); - this.add(label); - - gui.redraw(); - } -} diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java b/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java deleted file mode 100644 index dd7f9f97..00000000 --- a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java +++ /dev/null @@ -1,37 +0,0 @@ -package cuchaz.enigma.gui.panels; - -import cuchaz.enigma.gui.ClassSelector; -import cuchaz.enigma.gui.Gui; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.utils.I18n; - -import javax.swing.*; -import java.awt.*; -import java.util.Comparator; - -public class PanelObf extends JPanel { - - public final ClassSelector obfClasses; - private final Gui gui; - - public PanelObf(Gui gui) { - this.gui = gui; - - Comparator obfClassComparator = (a, b) -> { - String aname = a.getFullName(); - String bname = b.getFullName(); - if (aname.length() != bname.length()) { - return aname.length() - bname.length(); - } - return aname.compareTo(bname); - }; - - this.obfClasses = new ClassSelector(gui, obfClassComparator, false); - this.obfClasses.setSelectionListener(gui.getController()::navigateTo); - this.obfClasses.setRenameSelectionListener(gui::onPanelRename); - - this.setLayout(new BorderLayout()); - this.add(new JLabel(I18n.translate("info_panel.classes.obfuscated")), BorderLayout.NORTH); - this.add(new JScrollPane(this.obfClasses), BorderLayout.CENTER); - } -} diff --git a/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java b/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java deleted file mode 100644 index e7835304..00000000 --- a/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java +++ /dev/null @@ -1,197 +0,0 @@ -package cuchaz.enigma.gui.stats; - -import com.google.gson.GsonBuilder; -import cuchaz.enigma.EnigmaProject; -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.analysis.index.EntryIndex; -import cuchaz.enigma.api.service.NameProposalService; -import cuchaz.enigma.api.service.ObfuscationTestService; -import cuchaz.enigma.translation.mapping.EntryRemapper; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.mapping.ResolutionStrategy; -import cuchaz.enigma.translation.representation.TypeDescriptor; -import cuchaz.enigma.translation.representation.entry.*; -import cuchaz.enigma.utils.I18n; - -import java.util.*; - -public class StatsGenerator { - private final EntryIndex entryIndex; - private final EntryRemapper mapper; - private final EntryResolver entryResolver; - private final List obfuscationTestServices; - private final List nameProposalServices; - - public StatsGenerator(EnigmaProject project) { - entryIndex = project.getJarIndex().getEntryIndex(); - mapper = project.getMapper(); - entryResolver = project.getJarIndex().getEntryResolver(); - obfuscationTestServices = project.getEnigma().getServices().get(ObfuscationTestService.TYPE); - nameProposalServices = project.getEnigma().getServices().get(NameProposalService.TYPE); - } - - public String generate(ProgressListener progress, Set includedMembers) { - includedMembers = EnumSet.copyOf(includedMembers); - int totalWork = 0; - - if (includedMembers.contains(StatsMember.METHODS) || includedMembers.contains(StatsMember.PARAMETERS)) { - totalWork += entryIndex.getMethods().size(); - } - - if (includedMembers.contains(StatsMember.FIELDS)) { - totalWork += entryIndex.getFields().size(); - } - - if (includedMembers.contains(StatsMember.CLASSES)) { - totalWork += entryIndex.getClasses().size(); - } - - progress.init(totalWork, I18n.translate("progress.stats")); - - Map counts = new HashMap<>(); - - int numDone = 0; - if (includedMembers.contains(StatsMember.METHODS) || includedMembers.contains(StatsMember.PARAMETERS)) { - for (MethodEntry method : entryIndex.getMethods()) { - progress.step(numDone++, I18n.translate("type.methods")); - MethodEntry root = entryResolver - .resolveEntry(method, ResolutionStrategy.RESOLVE_ROOT) - .stream() - .findFirst() - .orElseThrow(AssertionError::new); - - if (root == method && !((MethodDefEntry) method).getAccess().isSynthetic()) { - if (includedMembers.contains(StatsMember.METHODS)) { - update(counts, method); - } - - if (includedMembers.contains(StatsMember.PARAMETERS)) { - int index = ((MethodDefEntry) method).getAccess().isStatic() ? 0 : 1; - for (TypeDescriptor argument : method.getDesc().getArgumentDescs()) { - update(counts, new LocalVariableEntry(method, index, "", true,null)); - index += argument.getSize(); - } - } - } - } - } - - if (includedMembers.contains(StatsMember.FIELDS)) { - for (FieldEntry field : entryIndex.getFields()) { - progress.step(numDone++, I18n.translate("type.fields")); - update(counts, field); - } - } - - if (includedMembers.contains(StatsMember.CLASSES)) { - for (ClassEntry clazz : entryIndex.getClasses()) { - progress.step(numDone++, I18n.translate("type.classes")); - update(counts, clazz); - } - } - - progress.step(-1, I18n.translate("progress.stats.data")); - - Tree tree = new Tree<>(); - - for (Map.Entry entry : counts.entrySet()) { - if (entry.getKey().startsWith("com.mojang")) continue; // just a few unmapped names, no point in having a subsection - tree.getNode(entry.getKey()).value = entry.getValue(); - } - - tree.collapse(tree.root); - return new GsonBuilder().setPrettyPrinting().create().toJson(tree.root); - } - - private void update(Map counts, Entry entry) { - if (isObfuscated(entry)) { - String parent = mapper.deobfuscate(entry.getAncestry().get(0)).getName().replace('/', '.'); - counts.put(parent, counts.getOrDefault(parent, 0) + 1); - } - } - - private boolean isObfuscated(Entry entry) { - String name = entry.getName(); - - if (!obfuscationTestServices.isEmpty()) { - for (ObfuscationTestService service : obfuscationTestServices) { - if (service.testDeobfuscated(entry)) { - return false; - } - } - } - - if (!nameProposalServices.isEmpty()) { - for (NameProposalService service : nameProposalServices) { - if (service.proposeName(entry, mapper).isPresent()) { - return false; - } - } - } - - String mappedName = mapper.deobfuscate(entry).getName(); - if (mappedName != null && !mappedName.isEmpty() && !mappedName.equals(name)) { - return false; - } - - return true; - } - - private static class Tree { - public final Node root; - private final Map> nodes = new HashMap<>(); - - public static class Node { - public String name; - public T value; - public List> children = new ArrayList<>(); - private final transient Map> namedChildren = new HashMap<>(); - - public Node(String name, T value) { - this.name = name; - this.value = value; - } - } - - public Tree() { - root = new Node<>("", null); - } - - public Node getNode(String name) { - Node node = nodes.get(name); - - if (node == null) { - node = root; - - for (String part : name.split("\\.")) { - Node child = node.namedChildren.get(part); - - if (child == null) { - child = new Node<>(part, null); - node.namedChildren.put(part, child); - node.children.add(child); - } - - node = child; - } - - nodes.put(name, node); - } - - return node; - } - - public void collapse(Node node) { - while (node.children.size() == 1) { - Node child = node.children.get(0); - node.name = node.name.isEmpty() ? child.name : node.name + "." + child.name; - node.children = child.children; - node.value = child.value; - } - - for (Node child : node.children) { - collapse(child); - } - } - } -} diff --git a/src/main/java/cuchaz/enigma/gui/stats/StatsMember.java b/src/main/java/cuchaz/enigma/gui/stats/StatsMember.java deleted file mode 100644 index 70b4f40d..00000000 --- a/src/main/java/cuchaz/enigma/gui/stats/StatsMember.java +++ /dev/null @@ -1,8 +0,0 @@ -package cuchaz.enigma.gui.stats; - -public enum StatsMember { - METHODS, - FIELDS, - PARAMETERS, - CLASSES -} diff --git a/src/main/java/cuchaz/enigma/gui/util/AbstractListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/util/AbstractListCellRenderer.java deleted file mode 100644 index 612e3e92..00000000 --- a/src/main/java/cuchaz/enigma/gui/util/AbstractListCellRenderer.java +++ /dev/null @@ -1,77 +0,0 @@ -package cuchaz.enigma.gui.util; - -import java.awt.Component; -import java.awt.event.MouseEvent; - -import javax.swing.*; -import javax.swing.border.Border; - -public abstract class AbstractListCellRenderer extends JPanel implements ListCellRenderer { - - private static final Border NO_FOCUS_BORDER = BorderFactory.createEmptyBorder(1, 1, 1, 1); - - private Border noFocusBorder; - - public AbstractListCellRenderer() { - setBorder(getNoFocusBorder()); - } - - protected Border getNoFocusBorder() { - if (noFocusBorder == null) { - Border border = UIManager.getLookAndFeel().getDefaults().getBorder("List.List.cellNoFocusBorder"); - noFocusBorder = border != null ? border : NO_FOCUS_BORDER; - } - return noFocusBorder; - } - - protected Border getBorder(boolean isSelected, boolean cellHasFocus) { - Border b = null; - if (cellHasFocus) { - UIDefaults defaults = UIManager.getLookAndFeel().getDefaults(); - if (isSelected) { - b = defaults.getBorder("List.focusSelectedCellHighlightBorder"); - } - if (b == null) { - b = defaults.getBorder("List.focusCellHighlightBorder"); - } - } else { - b = getNoFocusBorder(); - } - return b; - } - - public abstract void updateUiForEntry(JList list, E value, int index, boolean isSelected, boolean cellHasFocus); - - @Override - public Component getListCellRendererComponent(JList list, E value, int index, boolean isSelected, boolean cellHasFocus) { - updateUiForEntry(list, value, index, isSelected, cellHasFocus); - - if (isSelected) { - setBackground(list.getSelectionBackground()); - setForeground(list.getSelectionForeground()); - } else { - setBackground(list.getBackground()); - setForeground(list.getForeground()); - } - - setEnabled(list.isEnabled()); - setFont(list.getFont()); - - setBorder(getBorder(isSelected, cellHasFocus)); - - // This isn't the width of the cell, but it's close enough for where it's needed (getComponentAt in getToolTipText) - setSize(list.getWidth(), getPreferredSize().height); - - return this; - } - - @Override - public String getToolTipText(MouseEvent event) { - Component c = getComponentAt(event.getPoint()); - if (c instanceof JComponent) { - return ((JComponent) c).getToolTipText(); - } - return getToolTipText(); - } - -} diff --git a/src/main/java/cuchaz/enigma/gui/util/History.java b/src/main/java/cuchaz/enigma/gui/util/History.java deleted file mode 100644 index 94f31051..00000000 --- a/src/main/java/cuchaz/enigma/gui/util/History.java +++ /dev/null @@ -1,49 +0,0 @@ -package cuchaz.enigma.gui.util; - -import com.google.common.collect.Queues; - -import java.util.Deque; - -public class History { - private final Deque previous = Queues.newArrayDeque(); - private final Deque next = Queues.newArrayDeque(); - private T current; - - public History(T initial) { - current = initial; - } - - public T getCurrent() { - return current; - } - - public void push(T value) { - previous.addLast(current); - current = value; - next.clear(); - } - - public void replace(T value) { - current = value; - } - - public boolean canGoBack() { - return !previous.isEmpty(); - } - - public T goBack() { - next.addFirst(current); - current = previous.removeLast(); - return current; - } - - public boolean canGoForward() { - return !next.isEmpty(); - } - - public T goForward() { - previous.addLast(current); - current = next.removeFirst(); - return current; - } -} diff --git a/src/main/java/cuchaz/enigma/gui/util/ScaleChangeListener.java b/src/main/java/cuchaz/enigma/gui/util/ScaleChangeListener.java deleted file mode 100644 index d045c6d5..00000000 --- a/src/main/java/cuchaz/enigma/gui/util/ScaleChangeListener.java +++ /dev/null @@ -1,8 +0,0 @@ -package cuchaz.enigma.gui.util; - -@FunctionalInterface -public interface ScaleChangeListener { - - void onScaleChanged(float scale, float oldScale); - -} diff --git a/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java b/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java deleted file mode 100644 index 9f722e9f..00000000 --- a/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java +++ /dev/null @@ -1,110 +0,0 @@ -package cuchaz.enigma.gui.util; - -import java.awt.Dimension; -import java.awt.Font; -import java.io.IOException; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.BorderFactory; -import javax.swing.UIManager; -import javax.swing.border.Border; - -import com.github.swingdpi.UiDefaultsScaler; -import com.github.swingdpi.plaf.BasicTweaker; -import com.github.swingdpi.plaf.MetalTweaker; -import com.github.swingdpi.plaf.NimbusTweaker; -import com.github.swingdpi.plaf.WindowsTweaker; -import cuchaz.enigma.config.Config; -import de.sciss.syntaxpane.DefaultSyntaxKit; - -public class ScaleUtil { - - private static List listeners = new ArrayList<>(); - - public static float getScaleFactor() { - return Config.getInstance().scaleFactor; - } - - public static void setScaleFactor(float scaleFactor) { - float oldScale = getScaleFactor(); - float clamped = Math.min(Math.max(0.25f, scaleFactor), 10.0f); - Config.getInstance().scaleFactor = clamped; - try { - Config.getInstance().saveConfig(); - } catch (IOException e) { - e.printStackTrace(); - } - listeners.forEach(l -> l.onScaleChanged(clamped, oldScale)); - } - - public static void addListener(ScaleChangeListener listener) { - listeners.add(listener); - } - - public static void removeListener(ScaleChangeListener listener) { - listeners.remove(listener); - } - - public static Dimension getDimension(int width, int height) { - return new Dimension(scale(width), scale(height)); - } - - public static Font getFont(String fontName, int plain, int fontSize) { - return scaleFont(new Font(fontName, plain, fontSize)); - } - - public static Font scaleFont(Font font) { - return createTweakerForCurrentLook(getScaleFactor()).modifyFont("", font); - } - - public static float scale(float f) { - return f * getScaleFactor(); - } - - public static float invert(float f) { - return f / getScaleFactor(); - } - - public static int scale(int i) { - return (int) (i * getScaleFactor()); - } - - public static Border createEmptyBorder(int top, int left, int bottom, int right) { - return BorderFactory.createEmptyBorder(scale(top), scale(left), scale(bottom), scale(right)); - } - - public static int invert(int i) { - return (int) (i / getScaleFactor()); - } - - public static void applyScaling() { - float scale = getScaleFactor(); - UiDefaultsScaler.updateAndApplyGlobalScaling((int) (100 * scale), true); - try { - Field defaultFontField = DefaultSyntaxKit.class.getDeclaredField("DEFAULT_FONT"); - defaultFontField.setAccessible(true); - Font font = (Font) defaultFontField.get(null); - font = font.deriveFont(12 * scale); - defaultFontField.set(null, font); - } catch (NoSuchFieldException | IllegalAccessException e) { - e.printStackTrace(); - } - } - - private static BasicTweaker createTweakerForCurrentLook(float dpiScaling) { - String testString = UIManager.getLookAndFeel().getName().toLowerCase(); - if (testString.contains("windows")) { - return new WindowsTweaker(dpiScaling, testString.contains("classic")); - } - if (testString.contains("metal")) { - return new MetalTweaker(dpiScaling); - } - if (testString.contains("nimbus")) { - return new NimbusTweaker(dpiScaling); - } - return new BasicTweaker(dpiScaling); - } - -} diff --git a/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java b/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java deleted file mode 100644 index 2cfe8233..00000000 --- a/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java +++ /dev/null @@ -1,164 +0,0 @@ -package cuchaz.enigma.network; - -import com.google.common.io.MoreFiles; -import cuchaz.enigma.*; -import cuchaz.enigma.throwables.MappingParseException; -import cuchaz.enigma.translation.mapping.EntryRemapper; -import cuchaz.enigma.translation.mapping.serde.MappingFormat; -import cuchaz.enigma.utils.Utils; -import joptsimple.OptionParser; -import joptsimple.OptionSet; -import joptsimple.OptionSpec; - -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.TimeUnit; - -public class DedicatedEnigmaServer extends EnigmaServer { - - private final EnigmaProfile profile; - private final MappingFormat mappingFormat; - private final Path mappingsFile; - private final PrintWriter log; - private BlockingQueue tasks = new LinkedBlockingDeque<>(); - - public DedicatedEnigmaServer( - byte[] jarChecksum, - char[] password, - EnigmaProfile profile, - MappingFormat mappingFormat, - Path mappingsFile, - PrintWriter log, - EntryRemapper mappings, - int port - ) { - super(jarChecksum, password, mappings, port); - this.profile = profile; - this.mappingFormat = mappingFormat; - this.mappingsFile = mappingsFile; - this.log = log; - } - - @Override - protected void runOnThread(Runnable task) { - tasks.add(task); - } - - @Override - public void log(String message) { - super.log(message); - log.println(message); - } - - public static void main(String[] args) { - OptionParser parser = new OptionParser(); - - OptionSpec jarOpt = parser.accepts("jar", "Jar file to open at startup") - .withRequiredArg() - .required() - .withValuesConvertedBy(Main.PathConverter.INSTANCE); - - OptionSpec mappingsOpt = parser.accepts("mappings", "Mappings file to open at startup") - .withRequiredArg() - .required() - .withValuesConvertedBy(Main.PathConverter.INSTANCE); - - OptionSpec profileOpt = parser.accepts("profile", "Profile json to apply at startup") - .withRequiredArg() - .withValuesConvertedBy(Main.PathConverter.INSTANCE); - - OptionSpec portOpt = parser.accepts("port", "Port to run the server on") - .withOptionalArg() - .ofType(Integer.class) - .defaultsTo(EnigmaServer.DEFAULT_PORT); - - OptionSpec passwordOpt = parser.accepts("password", "The password to join the server") - .withRequiredArg() - .defaultsTo(""); - - OptionSpec logFileOpt = parser.accepts("log", "The log file to write to") - .withRequiredArg() - .withValuesConvertedBy(Main.PathConverter.INSTANCE) - .defaultsTo(Paths.get("log.txt")); - - OptionSet parsedArgs = parser.parse(args); - Path jar = parsedArgs.valueOf(jarOpt); - Path mappingsFile = parsedArgs.valueOf(mappingsOpt); - Path profileFile = parsedArgs.valueOf(profileOpt); - int port = parsedArgs.valueOf(portOpt); - char[] password = parsedArgs.valueOf(passwordOpt).toCharArray(); - if (password.length > EnigmaServer.MAX_PASSWORD_LENGTH) { - System.err.println("Password too long, must be at most " + EnigmaServer.MAX_PASSWORD_LENGTH + " characters"); - System.exit(1); - } - Path logFile = parsedArgs.valueOf(logFileOpt); - - System.out.println("Starting Enigma server"); - DedicatedEnigmaServer server; - try { - byte[] checksum = Utils.zipSha1(parsedArgs.valueOf(jarOpt)); - - EnigmaProfile profile = EnigmaProfile.read(profileFile); - Enigma enigma = Enigma.builder().setProfile(profile).build(); - System.out.println("Indexing Jar..."); - EnigmaProject project = enigma.openJar(jar, ProgressListener.none()); - - MappingFormat mappingFormat = MappingFormat.ENIGMA_DIRECTORY; - EntryRemapper mappings; - if (!Files.exists(mappingsFile)) { - mappings = EntryRemapper.empty(project.getJarIndex()); - } else { - System.out.println("Reading mappings..."); - if (Files.isDirectory(mappingsFile)) { - mappingFormat = MappingFormat.ENIGMA_DIRECTORY; - } else if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(mappingsFile))) { - mappingFormat = MappingFormat.ENIGMA_ZIP; - } else { - mappingFormat = MappingFormat.ENIGMA_FILE; - } - mappings = EntryRemapper.mapped(project.getJarIndex(), mappingFormat.read(mappingsFile, ProgressListener.none(), profile.getMappingSaveParameters())); - } - - PrintWriter log = new PrintWriter(Files.newBufferedWriter(logFile)); - - server = new DedicatedEnigmaServer(checksum, password, profile, mappingFormat, mappingsFile, log, mappings, port); - server.start(); - System.out.println("Server started"); - } catch (IOException | MappingParseException e) { - System.err.println("Error starting server!"); - e.printStackTrace(); - System.exit(1); - return; - } - - // noinspection RedundantSuppression - // noinspection Convert2MethodRef - javac 8 bug - Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> server.runOnThread(() -> server.saveMappings()), 0, 1, TimeUnit.MINUTES); - Runtime.getRuntime().addShutdownHook(new Thread(server::saveMappings)); - - while (true) { - try { - server.tasks.take().run(); - } catch (InterruptedException e) { - break; - } - } - } - - @Override - public synchronized void stop() { - super.stop(); - System.exit(0); - } - - private void saveMappings() { - mappingFormat.write(getMappings().getObfToDeobf(), getMappings().takeMappingDelta(), mappingsFile, ProgressListener.none(), profile.getMappingSaveParameters()); - log.flush(); - } -} diff --git a/src/main/java/cuchaz/enigma/network/EnigmaClient.java b/src/main/java/cuchaz/enigma/network/EnigmaClient.java deleted file mode 100644 index bfa53d73..00000000 --- a/src/main/java/cuchaz/enigma/network/EnigmaClient.java +++ /dev/null @@ -1,85 +0,0 @@ -package cuchaz.enigma.network; - -import cuchaz.enigma.gui.GuiController; -import cuchaz.enigma.network.packet.LoginC2SPacket; -import cuchaz.enigma.network.packet.Packet; -import cuchaz.enigma.network.packet.PacketRegistry; - -import javax.swing.SwingUtilities; -import java.io.DataInput; -import java.io.DataInputStream; -import java.io.DataOutput; -import java.io.DataOutputStream; -import java.io.EOFException; -import java.io.IOException; -import java.net.Socket; -import java.net.SocketException; - -public class EnigmaClient { - - private final GuiController controller; - - private final String ip; - private final int port; - private Socket socket; - private DataOutput output; - - public EnigmaClient(GuiController controller, String ip, int port) { - this.controller = controller; - this.ip = ip; - this.port = port; - } - - public void connect() throws IOException { - socket = new Socket(ip, port); - output = new DataOutputStream(socket.getOutputStream()); - Thread thread = new Thread(() -> { - try { - DataInput input = new DataInputStream(socket.getInputStream()); - while (true) { - int packetId; - try { - packetId = input.readUnsignedByte(); - } catch (EOFException | SocketException e) { - break; - } - Packet packet = PacketRegistry.createS2CPacket(packetId); - if (packet == null) { - throw new IOException("Received invalid packet id " + packetId); - } - packet.read(input); - SwingUtilities.invokeLater(() -> packet.handle(controller)); - } - } catch (IOException e) { - controller.disconnectIfConnected(e.toString()); - return; - } - controller.disconnectIfConnected("Disconnected"); - }); - thread.setName("Client I/O thread"); - thread.setDaemon(true); - thread.start(); - } - - public synchronized void disconnect() { - if (socket != null && !socket.isClosed()) { - try { - socket.close(); - } catch (IOException e1) { - System.err.println("Failed to close socket"); - e1.printStackTrace(); - } - } - } - - - public void sendPacket(Packet packet) { - try { - output.writeByte(PacketRegistry.getC2SId(packet)); - packet.write(output); - } catch (IOException e) { - controller.disconnectIfConnected(e.toString()); - } - } - -} diff --git a/src/main/java/cuchaz/enigma/network/EnigmaServer.java b/src/main/java/cuchaz/enigma/network/EnigmaServer.java deleted file mode 100644 index b0e15a3c..00000000 --- a/src/main/java/cuchaz/enigma/network/EnigmaServer.java +++ /dev/null @@ -1,292 +0,0 @@ -package cuchaz.enigma.network; - -import cuchaz.enigma.gui.GuiController; -import cuchaz.enigma.network.packet.KickS2CPacket; -import cuchaz.enigma.network.packet.MessageS2CPacket; -import cuchaz.enigma.network.packet.Packet; -import cuchaz.enigma.network.packet.PacketRegistry; -import cuchaz.enigma.network.packet.RemoveMappingS2CPacket; -import cuchaz.enigma.network.packet.RenameS2CPacket; -import cuchaz.enigma.network.packet.UserListS2CPacket; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.EntryRemapper; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.utils.Message; - -import java.io.DataInput; -import java.io.DataInputStream; -import java.io.DataOutput; -import java.io.DataOutputStream; -import java.io.EOFException; -import java.io.IOException; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArrayList; - -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 String OWNER_USERNAME = "Owner"; - 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 - - private final int port; - private ServerSocket socket; - private List clients = new CopyOnWriteArrayList<>(); - private Map usernames = new HashMap<>(); - private Set unapprovedClients = new HashSet<>(); - - private final byte[] jarChecksum; - private final char[] password; - - public static final int DUMMY_SYNC_ID = 0; - private final EntryRemapper mappings; - private Map, Integer> syncIds = new HashMap<>(); - private Map> inverseSyncIds = new HashMap<>(); - private Map> clientsNeedingConfirmation = new HashMap<>(); - private int nextSyncId = DUMMY_SYNC_ID + 1; - - private static int nextIoId = 0; - - public EnigmaServer(byte[] jarChecksum, char[] password, EntryRemapper mappings, int port) { - this.jarChecksum = jarChecksum; - this.password = password; - this.mappings = mappings; - this.port = port; - } - - public void start() throws IOException { - socket = new ServerSocket(port); - log("Server started on " + socket.getInetAddress() + ":" + port); - Thread thread = new Thread(() -> { - try { - while (!socket.isClosed()) { - acceptClient(); - } - } catch (SocketException e) { - System.out.println("Server closed"); - } catch (IOException e) { - e.printStackTrace(); - } - }); - thread.setName("Server client listener"); - thread.setDaemon(true); - thread.start(); - } - - private void acceptClient() throws IOException { - Socket client = socket.accept(); - clients.add(client); - Thread thread = new Thread(() -> { - try { - DataInput input = new DataInputStream(client.getInputStream()); - while (true) { - int packetId; - try { - packetId = input.readUnsignedByte(); - } catch (EOFException | SocketException e) { - break; - } - Packet packet = PacketRegistry.createC2SPacket(packetId); - if (packet == null) { - throw new IOException("Received invalid packet id " + packetId); - } - packet.read(input); - runOnThread(() -> packet.handle(new ServerPacketHandler(client, this))); - } - } catch (IOException e) { - kick(client, e.toString()); - e.printStackTrace(); - return; - } - kick(client, "disconnect.disconnected"); - }); - thread.setName("Server I/O thread #" + (nextIoId++)); - thread.setDaemon(true); - thread.start(); - } - - public void stop() { - runOnThread(() -> { - if (socket != null && !socket.isClosed()) { - for (Socket client : clients) { - kick(client, "disconnect.server_closed"); - } - try { - socket.close(); - } catch (IOException e) { - System.err.println("Failed to close server socket"); - e.printStackTrace(); - } - } - }); - } - - public void kick(Socket client, String reason) { - if (!clients.remove(client)) return; - - sendPacket(client, new KickS2CPacket(reason)); - - clientsNeedingConfirmation.values().removeIf(list -> { - list.remove(client); - return list.isEmpty(); - }); - String username = usernames.remove(client); - try { - client.close(); - } catch (IOException e) { - System.err.println("Failed to close server client socket"); - e.printStackTrace(); - } - - if (username != null) { - System.out.println("Kicked " + username + " because " + reason); - sendMessage(Message.disconnect(username)); - } - sendUsernamePacket(); - } - - public boolean isUsernameTaken(String username) { - return usernames.containsValue(username); - } - - public void setUsername(Socket client, String username) { - usernames.put(client, username); - sendUsernamePacket(); - } - - private void sendUsernamePacket() { - List usernames = new ArrayList<>(this.usernames.values()); - Collections.sort(usernames); - sendToAll(new UserListS2CPacket(usernames)); - } - - public String getUsername(Socket client) { - return usernames.get(client); - } - - public void sendPacket(Socket client, Packet packet) { - if (!client.isClosed()) { - int packetId = PacketRegistry.getS2CId(packet); - try { - DataOutput output = new DataOutputStream(client.getOutputStream()); - output.writeByte(packetId); - packet.write(output); - } catch (IOException e) { - if (!(packet instanceof KickS2CPacket)) { - kick(client, e.toString()); - e.printStackTrace(); - } - } - } - } - - public void sendToAll(Packet packet) { - for (Socket client : clients) { - sendPacket(client, packet); - } - } - - public void sendToAllExcept(Socket excluded, Packet packet) { - for (Socket client : clients) { - if (client != excluded) { - sendPacket(client, packet); - } - } - } - - public boolean canModifyEntry(Socket client, Entry entry) { - if (unapprovedClients.contains(client)) { - return false; - } - - Integer syncId = syncIds.get(entry); - if (syncId == null) { - return true; - } - Set clients = clientsNeedingConfirmation.get(syncId); - return clients == null || !clients.contains(client); - } - - public int lockEntry(Socket exception, Entry entry) { - int syncId = nextSyncId; - nextSyncId++; - // sync id is sent as an unsigned short, can't have more than 65536 - if (nextSyncId == 65536) { - nextSyncId = DUMMY_SYNC_ID + 1; - } - Integer oldSyncId = syncIds.get(entry); - if (oldSyncId != null) { - clientsNeedingConfirmation.remove(oldSyncId); - } - syncIds.put(entry, syncId); - inverseSyncIds.put(syncId, entry); - Set clients = new HashSet<>(this.clients); - clients.remove(exception); - clientsNeedingConfirmation.put(syncId, clients); - return syncId; - } - - public void confirmChange(Socket client, int syncId) { - if (usernames.containsKey(client)) { - unapprovedClients.remove(client); - } - - Set clients = clientsNeedingConfirmation.get(syncId); - if (clients != null) { - clients.remove(client); - if (clients.isEmpty()) { - clientsNeedingConfirmation.remove(syncId); - syncIds.remove(inverseSyncIds.remove(syncId)); - } - } - } - - public void sendCorrectMapping(Socket client, Entry entry, boolean refreshClassTree) { - EntryMapping oldMapping = mappings.getDeobfMapping(entry); - String oldName = oldMapping == null ? null : oldMapping.getTargetName(); - if (oldName == null) { - sendPacket(client, new RemoveMappingS2CPacket(DUMMY_SYNC_ID, entry)); - } else { - sendPacket(client, new RenameS2CPacket(0, entry, oldName, refreshClassTree)); - } - } - - protected abstract void runOnThread(Runnable task); - - public void log(String message) { - System.out.println(message); - } - - protected boolean isRunning() { - return !socket.isClosed(); - } - - public byte[] getJarChecksum() { - return jarChecksum; - } - - public char[] getPassword() { - return password; - } - - public EntryRemapper getMappings() { - return mappings; - } - - public void sendMessage(Message message) { - log(String.format("[MSG] %s", message.translate())); - sendToAll(new MessageS2CPacket(message)); - } - -} diff --git a/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java b/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java deleted file mode 100644 index 21c6825b..00000000 --- a/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java +++ /dev/null @@ -1,16 +0,0 @@ -package cuchaz.enigma.network; - -import cuchaz.enigma.translation.mapping.EntryRemapper; - -import javax.swing.*; - -public class IntegratedEnigmaServer extends EnigmaServer { - public IntegratedEnigmaServer(byte[] jarChecksum, char[] password, EntryRemapper mappings, int port) { - super(jarChecksum, password, mappings, port); - } - - @Override - protected void runOnThread(Runnable task) { - SwingUtilities.invokeLater(task); - } -} diff --git a/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java b/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java deleted file mode 100644 index 86185536..00000000 --- a/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java +++ /dev/null @@ -1,22 +0,0 @@ -package cuchaz.enigma.network; - -import java.net.Socket; - -public class ServerPacketHandler { - - private final Socket client; - private final EnigmaServer server; - - public ServerPacketHandler(Socket client, EnigmaServer server) { - this.client = client; - this.server = server; - } - - public Socket getClient() { - return client; - } - - public EnigmaServer getServer() { - return server; - } -} diff --git a/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java deleted file mode 100644 index 4d5d86f3..00000000 --- a/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java +++ /dev/null @@ -1,59 +0,0 @@ -package cuchaz.enigma.network.packet; - -import cuchaz.enigma.network.EnigmaServer; -import cuchaz.enigma.network.ServerPacketHandler; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.utils.Message; -import cuchaz.enigma.utils.Utils; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -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) { - 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(entry, mapping.withDocs(Utils.isBlank(newDocs) ? null : newDocs)); - - 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/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java deleted file mode 100644 index bf5b7cb8..00000000 --- a/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java +++ /dev/null @@ -1,44 +0,0 @@ -package cuchaz.enigma.network.packet; - -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.gui.GuiController; -import cuchaz.enigma.translation.representation.entry.Entry; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -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(GuiController controller) { - controller.changeDocs(new EntryReference<>(entry, entry.getName()), newDocs, false); - controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); - } -} diff --git a/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java deleted file mode 100644 index 78ef9645..00000000 --- a/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java +++ /dev/null @@ -1,33 +0,0 @@ -package cuchaz.enigma.network.packet; - -import cuchaz.enigma.network.ServerPacketHandler; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -public class ConfirmChangeC2SPacket implements Packet { - private int syncId; - - ConfirmChangeC2SPacket() { - } - - public ConfirmChangeC2SPacket(int syncId) { - this.syncId = syncId; - } - - @Override - public void read(DataInput input) throws IOException { - this.syncId = input.readUnsignedShort(); - } - - @Override - public void write(DataOutput output) throws IOException { - output.writeShort(syncId); - } - - @Override - public void handle(ServerPacketHandler handler) { - handler.getServer().confirmChange(handler.getClient(), syncId); - } -} diff --git a/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java deleted file mode 100644 index bd007d31..00000000 --- a/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java +++ /dev/null @@ -1,33 +0,0 @@ -package cuchaz.enigma.network.packet; - -import cuchaz.enigma.gui.GuiController; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -public class KickS2CPacket implements Packet { - private String reason; - - KickS2CPacket() { - } - - public KickS2CPacket(String reason) { - this.reason = reason; - } - - @Override - public void read(DataInput input) throws IOException { - this.reason = PacketHelper.readString(input); - } - - @Override - public void write(DataOutput output) throws IOException { - PacketHelper.writeString(output, reason); - } - - @Override - public void handle(GuiController controller) { - controller.disconnectIfConnected(reason); - } -} diff --git a/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java deleted file mode 100644 index 722cbbf3..00000000 --- a/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java +++ /dev/null @@ -1,75 +0,0 @@ -package cuchaz.enigma.network.packet; - -import cuchaz.enigma.network.EnigmaServer; -import cuchaz.enigma.network.ServerPacketHandler; -import cuchaz.enigma.utils.Message; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.util.Arrays; - -public class LoginC2SPacket implements Packet { - private byte[] jarChecksum; - private char[] password; - private String username; - - LoginC2SPacket() { - } - - public LoginC2SPacket(byte[] jarChecksum, char[] password, String username) { - this.jarChecksum = jarChecksum; - this.password = password; - this.username = username; - } - - @Override - public void read(DataInput input) throws IOException { - if (input.readUnsignedShort() != EnigmaServer.PROTOCOL_VERSION) { - throw new IOException("Mismatching protocol"); - } - this.jarChecksum = new byte[EnigmaServer.CHECKSUM_SIZE]; - input.readFully(jarChecksum); - this.password = new char[input.readUnsignedByte()]; - for (int i = 0; i < password.length; i++) { - password[i] = input.readChar(); - } - this.username = PacketHelper.readString(input); - } - - @Override - public void write(DataOutput output) throws IOException { - output.writeShort(EnigmaServer.PROTOCOL_VERSION); - output.write(jarChecksum); - output.writeByte(password.length); - for (char c : password) { - output.writeChar(c); - } - PacketHelper.writeString(output, username); - } - - @Override - public void handle(ServerPacketHandler handler) { - boolean usernameTaken = handler.getServer().isUsernameTaken(username); - handler.getServer().setUsername(handler.getClient(), username); - handler.getServer().log(username + " logged in with IP " + handler.getClient().getInetAddress().toString() + ":" + handler.getClient().getPort()); - - if (!Arrays.equals(password, handler.getServer().getPassword())) { - handler.getServer().kick(handler.getClient(), "disconnect.wrong_password"); - return; - } - - if (usernameTaken) { - handler.getServer().kick(handler.getClient(), "disconnect.username_taken"); - return; - } - - if (!Arrays.equals(jarChecksum, handler.getServer().getJarChecksum())) { - handler.getServer().kick(handler.getClient(), "disconnect.wrong_jar"); - return; - } - - handler.getServer().sendPacket(handler.getClient(), new SyncMappingsS2CPacket(handler.getServer().getMappings().getObfToDeobf())); - handler.getServer().sendMessage(Message.connect(username)); - } -} diff --git a/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java deleted file mode 100644 index 98d20d96..00000000 --- a/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java +++ /dev/null @@ -1,48 +0,0 @@ -package cuchaz.enigma.network.packet; - -import cuchaz.enigma.network.ServerPacketHandler; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.utils.Message; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -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) { - boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); - if (!valid) { - handler.getServer().sendCorrectMapping(handler.getClient(), entry, true); - return; - } - - handler.getServer().getMappings().mapFromObf(entry, new EntryMapping(handler.getServer().getMappings().deobfuscate(entry).getName())); - 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/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java deleted file mode 100644 index b7d6eda3..00000000 --- a/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java +++ /dev/null @@ -1,40 +0,0 @@ -package cuchaz.enigma.network.packet; - -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.gui.GuiController; -import cuchaz.enigma.translation.representation.entry.Entry; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -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(GuiController controller) { - controller.markAsDeobfuscated(new EntryReference<>(entry, entry.getName()), false); - controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); - } -} diff --git a/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java deleted file mode 100644 index b8e0f14f..00000000 --- a/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java +++ /dev/null @@ -1,39 +0,0 @@ -package cuchaz.enigma.network.packet; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -import cuchaz.enigma.network.ServerPacketHandler; -import cuchaz.enigma.utils.Message; - -public class MessageC2SPacket implements Packet { - - private String message; - - MessageC2SPacket() { - } - - public MessageC2SPacket(String message) { - this.message = message; - } - - @Override - public void read(DataInput input) throws IOException { - message = PacketHelper.readString(input); - } - - @Override - public void write(DataOutput output) throws IOException { - PacketHelper.writeString(output, message); - } - - @Override - public void handle(ServerPacketHandler handler) { - String message = this.message.trim(); - if (!message.isEmpty()) { - handler.getServer().sendMessage(Message.chat(handler.getServer().getUsername(handler.getClient()), message)); - } - } - -} diff --git a/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java deleted file mode 100644 index edeaae0b..00000000 --- a/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java +++ /dev/null @@ -1,36 +0,0 @@ -package cuchaz.enigma.network.packet; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -import cuchaz.enigma.gui.GuiController; -import cuchaz.enigma.utils.Message; - -public class MessageS2CPacket implements Packet { - - private Message message; - - MessageS2CPacket() { - } - - public MessageS2CPacket(Message message) { - this.message = message; - } - - @Override - public void read(DataInput input) throws IOException { - message = Message.read(input); - } - - @Override - public void write(DataOutput output) throws IOException { - message.write(output); - } - - @Override - public void handle(GuiController handler) { - handler.addMessage(message); - } - -} diff --git a/src/main/java/cuchaz/enigma/network/packet/Packet.java b/src/main/java/cuchaz/enigma/network/packet/Packet.java deleted file mode 100644 index 2f16dfb9..00000000 --- a/src/main/java/cuchaz/enigma/network/packet/Packet.java +++ /dev/null @@ -1,15 +0,0 @@ -package cuchaz.enigma.network.packet; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -public interface Packet { - - void read(DataInput input) throws IOException; - - void write(DataOutput output) throws IOException; - - void handle(H handler); - -} diff --git a/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java b/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java deleted file mode 100644 index 464606e0..00000000 --- a/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java +++ /dev/null @@ -1,135 +0,0 @@ -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; - -public class PacketHelper { - - private static final int ENTRY_CLASS = 0, ENTRY_FIELD = 1, ENTRY_METHOD = 2, ENTRY_LOCAL_VAR = 3; - private static final int MAX_STRING_LENGTH = 65535; - - public static Entry readEntry(DataInput input) throws IOException { - return readEntry(input, null, true); - } - - public static Entry readEntry(DataInput input, Entry parent, boolean includeParent) throws IOException { - int type = input.readUnsignedByte(); - - if (includeParent && input.readBoolean()) { - parent = readEntry(input, null, true); - } - - String name = readString(input); - - String javadocs = null; - if (input.readBoolean()) { - javadocs = readString(input); - } - - switch (type) { - case ENTRY_CLASS: { - if (parent != null && !(parent instanceof ClassEntry)) { - throw new IOException("Class requires class parent"); - } - return new ClassEntry((ClassEntry) parent, name, javadocs); - } - case ENTRY_FIELD: { - if (!(parent instanceof ClassEntry)) { - throw new IOException("Field requires class parent"); - } - 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"); - } - 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"); - } - 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); - } - } - - public static void writeEntry(DataOutput output, Entry entry) throws IOException { - writeEntry(output, entry, true); - } - - public static void writeEntry(DataOutput output, Entry entry, boolean includeParent) throws IOException { - // type - if (entry instanceof ClassEntry) { - output.writeByte(ENTRY_CLASS); - } else if (entry instanceof FieldEntry) { - output.writeByte(ENTRY_FIELD); - } else if (entry instanceof MethodEntry) { - output.writeByte(ENTRY_METHOD); - } else if (entry instanceof LocalVariableEntry) { - output.writeByte(ENTRY_LOCAL_VAR); - } else { - throw new IOException("Don't know how to serialize entry of type " + entry.getClass().getSimpleName()); - } - - // parent - if (includeParent) { - output.writeBoolean(entry.getParent() != null); - if (entry.getParent() != null) { - writeEntry(output, entry.getParent(), true); - } - } - - // name - writeString(output, entry.getName()); - - // javadocs - output.writeBoolean(entry.getJavadocs() != null); - if (entry.getJavadocs() != null) { - writeString(output, entry.getJavadocs()); - } - - // type-specific stuff - if (entry instanceof FieldEntry) { - writeString(output, ((FieldEntry) entry).getDesc().toString()); - } else if (entry instanceof MethodEntry) { - writeString(output, ((MethodEntry) entry).getDesc().toString()); - } else if (entry instanceof LocalVariableEntry) { - LocalVariableEntry localVar = (LocalVariableEntry) entry; - output.writeShort(localVar.getIndex()); - output.writeBoolean(localVar.isArgument()); - } - } - - public static String readString(DataInput input) throws IOException { - int length = input.readUnsignedShort(); - byte[] bytes = new byte[length]; - input.readFully(bytes); - return new String(bytes, StandardCharsets.UTF_8); - } - - public static void writeString(DataOutput output, String str) throws IOException { - byte[] bytes = str.getBytes(StandardCharsets.UTF_8); - if (bytes.length > MAX_STRING_LENGTH) { - throw new IOException("String too long, was " + bytes.length + " bytes, max " + MAX_STRING_LENGTH + " allowed"); - } - output.writeShort(bytes.length); - output.write(bytes); - } - -} diff --git a/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java b/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java deleted file mode 100644 index ba5d9dec..00000000 --- a/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java +++ /dev/null @@ -1,64 +0,0 @@ -package cuchaz.enigma.network.packet; - -import cuchaz.enigma.gui.GuiController; -import cuchaz.enigma.network.ServerPacketHandler; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.Supplier; - -public class PacketRegistry { - - private static final Map>, Integer> c2sPacketIds = new HashMap<>(); - private static final Map>> c2sPacketCreators = new HashMap<>(); - private static final Map>, Integer> s2cPacketIds = new HashMap<>(); - private static final Map>> s2cPacketCreators = new HashMap<>(); - - private static > void registerC2S(int id, Class clazz, Supplier creator) { - c2sPacketIds.put(clazz, id); - c2sPacketCreators.put(id, creator); - } - - private static > void registerS2C(int id, Class clazz, Supplier creator) { - s2cPacketIds.put(clazz, id); - s2cPacketCreators.put(id, creator); - } - - 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); - - 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); - } - - public static int getC2SId(Packet packet) { - return c2sPacketIds.get(packet.getClass()); - } - - public static Packet createC2SPacket(int id) { - Supplier> creator = c2sPacketCreators.get(id); - return creator == null ? null : creator.get(); - } - - public static int getS2CId(Packet packet) { - return s2cPacketIds.get(packet.getClass()); - } - - public static Packet createS2CPacket(int id) { - Supplier> creator = s2cPacketCreators.get(id); - return creator == null ? null : creator.get(); - } - -} diff --git a/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java deleted file mode 100644 index a3f3d91d..00000000 --- a/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java +++ /dev/null @@ -1,55 +0,0 @@ -package cuchaz.enigma.network.packet; - -import cuchaz.enigma.network.ServerPacketHandler; -import cuchaz.enigma.throwables.IllegalNameException; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.utils.Message; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -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) { - boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); - - if (valid) { - try { - handler.getServer().getMappings().removeByObf(entry); - } catch (IllegalNameException e) { - valid = false; - } - } - - 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/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java deleted file mode 100644 index 7bb1b00d..00000000 --- a/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java +++ /dev/null @@ -1,40 +0,0 @@ -package cuchaz.enigma.network.packet; - -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.gui.GuiController; -import cuchaz.enigma.translation.representation.entry.Entry; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -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(GuiController controller) { - controller.removeMapping(new EntryReference<>(entry, entry.getName()), false); - controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); - } -} diff --git a/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java deleted file mode 100644 index 03e95d6f..00000000 --- a/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java +++ /dev/null @@ -1,64 +0,0 @@ -package cuchaz.enigma.network.packet; - -import cuchaz.enigma.network.ServerPacketHandler; -import cuchaz.enigma.throwables.IllegalNameException; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.utils.Message; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -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) { - boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); - - if (valid) { - try { - handler.getServer().getMappings().mapFromObf(entry, new EntryMapping(newName)); - } catch (IllegalNameException e) { - valid = false; - } - } - - 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/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java deleted file mode 100644 index 058f0e58..00000000 --- a/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java +++ /dev/null @@ -1,48 +0,0 @@ -package cuchaz.enigma.network.packet; - -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.gui.GuiController; -import cuchaz.enigma.translation.representation.entry.Entry; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -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(GuiController controller) { - controller.rename(new EntryReference<>(entry, entry.getName()), newName, refreshClassTree, false); - controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); - } -} diff --git a/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java deleted file mode 100644 index e6378d1d..00000000 --- a/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java +++ /dev/null @@ -1,88 +0,0 @@ -package cuchaz.enigma.network.packet; - -import cuchaz.enigma.gui.GuiController; -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; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; - -public class SyncMappingsS2CPacket implements Packet { - private EntryTree mappings; - - SyncMappingsS2CPacket() { - } - - public SyncMappingsS2CPacket(EntryTree mappings) { - this.mappings = mappings; - } - - @Override - public void read(DataInput input) throws IOException { - mappings = new HashEntryTree<>(); - int size = input.readInt(); - for (int i = 0; i < size; i++) { - readEntryTreeNode(input, null); - } - } - - 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); - } - } - mappings.insert(entry, mapping); - int size = input.readUnsignedShort(); - for (int i = 0; i < size; i++) { - readEntryTreeNode(input, entry); - } - } - - @Override - public void write(DataOutput output) throws IOException { - List> roots = mappings.getRootNodes().collect(Collectors.toList()); - output.writeInt(roots.size()); - for (EntryTreeNode node : roots) { - writeEntryTreeNode(output, node); - } - } - - 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()); - } - } - Collection> children = node.getChildNodes(); - output.writeShort(children.size()); - for (EntryTreeNode child : children) { - writeEntryTreeNode(output, child); - } - } - - @Override - public void handle(GuiController controller) { - controller.openMappings(mappings); - controller.sendPacket(new ConfirmChangeC2SPacket(EnigmaServer.DUMMY_SYNC_ID)); - } -} diff --git a/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java deleted file mode 100644 index 89048485..00000000 --- a/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java +++ /dev/null @@ -1,44 +0,0 @@ -package cuchaz.enigma.network.packet; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import cuchaz.enigma.gui.GuiController; - -public class UserListS2CPacket implements Packet { - - private List users; - - UserListS2CPacket() { - } - - public UserListS2CPacket(List users) { - this.users = users; - } - - @Override - public void read(DataInput input) throws IOException { - int len = input.readUnsignedShort(); - users = new ArrayList<>(len); - for (int i = 0; i < len; i++) { - users.add(input.readUTF()); - } - } - - @Override - public void write(DataOutput output) throws IOException { - output.writeShort(users.size()); - for (String user : users) { - PacketHelper.writeString(output, user); - } - } - - @Override - public void handle(GuiController handler) { - handler.updateUserList(users); - } - -} diff --git a/src/main/java/cuchaz/enigma/source/Decompiler.java b/src/main/java/cuchaz/enigma/source/Decompiler.java deleted file mode 100644 index c9666d52..00000000 --- a/src/main/java/cuchaz/enigma/source/Decompiler.java +++ /dev/null @@ -1,5 +0,0 @@ -package cuchaz.enigma.source; - -public interface Decompiler { - Source getSource(String className); -} diff --git a/src/main/java/cuchaz/enigma/source/DecompilerService.java b/src/main/java/cuchaz/enigma/source/DecompilerService.java deleted file mode 100644 index 377ccbc1..00000000 --- a/src/main/java/cuchaz/enigma/source/DecompilerService.java +++ /dev/null @@ -1,11 +0,0 @@ -package cuchaz.enigma.source; - -import cuchaz.enigma.ClassProvider; -import cuchaz.enigma.api.service.EnigmaService; -import cuchaz.enigma.api.service.EnigmaServiceType; - -public interface DecompilerService extends EnigmaService { - EnigmaServiceType TYPE = EnigmaServiceType.create("decompiler"); - - Decompiler create(ClassProvider classProvider, SourceSettings settings); -} diff --git a/src/main/java/cuchaz/enigma/source/Decompilers.java b/src/main/java/cuchaz/enigma/source/Decompilers.java deleted file mode 100644 index 7d154a6a..00000000 --- a/src/main/java/cuchaz/enigma/source/Decompilers.java +++ /dev/null @@ -1,9 +0,0 @@ -package cuchaz.enigma.source; - -import cuchaz.enigma.source.cfr.CfrDecompiler; -import cuchaz.enigma.source.procyon.ProcyonDecompiler; - -public class Decompilers { - public static final DecompilerService PROCYON = ProcyonDecompiler::new; - public static final DecompilerService CFR = CfrDecompiler::new; -} diff --git a/src/main/java/cuchaz/enigma/source/Source.java b/src/main/java/cuchaz/enigma/source/Source.java deleted file mode 100644 index 43c4de0c..00000000 --- a/src/main/java/cuchaz/enigma/source/Source.java +++ /dev/null @@ -1,11 +0,0 @@ -package cuchaz.enigma.source; - -import cuchaz.enigma.translation.mapping.EntryRemapper; - -public interface Source { - String asString(); - - Source addJavadocs(EntryRemapper remapper); - - SourceIndex index(); -} diff --git a/src/main/java/cuchaz/enigma/source/SourceIndex.java b/src/main/java/cuchaz/enigma/source/SourceIndex.java deleted file mode 100644 index 6a335ec9..00000000 --- a/src/main/java/cuchaz/enigma/source/SourceIndex.java +++ /dev/null @@ -1,174 +0,0 @@ -package cuchaz.enigma.source; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.analysis.Token; -import cuchaz.enigma.gui.SourceRemapper; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.mapping.ResolutionStrategy; -import cuchaz.enigma.translation.representation.entry.Entry; - -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import java.util.stream.Collectors; - -public class SourceIndex { - private String source; - private List lineOffsets; - private final TreeMap, Entry>> tokenToReference; - private final Multimap, Entry>, Token> referenceToTokens; - private final Map, Token> declarationToToken; - - public SourceIndex() { - tokenToReference = new TreeMap<>(); - referenceToTokens = HashMultimap.create(); - declarationToToken = Maps.newHashMap(); - } - - public SourceIndex(String source) { - this(); - setSource(source); - } - - public void setSource(String source) { - this.source = source; - lineOffsets = Lists.newArrayList(); - lineOffsets.add(0); - - for (int i = 0; i < this.source.length(); i++) { - if (this.source.charAt(i) == '\n') { - lineOffsets.add(i + 1); - } - } - } - - public String getSource() { - return source; - } - - public int getLineNumber(int position) { - int line = 0; - - for (int offset : lineOffsets) { - if (offset > position) { - break; - } - - line++; - } - - return line; - } - - public int getColumnNumber(int position) { - return position - lineOffsets.get(getLineNumber(position) - 1) + 1; - } - - public int getPosition(int line, int column) { - return lineOffsets.get(line - 1) + column - 1; - } - - public Iterable> declarations() { - return declarationToToken.keySet(); - } - - public Iterable declarationTokens() { - return declarationToToken.values(); - } - - public Token getDeclarationToken(Entry entry) { - return declarationToToken.get(entry); - } - - public void addDeclaration(Token token, Entry deobfEntry) { - if (token != null) { - EntryReference, Entry> reference = new EntryReference<>(deobfEntry, token.text); - tokenToReference.put(token, reference); - referenceToTokens.put(reference, token); - declarationToToken.put(deobfEntry, token); - } - } - - public Iterable, Entry>> references() { - return referenceToTokens.keySet(); - } - - public EntryReference, Entry> getReference(Token token) { - if (token == null) { - return null; - } - - return tokenToReference.get(token); - } - - public Iterable referenceTokens() { - return tokenToReference.keySet(); - } - - public Token getReferenceToken(int pos) { - Token token = tokenToReference.floorKey(new Token(pos, pos, null)); - - if (token != null && token.contains(pos)) { - return token; - } - - return null; - } - - public Collection getReferenceTokens(EntryReference, Entry> deobfReference) { - return referenceToTokens.get(deobfReference); - } - - public void addReference(Token token, Entry deobfEntry, Entry deobfContext) { - if (token != null) { - EntryReference, Entry> deobfReference = new EntryReference<>(deobfEntry, token.text, deobfContext); - tokenToReference.put(token, deobfReference); - referenceToTokens.put(deobfReference, token); - } - } - - public void resolveReferences(EntryResolver resolver) { - // resolve all the classes in the source references - for (Token token : Lists.newArrayList(referenceToTokens.values())) { - EntryReference, Entry> reference = tokenToReference.get(token); - EntryReference, Entry> resolvedReference = resolver.resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST); - - // replace the reference - tokenToReference.replace(token, resolvedReference); - - Collection tokens = referenceToTokens.removeAll(reference); - referenceToTokens.putAll(resolvedReference, tokens); - } - } - - public SourceIndex remapTo(SourceRemapper.Result result) { - SourceIndex remapped = new SourceIndex(result.getSource()); - - for (Map.Entry, Token> entry : declarationToToken.entrySet()) { - remapped.declarationToToken.put(entry.getKey(), result.getRemappedToken(entry.getValue())); - } - - for (Map.Entry, Entry>, Collection> entry : referenceToTokens.asMap().entrySet()) { - EntryReference, Entry> reference = entry.getKey(); - Collection oldTokens = entry.getValue(); - - Collection newTokens = oldTokens - .stream() - .map(result::getRemappedToken) - .collect(Collectors.toList()); - - remapped.referenceToTokens.putAll(reference, newTokens); - } - - for (Map.Entry, Entry>> entry : tokenToReference.entrySet()) { - remapped.tokenToReference.put(result.getRemappedToken(entry.getKey()), entry.getValue()); - } - - return remapped; - } -} diff --git a/src/main/java/cuchaz/enigma/source/SourceSettings.java b/src/main/java/cuchaz/enigma/source/SourceSettings.java deleted file mode 100644 index f6c68e98..00000000 --- a/src/main/java/cuchaz/enigma/source/SourceSettings.java +++ /dev/null @@ -1,11 +0,0 @@ -package cuchaz.enigma.source; - -public class SourceSettings { - public final boolean removeImports; - public final boolean removeVariableFinal; - - public SourceSettings(boolean removeImports, boolean removeVariableFinal) { - this.removeImports = removeImports; - this.removeVariableFinal = removeVariableFinal; - } -} diff --git a/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java b/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java deleted file mode 100644 index 9e37f168..00000000 --- a/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java +++ /dev/null @@ -1,108 +0,0 @@ -package cuchaz.enigma.source.cfr; - -import com.google.common.io.ByteStreams; -import cuchaz.enigma.ClassProvider; -import cuchaz.enigma.source.Decompiler; -import cuchaz.enigma.source.Source; -import cuchaz.enigma.source.SourceSettings; -import org.benf.cfr.reader.apiunreleased.ClassFileSource2; -import org.benf.cfr.reader.apiunreleased.JarContent; -import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair; -import org.benf.cfr.reader.entities.ClassFile; -import org.benf.cfr.reader.mapping.MappingFactory; -import org.benf.cfr.reader.mapping.ObfuscationMapping; -import org.benf.cfr.reader.relationship.MemberNameResolver; -import org.benf.cfr.reader.state.DCCommonState; -import org.benf.cfr.reader.state.TypeUsageCollectingDumper; -import org.benf.cfr.reader.util.AnalysisType; -import org.benf.cfr.reader.util.CannotLoadClassException; -import org.benf.cfr.reader.util.collections.ListFactory; -import org.benf.cfr.reader.util.getopt.Options; -import org.benf.cfr.reader.util.getopt.OptionsImpl; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.tree.ClassNode; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - - -public class CfrDecompiler implements Decompiler { - private final DCCommonState state; - - public CfrDecompiler(ClassProvider classProvider, SourceSettings sourceSettings) { - Map options = new HashMap<>(); - - state = new DCCommonState(OptionsImpl.getFactory().create(options), new ClassFileSource2() { - @Override - public JarContent addJarContent(String s, AnalysisType analysisType) { - return null; - } - - @Override - public void informAnalysisRelativePathDetail(String usePath, String classFilePath) { - - } - - @Override - public Collection addJar(String jarPath) { - return null; - } - - @Override - public String getPossiblyRenamedPath(String path) { - return path; - } - - @Override - public Pair getClassFileContent(String path) { - ClassNode node = classProvider.getClassNode(path.substring(0, path.lastIndexOf('.'))); - - if (node == null) { - try (InputStream classResource = CfrDecompiler.class.getClassLoader().getResourceAsStream(path)) { - if (classResource != null) { - return new Pair<>(ByteStreams.toByteArray(classResource), path); - } - } catch (IOException ignored) {} - - return null; - } - - ClassWriter cw = new ClassWriter(0); - node.accept(cw); - return new Pair<>(cw.toByteArray(), path); - } - }); - } - - @Override - public Source getSource(String className) { - DCCommonState state = this.state; - Options options = state.getOptions(); - - ObfuscationMapping mapping = MappingFactory.get(options, state); - state = new DCCommonState(state, mapping); - ClassFile tree = state.getClassFileMaybePath(className); - - state.configureWith(tree); - - // To make sure we're analysing the cached version - try { - tree = state.getClassFile(tree.getClassType()); - } catch (CannotLoadClassException ignored) {} - - if (options.getOption(OptionsImpl.DECOMPILE_INNER_CLASSES)) { - tree.loadInnerClasses(state); - } - - if (options.getOption(OptionsImpl.RENAME_DUP_MEMBERS)) { - MemberNameResolver.resolveNames(state, ListFactory.newList(state.getClassCache().getLoadedTypes())); - } - - TypeUsageCollectingDumper typeUsageCollector = new TypeUsageCollectingDumper(options, tree); - tree.analyseTop(state, typeUsageCollector); - return new CfrSource(tree, state, typeUsageCollector.getRealTypeUsageInformation()); - } -} diff --git a/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java b/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java deleted file mode 100644 index d4f2da6a..00000000 --- a/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java +++ /dev/null @@ -1,38 +0,0 @@ -package cuchaz.enigma.source.cfr; - -import cuchaz.enigma.source.Source; -import cuchaz.enigma.source.SourceIndex; -import cuchaz.enigma.translation.mapping.EntryRemapper; -import org.benf.cfr.reader.entities.ClassFile; -import org.benf.cfr.reader.state.DCCommonState; -import org.benf.cfr.reader.state.TypeUsageInformation; - -public class CfrSource implements Source { - private final ClassFile tree; - private final SourceIndex index; - private final String string; - - public CfrSource(ClassFile tree, DCCommonState state, TypeUsageInformation typeUsages) { - this.tree = tree; - - EnigmaDumper dumper = new EnigmaDumper(typeUsages); - tree.dump(state.getObfuscationMapping().wrap(dumper)); - index = dumper.getIndex(); - string = dumper.getString(); - } - - @Override - public String asString() { - return string; - } - - @Override - public Source addJavadocs(EntryRemapper remapper) { - return this; // TODO - } - - @Override - public SourceIndex index() { - return index; - } -} diff --git a/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java b/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java deleted file mode 100644 index 09e0a9b2..00000000 --- a/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java +++ /dev/null @@ -1,433 +0,0 @@ -package cuchaz.enigma.source.cfr; - -import cuchaz.enigma.analysis.Token; -import cuchaz.enigma.source.SourceIndex; -import cuchaz.enigma.translation.representation.MethodDescriptor; -import cuchaz.enigma.translation.representation.TypeDescriptor; -import cuchaz.enigma.translation.representation.entry.*; -import org.benf.cfr.reader.bytecode.analysis.types.*; -import org.benf.cfr.reader.bytecode.analysis.variables.NamedVariable; -import org.benf.cfr.reader.entities.Field; -import org.benf.cfr.reader.entities.Method; -import org.benf.cfr.reader.mapping.NullMapping; -import org.benf.cfr.reader.mapping.ObfuscationMapping; -import org.benf.cfr.reader.state.TypeUsageInformation; -import org.benf.cfr.reader.util.collections.SetFactory; -import org.benf.cfr.reader.util.output.DelegatingDumper; -import org.benf.cfr.reader.util.output.Dumpable; -import org.benf.cfr.reader.util.output.Dumper; -import org.benf.cfr.reader.util.output.TypeContext; - -import java.util.Set; -import java.util.stream.Collectors; - -public class EnigmaDumper implements Dumper { - private int outputCount = 0; - private int indent; - private boolean atStart = true; - private boolean pendingCR = false; - private final StringBuilder sb = new StringBuilder(); - private final TypeUsageInformation typeUsageInformation; - private final Set emitted = SetFactory.newSet(); - private final SourceIndex index = new SourceIndex(); - private int position; - - - public EnigmaDumper(TypeUsageInformation typeUsageInformation) { - this.typeUsageInformation = typeUsageInformation; - } - - private void append(String s) { - sb.append(s); - position += s.length(); - } - - private String getDesc(JavaTypeInstance type) { - if (!type.isUsableType() && type != RawJavaType.VOID) { - throw new IllegalArgumentException(type.toString()); - } - - if (type instanceof JavaGenericBaseInstance) { - return getDesc(type.getDeGenerifiedType()); - } - - if (type instanceof JavaRefTypeInstance) { - return "L" + type.getRawName().replace('.', '/') + ";"; - } - - if (type instanceof JavaArrayTypeInstance) { - return "[" + getDesc(((JavaArrayTypeInstance) type).removeAnArrayIndirection()); - } - - if (type instanceof RawJavaType) { - switch ((RawJavaType) type) { - case BOOLEAN: - return "Z"; - case BYTE: - return "B"; - case CHAR: - return "C"; - case SHORT: - return "S"; - case INT: - return "I"; - case LONG: - return "J"; - case FLOAT: - return "F"; - case DOUBLE: - return "D"; - case VOID: - return "V"; - default: - throw new AssertionError(); - } - } - - throw new AssertionError(); - } - - private MethodEntry getMethodEntry(MethodPrototype method) { - if (method == null || method.getClassType() == null) { - return null; - } - - MethodDescriptor desc = new MethodDescriptor( - method.getArgs().stream().map(type -> new TypeDescriptor(getDesc(type))).collect(Collectors.toList()), - new TypeDescriptor(method.getName().equals("") || method.getName().equals("") ? "V" : getDesc(method.getReturnType())) - ); - - return new MethodEntry(getClassEntry(method.getClassType()), method.getName(), desc); - } - - private LocalVariableEntry getParameterEntry(MethodPrototype method, int parameterIndex, String name) { - int variableIndex = method.isInstanceMethod() ? 1 : 0; - for (int i = 0; i < parameterIndex; i++) { - variableIndex += method.getArgs().get(i).getStackType().getComputationCategory(); - } - - return new LocalVariableEntry(getMethodEntry(method), variableIndex, name, true, null); - } - - private FieldEntry getFieldEntry(JavaTypeInstance owner, String name, JavaTypeInstance type) { - return new FieldEntry(getClassEntry(owner), name, new TypeDescriptor(getDesc(type))); - } - - private ClassEntry getClassEntry(JavaTypeInstance type) { - return new ClassEntry(type.getRawName().replace('.', '/')); - } - - @Override - public Dumper beginBlockComment(boolean inline) { - print("/*").newln(); - return this; - } - - @Override - public Dumper endBlockComment() { - print(" */").newln(); - return this; - } - - @Override - public Dumper label(String s, boolean inline) { - processPendingCR(); - append(s); - append(":"); - return this; - } - - @Override - public Dumper comment(String s) { - append("// "); - append(s); - append("\n"); - return this; - } - - @Override - public void enqueuePendingCarriageReturn() { - pendingCR = true; - } - - @Override - public Dumper removePendingCarriageReturn() { - pendingCR = false; - return this; - } - - private void processPendingCR() { - if (pendingCR) { - append("\n"); - atStart = true; - pendingCR = false; - } - } - - @Override - public Dumper identifier(String s, Object ref, boolean defines) { - return print(s); - } - - @Override - public Dumper methodName(String name, MethodPrototype method, boolean special, boolean defines) { - doIndent(); - Token token = new Token(position, position + name.length(), name); - Entry entry = getMethodEntry(method); - - if (entry != null) { - if (defines) { - index.addDeclaration(token, entry); - } else { - index.addReference(token, entry, null); - } - } - - return identifier(name, null, defines); - } - - @Override - public Dumper parameterName(String name, MethodPrototype method, int index, boolean defines) { - doIndent(); - Token token = new Token(position, position + name.length(), name); - Entry entry = getParameterEntry(method, index, name); - - if (entry != null) { - if (defines) { - this.index.addDeclaration(token, entry); - } else { - this.index.addReference(token, entry, null); - } - } - - return identifier(name, null, defines); - } - - @Override - public Dumper variableName(String name, NamedVariable variable, boolean defines) { - return identifier(name, null, defines); - } - - @Override - public Dumper packageName(JavaRefTypeInstance t) { - String s = t.getPackageName(); - - if (!s.isEmpty()) { - keyword("package ").print(s).endCodeln().newln(); - } - - return this; - } - - @Override - public Dumper fieldName(String name, Field field, JavaTypeInstance owner, boolean hiddenDeclaration, boolean defines) { - doIndent(); - Token token = new Token(position, position + name.length(), name); - Entry entry = field == null ? null : getFieldEntry(owner, name, field.getJavaTypeInstance()); - - if (entry != null) { - if (defines) { - index.addDeclaration(token, entry); - } else { - index.addReference(token, entry, null); - } - } - - identifier(name, null, defines); - return this; - } - - @Override - public Dumper print(String s) { - processPendingCR(); - doIndent(); - append(s); - atStart = s.endsWith("\n"); - outputCount++; - return this; - } - - @Override - public Dumper print(char c) { - return print(String.valueOf(c)); - } - - @Override - public Dumper newln() { - append("\n"); - atStart = true; - outputCount++; - return this; - } - - @Override - public Dumper endCodeln() { - append(";\n"); - atStart = true; - outputCount++; - return this; - } - - @Override - public Dumper keyword(String s) { - print(s); - return this; - } - - @Override - public Dumper operator(String s) { - print(s); - return this; - } - - @Override - public Dumper separator(String s) { - print(s); - return this; - } - - @Override - public Dumper literal(String s, Object o) { - print(s); - return this; - } - - private void doIndent() { - if (!atStart) return; - String indents = " "; - - for (int x = 0; x < indent; ++x) { - append(indents); - } - - atStart = false; - } - - @Override - public void indent(int diff) { - indent += diff; - } - - @Override - public Dumper dump(Dumpable d) { - if (d == null) { - keyword("null"); - return this; - } - - d.dump(this); - return this; - } - - @Override - public TypeUsageInformation getTypeUsageInformation() { - return typeUsageInformation; - } - - @Override - public ObfuscationMapping getObfuscationMapping() { - return NullMapping.INSTANCE; - } - - @Override - public String toString() { - return sb.toString(); - } - - @Override - public void addSummaryError(Method method, String s) {} - - @Override - public void close() { - } - - @Override - public boolean canEmitClass(JavaTypeInstance type) { - return emitted.add(type); - } - - @Override - public int getOutputCount() { - return outputCount; - } - - @Override - public Dumper dump(JavaTypeInstance type) { - return dump(type, TypeContext.None, false); - } - - @Override - public Dumper dump(JavaTypeInstance type, boolean defines) { - return dump(type, TypeContext.None, false); - } - - @Override - public Dumper dump(JavaTypeInstance type, TypeContext context) { - return dump(type, context, false); - } - - private Dumper dump(JavaTypeInstance type, TypeContext context, boolean defines) { - doIndent(); - if (type instanceof JavaRefTypeInstance) { - int start = position; - type.dumpInto(this, typeUsageInformation, TypeContext.None); - int end = position; - Token token = new Token(start, end, sb.toString().substring(start, end)); - - if (defines) { - index.addDeclaration(token, getClassEntry(type)); - } else { - index.addReference(token, getClassEntry(type), null); - } - - return this; - } - - type.dumpInto(this, typeUsageInformation, context); - return this; - } - - @Override - public Dumper withTypeUsageInformation(TypeUsageInformation innerclassTypeUsageInformation) { - return new WithTypeUsageInformationDumper(this, innerclassTypeUsageInformation); - } - - public SourceIndex getIndex() { - index.setSource(getString()); - return index; - } - - public String getString() { - return sb.toString(); - } - - public static class WithTypeUsageInformationDumper extends DelegatingDumper { - private final TypeUsageInformation typeUsageInformation; - - WithTypeUsageInformationDumper(Dumper delegate, TypeUsageInformation typeUsageInformation) { - super(delegate); - this.typeUsageInformation = typeUsageInformation; - } - - @Override - public TypeUsageInformation getTypeUsageInformation() { - return typeUsageInformation; - } - - @Override - public Dumper dump(JavaTypeInstance javaTypeInstance) { - return dump(javaTypeInstance, TypeContext.None); - } - - @Override - public Dumper dump(JavaTypeInstance javaTypeInstance, TypeContext typeContext) { - javaTypeInstance.dumpInto(this, typeUsageInformation, typeContext); - return this; - } - - @Override - public Dumper withTypeUsageInformation(TypeUsageInformation innerclassTypeUsageInformation) { - return new WithTypeUsageInformationDumper(delegate, innerclassTypeUsageInformation); - } - } -} diff --git a/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java b/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java deleted file mode 100644 index 2fae61a6..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java +++ /dev/null @@ -1,49 +0,0 @@ -package cuchaz.enigma.source.procyon; - -import com.strobel.assembler.metadata.FieldDefinition; -import com.strobel.assembler.metadata.MethodDefinition; -import com.strobel.assembler.metadata.TypeDefinition; -import com.strobel.assembler.metadata.TypeReference; -import cuchaz.enigma.translation.representation.AccessFlags; -import cuchaz.enigma.translation.representation.MethodDescriptor; -import cuchaz.enigma.translation.representation.Signature; -import cuchaz.enigma.translation.representation.TypeDescriptor; -import cuchaz.enigma.translation.representation.entry.ClassDefEntry; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.FieldDefEntry; -import cuchaz.enigma.translation.representation.entry.MethodDefEntry; - -public class EntryParser { - public static FieldDefEntry parse(FieldDefinition definition) { - ClassEntry owner = parse(definition.getDeclaringType()); - TypeDescriptor descriptor = new TypeDescriptor(definition.getErasedSignature()); - Signature signature = Signature.createTypedSignature(definition.getSignature()); - AccessFlags access = new AccessFlags(definition.getModifiers()); - return new FieldDefEntry(owner, definition.getName(), descriptor, signature, access, null); - } - - public static ClassDefEntry parse(TypeDefinition def) { - String name = def.getInternalName(); - Signature signature = Signature.createSignature(def.getSignature()); - AccessFlags access = new AccessFlags(def.getModifiers()); - ClassEntry superClass = def.getBaseType() != null ? parse(def.getBaseType()) : null; - ClassEntry[] interfaces = def.getExplicitInterfaces().stream().map(EntryParser::parse).toArray(ClassEntry[]::new); - return new ClassDefEntry(name, signature, access, superClass, interfaces); - } - - public static ClassEntry parse(TypeReference typeReference) { - return new ClassEntry(typeReference.getInternalName()); - } - - public static MethodDefEntry parse(MethodDefinition definition) { - ClassEntry classEntry = parse(definition.getDeclaringType()); - MethodDescriptor descriptor = new MethodDescriptor(definition.getErasedSignature()); - Signature signature = Signature.createSignature(definition.getSignature()); - AccessFlags access = new AccessFlags(definition.getModifiers()); - return new MethodDefEntry(classEntry, definition.getName(), descriptor, signature, access, null); - } - - public static TypeDescriptor parseTypeDescriptor(TypeReference type) { - return new TypeDescriptor(type.getErasedSignature()); - } -} diff --git a/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java b/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java deleted file mode 100644 index 37bc0c86..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java +++ /dev/null @@ -1,81 +0,0 @@ -package cuchaz.enigma.source.procyon; - -import com.strobel.assembler.metadata.ITypeLoader; -import com.strobel.assembler.metadata.MetadataSystem; -import com.strobel.assembler.metadata.TypeDefinition; -import com.strobel.assembler.metadata.TypeReference; -import com.strobel.decompiler.DecompilerContext; -import com.strobel.decompiler.DecompilerSettings; -import com.strobel.decompiler.languages.java.BraceStyle; -import com.strobel.decompiler.languages.java.JavaFormattingOptions; -import com.strobel.decompiler.languages.java.ast.AstBuilder; -import com.strobel.decompiler.languages.java.ast.CompilationUnit; -import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; -import cuchaz.enigma.ClassProvider; -import cuchaz.enigma.api.EnigmaPluginContext; -import cuchaz.enigma.source.Source; -import cuchaz.enigma.source.Decompiler; -import cuchaz.enigma.source.SourceSettings; -import cuchaz.enigma.source.procyon.transformers.*; -import cuchaz.enigma.source.procyon.typeloader.CompiledSourceTypeLoader; -import cuchaz.enigma.source.procyon.typeloader.NoRetryMetadataSystem; -import cuchaz.enigma.source.procyon.typeloader.SynchronizedTypeLoader; -import cuchaz.enigma.utils.Utils; - -public class ProcyonDecompiler implements Decompiler { - private final SourceSettings settings; - private final DecompilerSettings decompilerSettings; - private final MetadataSystem metadataSystem; - - public ProcyonDecompiler(ClassProvider classProvider, SourceSettings settings) { - ITypeLoader typeLoader = new SynchronizedTypeLoader(new CompiledSourceTypeLoader(classProvider)); - - metadataSystem = new NoRetryMetadataSystem(typeLoader); - metadataSystem.setEagerMethodLoadingEnabled(true); - - decompilerSettings = DecompilerSettings.javaDefaults(); - decompilerSettings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true)); - decompilerSettings.setForceExplicitImports(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitImports", true)); - decompilerSettings.setForceExplicitTypeArguments(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true)); - decompilerSettings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false)); - decompilerSettings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false)); - decompilerSettings.setTypeLoader(typeLoader); - - JavaFormattingOptions formattingOptions = decompilerSettings.getJavaFormattingOptions(); - formattingOptions.ClassBraceStyle = BraceStyle.EndOfLine; - formattingOptions.InterfaceBraceStyle = BraceStyle.EndOfLine; - formattingOptions.EnumBraceStyle = BraceStyle.EndOfLine; - - this.settings = settings; - } - - @Override - public Source getSource(String className) { - TypeReference type = metadataSystem.lookupType(className); - if (type == null) { - throw new Error(String.format("Unable to find desc: %s", className)); - } - - TypeDefinition resolvedType = type.resolve(); - - DecompilerContext context = new DecompilerContext(); - context.setCurrentType(resolvedType); - context.setSettings(decompilerSettings); - - AstBuilder builder = new AstBuilder(context); - builder.addType(resolvedType); - builder.runTransformations(null); - CompilationUnit source = builder.getCompilationUnit(); - - new ObfuscatedEnumSwitchRewriterTransform(context).run(source); - new VarargsFixer(context).run(source); - new RemoveObjectCasts(context).run(source); - new Java8Generics().run(source); - new InvalidIdentifierFix().run(source); - if (settings.removeImports) DropImportAstTransform.INSTANCE.run(source); - if (settings.removeVariableFinal) DropVarModifiersAstTransform.INSTANCE.run(source); - source.acceptVisitor(new InsertParenthesesVisitor(), null); - - return new ProcyonSource(source, decompilerSettings); - } -} diff --git a/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java b/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java deleted file mode 100644 index 53c8c70b..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java +++ /dev/null @@ -1,49 +0,0 @@ -package cuchaz.enigma.source.procyon; - -import com.strobel.decompiler.DecompilerSettings; -import com.strobel.decompiler.PlainTextOutput; -import com.strobel.decompiler.languages.java.JavaOutputVisitor; -import com.strobel.decompiler.languages.java.ast.CompilationUnit; -import cuchaz.enigma.source.Source; -import cuchaz.enigma.source.SourceIndex; -import cuchaz.enigma.source.procyon.index.SourceIndexVisitor; -import cuchaz.enigma.source.procyon.transformers.AddJavadocsAstTransform; -import cuchaz.enigma.translation.mapping.EntryRemapper; - -import java.io.StringWriter; - -public class ProcyonSource implements Source { - private final DecompilerSettings settings; - private final CompilationUnit tree; - private String string; - - public ProcyonSource(CompilationUnit tree, DecompilerSettings settings) { - this.settings = settings; - this.tree = tree; - } - - @Override - public SourceIndex index() { - SourceIndex index = new SourceIndex(asString()); - tree.acceptVisitor(new SourceIndexVisitor(), index); - return index; - } - - @Override - public String asString() { - if (string == null) { - StringWriter writer = new StringWriter(); - tree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(writer), settings), null); - string = writer.toString(); - } - - return string; - } - - @Override - public Source addJavadocs(EntryRemapper remapper) { - CompilationUnit remappedTree = (CompilationUnit) tree.clone(); - new AddJavadocsAstTransform(remapper).run(remappedTree); - return new ProcyonSource(remappedTree, settings); - } -} diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java deleted file mode 100644 index f6eeb159..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java +++ /dev/null @@ -1,95 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.source.procyon.index; - -import com.strobel.assembler.metadata.FieldDefinition; -import com.strobel.assembler.metadata.MethodDefinition; -import com.strobel.assembler.metadata.TypeDefinition; -import com.strobel.assembler.metadata.TypeReference; -import com.strobel.decompiler.languages.TextLocation; -import com.strobel.decompiler.languages.java.ast.*; -import cuchaz.enigma.source.SourceIndex; -import cuchaz.enigma.source.procyon.EntryParser; -import cuchaz.enigma.translation.representation.entry.*; - -public class SourceIndexClassVisitor extends SourceIndexVisitor { - private ClassDefEntry classEntry; - - public SourceIndexClassVisitor(ClassDefEntry classEntry) { - this.classEntry = classEntry; - } - - @Override - public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { - // is this this class, or a subtype? - TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); - ClassDefEntry classEntry = EntryParser.parse(def); - if (!classEntry.equals(this.classEntry)) { - // it's a subtype, recurse - index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), classEntry); - return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); - } - - return visitChildren(node, index); - } - - @Override - public Void visitSimpleType(SimpleType node, SourceIndex index) { - TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); - if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { - ClassEntry classEntry = new ClassEntry(ref.getInternalName()); - index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), classEntry, this.classEntry); - } - - return visitChildren(node, index); - } - - @Override - public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { - MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); - MethodDefEntry methodEntry = EntryParser.parse(def); - AstNode tokenNode = node.getNameToken(); - if (methodEntry.isConstructor() && methodEntry.getName().equals("")) { - // for static initializers, check elsewhere for the token node - tokenNode = node.getModifiers().firstOrNullObject(); - } - index.addDeclaration(TokenFactory.createToken(index, tokenNode), methodEntry); - return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index); - } - - @Override - public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { - MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); - MethodDefEntry methodEntry = EntryParser.parse(def); - index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), methodEntry); - return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index); - } - - @Override - public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { - FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); - FieldDefEntry fieldEntry = EntryParser.parse(def); - assert (node.getVariables().size() == 1); - VariableInitializer variable = node.getVariables().firstOrNullObject(); - index.addDeclaration(TokenFactory.createToken(index, variable.getNameToken()), fieldEntry); - return visitChildren(node, index); - } - - @Override - public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { - // treat enum declarations as field declarations - FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); - FieldDefEntry fieldEntry = EntryParser.parse(def); - index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), fieldEntry); - return visitChildren(node, index); - } -} diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java deleted file mode 100644 index 0e8bc51a..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java +++ /dev/null @@ -1,218 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.source.procyon.index; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import com.strobel.assembler.metadata.*; -import com.strobel.decompiler.ast.Variable; -import com.strobel.decompiler.languages.TextLocation; -import com.strobel.decompiler.languages.java.ast.*; -import cuchaz.enigma.source.SourceIndex; -import cuchaz.enigma.source.procyon.EntryParser; -import cuchaz.enigma.translation.representation.MethodDescriptor; -import cuchaz.enigma.translation.representation.TypeDescriptor; -import cuchaz.enigma.translation.representation.entry.*; - -import java.lang.Error; -import java.util.HashMap; -import java.util.Map; - -public class SourceIndexMethodVisitor extends SourceIndexVisitor { - private final MethodDefEntry methodEntry; - - private Multimap unmatchedIdentifier = HashMultimap.create(); - private Map> identifierEntryCache = new HashMap<>(); - - public SourceIndexMethodVisitor(MethodDefEntry methodEntry) { - this.methodEntry = methodEntry; - } - - @Override - public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - - // get the behavior entry - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - MethodEntry methodEntry = null; - if (ref instanceof MethodReference) { - methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature())); - } - if (methodEntry != null) { - // get the node for the token - AstNode tokenNode = null; - if (node.getTarget() instanceof MemberReferenceExpression) { - tokenNode = ((MemberReferenceExpression) node.getTarget()).getMemberNameToken(); - } else if (node.getTarget() instanceof SuperReferenceExpression) { - tokenNode = node.getTarget(); - } else if (node.getTarget() instanceof ThisReferenceExpression) { - tokenNode = node.getTarget(); - } - if (tokenNode != null) { - index.addReference(TokenFactory.createToken(index, tokenNode), methodEntry, this.methodEntry); - } - } - - // Check for identifier - node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression) - .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index)); - return visitChildren(node, index); - } - - @Override - public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - if (ref instanceof FieldReference) { - // make sure this is actually a field - String erasedSignature = ref.getErasedSignature(); - if (erasedSignature.indexOf('(') >= 0) { - throw new Error("Expected a field here! got " + ref); - } - - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(erasedSignature)); - index.addReference(TokenFactory.createToken(index, node.getMemberNameToken()), fieldEntry, this.methodEntry); - } - - return visitChildren(node, index); - } - - @Override - public Void visitSimpleType(SimpleType node, SourceIndex index) { - TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); - if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { - ClassEntry classEntry = new ClassEntry(ref.getInternalName()); - index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), classEntry, this.methodEntry); - } - - return visitChildren(node, index); - } - - @Override - public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { - ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); - int parameterIndex = def.getSlot(); - - if (parameterIndex >= 0) { - MethodDefEntry ownerMethod = methodEntry; - if (def.getMethod() instanceof MethodDefinition) { - ownerMethod = EntryParser.parse((MethodDefinition) def.getMethod()); - } - - TypeDescriptor parameterType = EntryParser.parseTypeDescriptor(def.getParameterType()); - LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, parameterIndex, node.getName(), true, parameterType, null); - Identifier identifier = node.getNameToken(); - // cache the argument entry and the identifier - identifierEntryCache.put(identifier.getName(), localVariableEntry); - index.addDeclaration(TokenFactory.createToken(index, identifier), localVariableEntry); - } - - return visitChildren(node, index); - } - - @Override - public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - if (ref != null) { - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(ref.getErasedSignature())); - index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), fieldEntry, this.methodEntry); - } else - this.checkIdentifier(node, index); - return visitChildren(node, index); - } - - private void checkIdentifier(IdentifierExpression node, SourceIndex index) { - if (identifierEntryCache.containsKey(node.getIdentifier())) // If it's in the argument cache, create a token! - index.addDeclaration(TokenFactory.createToken(index, node.getIdentifierToken()), identifierEntryCache.get(node.getIdentifier())); - else - unmatchedIdentifier.put(node.getIdentifier(), node.getIdentifierToken()); // Not matched actually, put it! - } - - private void addDeclarationToUnmatched(String key, SourceIndex index) { - Entry entry = identifierEntryCache.get(key); - - // This cannot happened in theory - if (entry == null) - return; - for (Identifier identifier : unmatchedIdentifier.get(key)) - index.addDeclaration(TokenFactory.createToken(index, identifier), entry); - unmatchedIdentifier.removeAll(key); - } - - @Override - public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - if (ref != null && node.getType() instanceof SimpleType) { - SimpleType simpleTypeNode = (SimpleType) node.getType(); - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - MethodEntry constructorEntry = new MethodEntry(classEntry, "", new MethodDescriptor(ref.getErasedSignature())); - index.addReference(TokenFactory.createToken(index, simpleTypeNode.getIdentifierToken()), constructorEntry, this.methodEntry); - } - - return visitChildren(node, index); - } - - @Override - public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) { - AstNodeCollection variables = node.getVariables(); - - // Single assignation - if (variables.size() == 1) { - VariableInitializer initializer = variables.firstOrNullObject(); - if (initializer != null && node.getType() instanceof SimpleType) { - Identifier identifier = initializer.getNameToken(); - Variable variable = initializer.getUserData(Keys.VARIABLE); - if (variable != null) { - VariableDefinition originalVariable = variable.getOriginalVariable(); - if (originalVariable != null) { - int variableIndex = originalVariable.getSlot(); - if (variableIndex >= 0) { - MethodDefEntry ownerMethod = EntryParser.parse(originalVariable.getDeclaringMethod()); - TypeDescriptor variableType = EntryParser.parseTypeDescriptor(originalVariable.getVariableType()); - LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, variableIndex, initializer.getName(), false, variableType, null); - identifierEntryCache.put(identifier.getName(), localVariableEntry); - addDeclarationToUnmatched(identifier.getName(), index); - index.addDeclaration(TokenFactory.createToken(index, identifier), localVariableEntry); - } - } - } - } - } - return visitChildren(node, index); - } - - @Override - public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - - if (ref instanceof MethodReference) { - // get the behavior entry - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - MethodEntry methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature())); - - // get the node for the token - AstNode methodNameToken = node.getMethodNameToken(); - AstNode targetToken = node.getTarget(); - - if (methodNameToken != null) { - index.addReference(TokenFactory.createToken(index, methodNameToken), methodEntry, this.methodEntry); - } - - if (targetToken != null && !(targetToken instanceof ThisReferenceExpression)) { - index.addReference(TokenFactory.createToken(index, targetToken), methodEntry.getParent(), this.methodEntry); - } - } - - return visitChildren(node, index); - } -} diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java deleted file mode 100644 index dad505f7..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java +++ /dev/null @@ -1,40 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.source.procyon.index; - -import com.strobel.assembler.metadata.TypeDefinition; -import com.strobel.decompiler.languages.java.ast.AstNode; -import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; -import com.strobel.decompiler.languages.java.ast.Keys; -import com.strobel.decompiler.languages.java.ast.TypeDeclaration; -import cuchaz.enigma.source.SourceIndex; -import cuchaz.enigma.source.procyon.EntryParser; -import cuchaz.enigma.translation.representation.entry.ClassDefEntry; - -public class SourceIndexVisitor extends DepthFirstAstVisitor { - @Override - public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { - TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); - ClassDefEntry classEntry = EntryParser.parse(def); - index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), classEntry); - - return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); - } - - @Override - protected Void visitChildren(AstNode node, SourceIndex index) { - for (final AstNode child : node.getChildren()) { - child.acceptVisitor(this, index); - } - return null; - } -} diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java b/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java deleted file mode 100644 index 62e7c102..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java +++ /dev/null @@ -1,46 +0,0 @@ -package cuchaz.enigma.source.procyon.index; - -import com.strobel.decompiler.languages.Region; -import com.strobel.decompiler.languages.java.ast.AstNode; -import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; -import com.strobel.decompiler.languages.java.ast.Identifier; -import com.strobel.decompiler.languages.java.ast.TypeDeclaration; -import cuchaz.enigma.analysis.Token; -import cuchaz.enigma.source.SourceIndex; - -import java.util.regex.Pattern; - -public class TokenFactory { - private static final Pattern ANONYMOUS_INNER = Pattern.compile("\\$\\d+$"); - - public static Token createToken(SourceIndex index, AstNode node) { - String name = node instanceof Identifier ? ((Identifier) node).getName() : ""; - Region region = node.getRegion(); - - if (region.getBeginLine() == 0) { - System.err.println("Got bad region from Procyon for node " + node); - return null; - } - - int start = index.getPosition(region.getBeginLine(), region.getBeginColumn()); - int end = index.getPosition(region.getEndLine(), region.getEndColumn()); - String text = index.getSource().substring(start, end); - Token token = new Token(start, end, text); - - boolean isAnonymousInner = - node instanceof Identifier && - name.indexOf('$') >= 0 && node.getParent() instanceof ConstructorDeclaration && - name.lastIndexOf('$') >= 0 && - !ANONYMOUS_INNER.matcher(name).matches(); - - if (isAnonymousInner) { - TypeDeclaration type = node.getParent().getParent() instanceof TypeDeclaration ? (TypeDeclaration) node.getParent().getParent() : null; - if (type != null) { - name = type.getName(); - token.end = token.start + name.length(); - } - } - - return token; - } -} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java deleted file mode 100644 index 70fc8c6b..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java +++ /dev/null @@ -1,134 +0,0 @@ -package cuchaz.enigma.source.procyon.transformers; - -import com.google.common.base.Function; -import com.google.common.base.Strings; -import com.strobel.assembler.metadata.ParameterDefinition; -import com.strobel.decompiler.languages.java.ast.*; -import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; -import cuchaz.enigma.source.procyon.EntryParser; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.EntryRemapper; -import cuchaz.enigma.translation.mapping.ResolutionStrategy; -import cuchaz.enigma.translation.representation.entry.*; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; - -public final class AddJavadocsAstTransform implements IAstTransform { - - private final EntryRemapper remapper; - - public AddJavadocsAstTransform(EntryRemapper remapper) { - this.remapper = remapper; - } - - @Override - public void run(AstNode compilationUnit) { - compilationUnit.acceptVisitor(new Visitor(remapper), null); - } - - static class Visitor extends DepthFirstAstVisitor { - - private final EntryRemapper remapper; - - Visitor(EntryRemapper remapper) { - this.remapper = remapper; - } - - private void addDoc(T node, Function> retriever) { - final Comment[] comments = getComments(node, retriever); - if (comments != null) { - node.insertChildrenBefore(node.getFirstChild(), Roles.COMMENT, comments); - } - } - - 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()); - 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)); - final Comment[] ret = getComments(node, retriever); - if (ret != null) { - final String paramPrefix = "@param " + mapping.getTargetName() + " "; - final String indent = Strings.repeat(" ", paramPrefix.length()); - ret[0].setContent(paramPrefix + ret[0].getContent()); - for (int i = 1; i < ret.length; i++) { - ret[i].setContent(indent + ret[i].getContent()); - } - } - return ret; - } - - private void visitMethod(AstNode node) { - final MethodDefEntry methodDefEntry = EntryParser.parse(node.getUserData(Keys.METHOD_DEFINITION)); - final Comment[] baseComments = getComments(node, $ -> methodDefEntry); - List comments = new ArrayList<>(); - if (baseComments != null) - Collections.addAll(comments, baseComments); - - for (ParameterDeclaration dec : node.getChildrenByRole(Roles.PARAMETER)) { - ParameterDefinition def = dec.getUserData(Keys.PARAMETER_DEFINITION); - final Comment[] paramComments = getParameterComments(dec, $ -> new LocalVariableDefEntry(methodDefEntry, def.getSlot(), def.getName(), - true, - EntryParser.parseTypeDescriptor(def.getParameterType()), null)); - if (paramComments != null) - Collections.addAll(comments, paramComments); - } - - if (!comments.isEmpty()) { - if (remapper.getObfResolver().resolveEntry(methodDefEntry, ResolutionStrategy.RESOLVE_ROOT).stream().noneMatch(e -> Objects.equals(e, methodDefEntry))) { - comments.add(0, new Comment("{@inheritDoc}", CommentType.Documentation)); - } - final AstNode oldFirst = node.getFirstChild(); - for (Comment comment : comments) { - node.insertChildBefore(oldFirst, comment, Roles.COMMENT); - } - } - } - - @Override - protected Void visitChildren(AstNode node, Void data) { - for (final AstNode child : node.getChildren()) { - child.acceptVisitor(this, data); - } - return null; - } - - @Override - public Void visitMethodDeclaration(MethodDeclaration node, Void data) { - visitMethod(node); - return super.visitMethodDeclaration(node, data); - } - - @Override - public Void visitConstructorDeclaration(ConstructorDeclaration node, Void data) { - visitMethod(node); - return super.visitConstructorDeclaration(node, data); - } - - @Override - public Void visitFieldDeclaration(FieldDeclaration node, Void data) { - addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.FIELD_DEFINITION))); - return super.visitFieldDeclaration(node, data); - } - - @Override - public Void visitTypeDeclaration(TypeDeclaration node, Void data) { - addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.TYPE_DEFINITION))); - return super.visitTypeDeclaration(node, data); - } - - @Override - public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void data) { - addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.FIELD_DEFINITION))); - return super.visitEnumValueDeclaration(node, data); - } - } -} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java deleted file mode 100644 index 39e599d3..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java +++ /dev/null @@ -1,33 +0,0 @@ -package cuchaz.enigma.source.procyon.transformers; - -import com.strobel.decompiler.languages.java.ast.AstNode; -import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; -import com.strobel.decompiler.languages.java.ast.ImportDeclaration; -import com.strobel.decompiler.languages.java.ast.PackageDeclaration; -import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; - -public final class DropImportAstTransform implements IAstTransform { - public static final DropImportAstTransform INSTANCE = new DropImportAstTransform(); - - private DropImportAstTransform() { - } - - @Override - public void run(AstNode compilationUnit) { - compilationUnit.acceptVisitor(new Visitor(), null); - } - - static class Visitor extends DepthFirstAstVisitor { - @Override - public Void visitPackageDeclaration(PackageDeclaration node, Void data) { - node.remove(); - return null; - } - - @Override - public Void visitImportDeclaration(ImportDeclaration node, Void data) { - node.remove(); - return null; - } - } -} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java deleted file mode 100644 index b8c087b9..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java +++ /dev/null @@ -1,37 +0,0 @@ -package cuchaz.enigma.source.procyon.transformers; - -import com.strobel.decompiler.languages.java.ast.*; -import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; - -import javax.lang.model.element.Modifier; - -public final class DropVarModifiersAstTransform implements IAstTransform { - public static final DropVarModifiersAstTransform INSTANCE = new DropVarModifiersAstTransform(); - - private DropVarModifiersAstTransform() { - } - - @Override - public void run(AstNode compilationUnit) { - compilationUnit.acceptVisitor(new Visitor(), null); - } - - static class Visitor extends DepthFirstAstVisitor { - @Override - public Void visitParameterDeclaration(ParameterDeclaration node, Void data) { - for (JavaModifierToken modifierToken : node.getChildrenByRole(EntityDeclaration.MODIFIER_ROLE)) { - if (modifierToken.getModifier() == Modifier.FINAL) { - modifierToken.remove(); - } - } - - return null; - } - - @Override - public Void visitVariableDeclaration(VariableDeclarationStatement node, Void data) { - node.removeModifier(Modifier.FINAL); - return null; - } - } -} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java deleted file mode 100644 index 34d95fa5..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java +++ /dev/null @@ -1,29 +0,0 @@ -package cuchaz.enigma.source.procyon.transformers; - -import com.strobel.decompiler.languages.java.ast.AstNode; -import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; -import com.strobel.decompiler.languages.java.ast.Identifier; -import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; - -/** - * Created by Thiakil on 13/07/2018. - */ -public class InvalidIdentifierFix implements IAstTransform { - @Override - public void run(AstNode compilationUnit) { - compilationUnit.acceptVisitor(new Visitor(), null); - } - - class Visitor extends DepthFirstAstVisitor{ - @Override - public Void visitIdentifier(Identifier node, Void data) { - super.visitIdentifier(node, data); - if (node.getName().equals("do") || node.getName().equals("if")){ - Identifier newIdentifier = Identifier.create(node.getName() + "_", node.getStartLocation()); - newIdentifier.copyUserDataFrom(node); - node.replaceWith(newIdentifier); - } - return null; - } - } -} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java deleted file mode 100644 index 8accfc7c..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java +++ /dev/null @@ -1,107 +0,0 @@ -package cuchaz.enigma.source.procyon.transformers; - -import com.strobel.assembler.metadata.BuiltinTypes; -import com.strobel.assembler.metadata.CommonTypeReferences; -import com.strobel.assembler.metadata.Flags; -import com.strobel.assembler.metadata.IGenericInstance; -import com.strobel.assembler.metadata.IMemberDefinition; -import com.strobel.assembler.metadata.JvmType; -import com.strobel.assembler.metadata.MemberReference; -import com.strobel.assembler.metadata.MethodDefinition; -import com.strobel.assembler.metadata.TypeDefinition; -import com.strobel.assembler.metadata.TypeReference; -import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression; -import com.strobel.decompiler.languages.java.ast.AstNode; -import com.strobel.decompiler.languages.java.ast.AstNodeCollection; -import com.strobel.decompiler.languages.java.ast.AstType; -import com.strobel.decompiler.languages.java.ast.CastExpression; -import com.strobel.decompiler.languages.java.ast.ComposedType; -import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; -import com.strobel.decompiler.languages.java.ast.Expression; -import com.strobel.decompiler.languages.java.ast.Identifier; -import com.strobel.decompiler.languages.java.ast.InvocationExpression; -import com.strobel.decompiler.languages.java.ast.Keys; -import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; -import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; -import com.strobel.decompiler.languages.java.ast.Roles; -import com.strobel.decompiler.languages.java.ast.SimpleType; -import com.strobel.decompiler.languages.java.ast.WildcardType; -import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; - -/** - * Created by Thiakil on 12/07/2018. - */ -public class Java8Generics implements IAstTransform { - - @Override - public void run(AstNode compilationUnit) { - compilationUnit.acceptVisitor(new Visitor(), null); - } - - static class Visitor extends DepthFirstAstVisitor{ - - @Override - public Void visitInvocationExpression(InvocationExpression node, Void data) { - super.visitInvocationExpression(node, data); - if (node.getTarget() instanceof MemberReferenceExpression){ - MemberReferenceExpression referenceExpression = (MemberReferenceExpression) node.getTarget(); - if (referenceExpression.getTypeArguments().stream().map(t->{ - TypeReference tr = t.toTypeReference(); - if (tr.getDeclaringType() != null){//ensure that inner types are resolved so we can get the TypeDefinition below - TypeReference resolved = tr.resolve(); - if (resolved != null) - return resolved; - } - return tr; - }).anyMatch(t -> t.isWildcardType() || (t instanceof TypeDefinition && ((TypeDefinition) t).isAnonymous()))) { - //these are invalid for invocations, let the compiler work it out - referenceExpression.getTypeArguments().clear(); - } else if (referenceExpression.getTypeArguments().stream().allMatch(t->t.toTypeReference().equals(CommonTypeReferences.Object))){ - //all are , thereby redundant and/or bad - referenceExpression.getTypeArguments().clear(); - } - } - return null; - } - - @Override - public Void visitObjectCreationExpression(ObjectCreationExpression node, Void data) { - super.visitObjectCreationExpression(node, data); - AstType type = node.getType(); - if (type instanceof SimpleType && !((SimpleType) type).getTypeArguments().isEmpty()){ - SimpleType simpleType = (SimpleType) type; - AstNodeCollection typeArguments = simpleType.getTypeArguments(); - if (typeArguments.size() == 1 && typeArguments.firstOrNullObject().toTypeReference().equals(CommonTypeReferences.Object)){ - //all are , thereby redundant and/or bad - typeArguments.firstOrNullObject().getChildByRole(Roles.IDENTIFIER).replaceWith(Identifier.create("")); - } - } - return null; - } - - @Override - public Void visitCastExpression(CastExpression node, Void data) { - boolean doReplace = false; - TypeReference typeReference = node.getType().toTypeReference(); - if (typeReference.isArray() && typeReference.getElementType().isGenericType()){ - doReplace = true; - } else if (typeReference.isGenericType()) { - Expression target = node.getExpression(); - if (typeReference instanceof IGenericInstance && ((IGenericInstance)typeReference).getTypeArguments().stream().anyMatch(t->t.isWildcardType())){ - doReplace = true; - } else if (target instanceof InvocationExpression) { - InvocationExpression invocationExpression = (InvocationExpression)target; - if (invocationExpression.getTarget() instanceof MemberReferenceExpression && !((MemberReferenceExpression) invocationExpression.getTarget()).getTypeArguments().isEmpty()) { - ((MemberReferenceExpression) invocationExpression.getTarget()).getTypeArguments().clear(); - doReplace = true; - } - } - } - super.visitCastExpression(node, data); - if (doReplace){ - node.replaceWith(node.getExpression()); - } - return null; - } - } -} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java deleted file mode 100644 index 32bb72f4..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java +++ /dev/null @@ -1,414 +0,0 @@ -/* - * Originally: - * EnumSwitchRewriterTransform.java - * - * Copyright (c) 2013 Mike Strobel - * - * This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain; - * and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa. - * - * This source code is subject to terms and conditions of the Apache License, Version 2.0. - * A copy of the license can be found in the License.html file at the root of this distribution. - * By using this source code in any fashion, you are agreeing to be bound by the terms of the - * Apache License, Version 2.0. - * - * You must not remove this notice, or any other, from this software. - */ - -package cuchaz.enigma.source.procyon.transformers; - -import com.strobel.assembler.metadata.BuiltinTypes; -import com.strobel.assembler.metadata.FieldDefinition; -import com.strobel.assembler.metadata.MethodDefinition; -import com.strobel.assembler.metadata.TypeDefinition; -import com.strobel.assembler.metadata.TypeReference; -import com.strobel.core.SafeCloseable; -import com.strobel.core.VerifyArgument; -import com.strobel.decompiler.DecompilerContext; -import com.strobel.decompiler.languages.java.ast.AssignmentExpression; -import com.strobel.decompiler.languages.java.ast.AstBuilder; -import com.strobel.decompiler.languages.java.ast.AstNode; -import com.strobel.decompiler.languages.java.ast.CaseLabel; -import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; -import com.strobel.decompiler.languages.java.ast.Expression; -import com.strobel.decompiler.languages.java.ast.IdentifierExpression; -import com.strobel.decompiler.languages.java.ast.IndexerExpression; -import com.strobel.decompiler.languages.java.ast.InvocationExpression; -import com.strobel.decompiler.languages.java.ast.Keys; -import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; -import com.strobel.decompiler.languages.java.ast.PrimitiveExpression; -import com.strobel.decompiler.languages.java.ast.SwitchSection; -import com.strobel.decompiler.languages.java.ast.SwitchStatement; -import com.strobel.decompiler.languages.java.ast.TypeDeclaration; -import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression; -import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; - -import java.util.ArrayList; -import java.util.IdentityHashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * Copy of {@link com.strobel.decompiler.languages.java.ast.transforms.EnumSwitchRewriterTransform} modified to: - * - Not rely on a field containing "$SwitchMap$" (Proguard strips it) - * - Ignore classes *with* SwitchMap$ names (so the original can handle it) - * - Ignores inner synthetics that are not package private - */ -@SuppressWarnings("Duplicates") -public class ObfuscatedEnumSwitchRewriterTransform implements IAstTransform { - private final DecompilerContext _context; - - public ObfuscatedEnumSwitchRewriterTransform(final DecompilerContext context) { - _context = VerifyArgument.notNull(context, "context"); - } - - @Override - public void run(final AstNode compilationUnit) { - compilationUnit.acceptVisitor(new Visitor(_context), null); - } - - private final static class Visitor extends ContextTrackingVisitor { - private final static class SwitchMapInfo { - final String enclosingType; - final Map> switches = new LinkedHashMap<>(); - final Map> mappings = new LinkedHashMap<>(); - - TypeDeclaration enclosingTypeDeclaration; - - SwitchMapInfo(final String enclosingType) { - this.enclosingType = enclosingType; - } - } - - private final Map _switchMaps = new LinkedHashMap<>(); - private boolean _isSwitchMapWrapper; - - protected Visitor(final DecompilerContext context) { - super(context); - } - - @Override - public Void visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Void p) { - final boolean oldIsSwitchMapWrapper = _isSwitchMapWrapper; - final TypeDefinition typeDefinition = typeDeclaration.getUserData(Keys.TYPE_DEFINITION); - final boolean isSwitchMapWrapper = isSwitchMapWrapper(typeDefinition); - - if (isSwitchMapWrapper) { - final String internalName = typeDefinition.getInternalName(); - - SwitchMapInfo info = _switchMaps.get(internalName); - - if (info == null) { - _switchMaps.put(internalName, info = new SwitchMapInfo(internalName)); - } - - info.enclosingTypeDeclaration = typeDeclaration; - } - - _isSwitchMapWrapper = isSwitchMapWrapper; - - try { - super.visitTypeDeclaration(typeDeclaration, p); - } - finally { - _isSwitchMapWrapper = oldIsSwitchMapWrapper; - } - - rewrite(); - - return null; - } - - @Override - public Void visitSwitchStatement(final SwitchStatement node, final Void data) { - final Expression test = node.getExpression(); - - if (test instanceof IndexerExpression) { - final IndexerExpression indexer = (IndexerExpression) test; - final Expression array = indexer.getTarget(); - final Expression argument = indexer.getArgument(); - - if (!(array instanceof MemberReferenceExpression)) { - return super.visitSwitchStatement(node, data); - } - - final MemberReferenceExpression arrayAccess = (MemberReferenceExpression) array; - final Expression arrayOwner = arrayAccess.getTarget(); - final String mapName = arrayAccess.getMemberName(); - - if (mapName == null || mapName.startsWith("$SwitchMap$") || !(arrayOwner instanceof TypeReferenceExpression)) { - return super.visitSwitchStatement(node, data); - } - - final TypeReferenceExpression enclosingTypeExpression = (TypeReferenceExpression) arrayOwner; - final TypeReference enclosingType = enclosingTypeExpression.getType().getUserData(Keys.TYPE_REFERENCE); - - if (!isSwitchMapWrapper(enclosingType) || !(argument instanceof InvocationExpression)) { - return super.visitSwitchStatement(node, data); - } - - final InvocationExpression invocation = (InvocationExpression) argument; - final Expression invocationTarget = invocation.getTarget(); - - if (!(invocationTarget instanceof MemberReferenceExpression)) { - return super.visitSwitchStatement(node, data); - } - - final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget; - - if (!"ordinal".equals(memberReference.getMemberName())) { - return super.visitSwitchStatement(node, data); - } - - final String enclosingTypeName = enclosingType.getInternalName(); - - SwitchMapInfo info = _switchMaps.get(enclosingTypeName); - - if (info == null) { - _switchMaps.put(enclosingTypeName, info = new SwitchMapInfo(enclosingTypeName)); - - final TypeDefinition resolvedType = enclosingType.resolve(); - - if (resolvedType != null) { - AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER); - - if (astBuilder == null) { - astBuilder = new AstBuilder(context); - } - - try (final SafeCloseable importSuppression = astBuilder.suppressImports()) { - final TypeDeclaration declaration = astBuilder.createType(resolvedType); - - declaration.acceptVisitor(this, data); - } - } - } - - List switches = info.switches.get(mapName); - - if (switches == null) { - info.switches.put(mapName, switches = new ArrayList<>()); - } - - switches.add(node); - } - - return super.visitSwitchStatement(node, data); - } - - @Override - public Void visitAssignmentExpression(final AssignmentExpression node, final Void data) { - final TypeDefinition currentType = context.getCurrentType(); - final MethodDefinition currentMethod = context.getCurrentMethod(); - - if (_isSwitchMapWrapper && - currentType != null && - currentMethod != null && - currentMethod.isTypeInitializer()) { - - final Expression left = node.getLeft(); - final Expression right = node.getRight(); - - if (left instanceof IndexerExpression && - right instanceof PrimitiveExpression) { - - String mapName = null; - - final Expression array = ((IndexerExpression) left).getTarget(); - final Expression argument = ((IndexerExpression) left).getArgument(); - - if (array instanceof MemberReferenceExpression) { - mapName = ((MemberReferenceExpression) array).getMemberName(); - } - else if (array instanceof IdentifierExpression) { - mapName = ((IdentifierExpression) array).getIdentifier(); - } - - if (mapName == null || mapName.startsWith("$SwitchMap$")) { - return super.visitAssignmentExpression(node, data); - } - - if (!(argument instanceof InvocationExpression)) { - return super.visitAssignmentExpression(node, data); - } - - final InvocationExpression invocation = (InvocationExpression) argument; - final Expression invocationTarget = invocation.getTarget(); - - if (!(invocationTarget instanceof MemberReferenceExpression)) { - return super.visitAssignmentExpression(node, data); - } - - final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget; - final Expression memberTarget = memberReference.getTarget(); - - if (!(memberTarget instanceof MemberReferenceExpression) || !"ordinal".equals(memberReference.getMemberName())) { - return super.visitAssignmentExpression(node, data); - } - - final MemberReferenceExpression outerMemberReference = (MemberReferenceExpression) memberTarget; - final Expression outerMemberTarget = outerMemberReference.getTarget(); - - if (!(outerMemberTarget instanceof TypeReferenceExpression)) { - return super.visitAssignmentExpression(node, data); - } - - final String enclosingType = currentType.getInternalName(); - - SwitchMapInfo info = _switchMaps.get(enclosingType); - - if (info == null) { - _switchMaps.put(enclosingType, info = new SwitchMapInfo(enclosingType)); - - AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER); - - if (astBuilder == null) { - astBuilder = new AstBuilder(context); - } - - info.enclosingTypeDeclaration = astBuilder.createType(currentType); - } - - final PrimitiveExpression value = (PrimitiveExpression) right; - - assert value.getValue() instanceof Integer; - - Map mapping = info.mappings.get(mapName); - - if (mapping == null) { - info.mappings.put(mapName, mapping = new LinkedHashMap<>()); - } - - final IdentifierExpression enumValue = new IdentifierExpression( Expression.MYSTERY_OFFSET, outerMemberReference.getMemberName()); - - enumValue.putUserData(Keys.MEMBER_REFERENCE, outerMemberReference.getUserData(Keys.MEMBER_REFERENCE)); - - mapping.put(((Number) value.getValue()).intValue(), enumValue); - } - } - - return super.visitAssignmentExpression(node, data); - } - - private void rewrite() { - if (_switchMaps.isEmpty()) { - return; - } - - for (final SwitchMapInfo info : _switchMaps.values()) { - rewrite(info); - } - - // - // Remove switch map type wrappers that are no longer referenced. - // - - outer: - for (final SwitchMapInfo info : _switchMaps.values()) { - for (final String mapName : info.switches.keySet()) { - final List switches = info.switches.get(mapName); - - if (switches != null && !switches.isEmpty()) { - continue outer; - } - } - - final TypeDeclaration enclosingTypeDeclaration = info.enclosingTypeDeclaration; - - if (enclosingTypeDeclaration != null) { - enclosingTypeDeclaration.remove(); - } - } - } - - private void rewrite(final SwitchMapInfo info) { - if (info.switches.isEmpty()) { - return; - } - - for (final String mapName : info.switches.keySet()) { - final List switches = info.switches.get(mapName); - final Map mappings = info.mappings.get(mapName); - - if (switches != null && mappings != null) { - for (int i = 0; i < switches.size(); i++) { - if (rewriteSwitch(switches.get(i), mappings)) { - switches.remove(i--); - } - } - } - } - } - - private boolean rewriteSwitch(final SwitchStatement s, final Map mappings) { - final Map replacements = new IdentityHashMap<>(); - - for (final SwitchSection section : s.getSwitchSections()) { - for (final CaseLabel caseLabel : section.getCaseLabels()) { - final Expression expression = caseLabel.getExpression(); - - if (expression.isNull()) { - continue; - } - - if (expression instanceof PrimitiveExpression) { - final Object value = ((PrimitiveExpression) expression).getValue(); - - if (value instanceof Integer) { - final Expression replacement = mappings.get(value); - - if (replacement != null) { - replacements.put(expression, replacement); - continue; - } - } - } - - // - // If we can't rewrite all cases, we abort. - // - - return false; - } - } - - final IndexerExpression indexer = (IndexerExpression) s.getExpression(); - final InvocationExpression argument = (InvocationExpression) indexer.getArgument(); - final MemberReferenceExpression memberReference = (MemberReferenceExpression) argument.getTarget(); - final Expression newTest = memberReference.getTarget(); - - newTest.remove(); - indexer.replaceWith(newTest); - - for (final Map.Entry entry : replacements.entrySet()) { - entry.getKey().replaceWith(entry.getValue().clone()); - } - - return true; - } - - private static boolean isSwitchMapWrapper(final TypeReference type) { - if (type == null) { - return false; - } - - final TypeDefinition definition = type instanceof TypeDefinition ? (TypeDefinition) type - : type.resolve(); - - if (definition == null || !definition.isSynthetic() || !definition.isInnerClass() || !definition.isPackagePrivate()) { - return false; - } - - for (final FieldDefinition field : definition.getDeclaredFields()) { - if (!field.getName().startsWith("$SwitchMap$") && - BuiltinTypes.Integer.makeArrayType().equals(field.getFieldType())) { - - return true; - } - } - - return false; - } - } -} \ No newline at end of file diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java deleted file mode 100644 index cf0376f3..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java +++ /dev/null @@ -1,39 +0,0 @@ -package cuchaz.enigma.source.procyon.transformers; - -import com.strobel.assembler.metadata.BuiltinTypes; -import com.strobel.decompiler.DecompilerContext; -import com.strobel.decompiler.languages.java.ast.AstNode; -import com.strobel.decompiler.languages.java.ast.CastExpression; -import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; -import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; - -/** - * Created by Thiakil on 11/07/2018. - */ -public class RemoveObjectCasts implements IAstTransform { - private final DecompilerContext _context; - - public RemoveObjectCasts(DecompilerContext context) { - _context = context; - } - - @Override - public void run(AstNode compilationUnit) { - compilationUnit.acceptVisitor(new Visitor(_context), null); - } - - private final static class Visitor extends ContextTrackingVisitor{ - - protected Visitor(DecompilerContext context) { - super(context); - } - - @Override - public Void visitCastExpression(CastExpression node, Void data) { - if (node.getType().toTypeReference().equals(BuiltinTypes.Object)){ - node.replaceWith(node.getExpression()); - } - return super.visitCastExpression(node, data); - } - } -} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java deleted file mode 100644 index d3ddaab6..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java +++ /dev/null @@ -1,197 +0,0 @@ -package cuchaz.enigma.source.procyon.transformers; - -import com.strobel.assembler.metadata.MemberReference; -import com.strobel.assembler.metadata.MetadataFilters; -import com.strobel.assembler.metadata.MetadataHelper; -import com.strobel.assembler.metadata.MethodBinder; -import com.strobel.assembler.metadata.MethodDefinition; -import com.strobel.assembler.metadata.MethodReference; -import com.strobel.assembler.metadata.TypeReference; -import com.strobel.core.StringUtilities; -import com.strobel.core.VerifyArgument; -import com.strobel.decompiler.DecompilerContext; -import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression; -import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression; -import com.strobel.decompiler.languages.java.ast.AstNode; -import com.strobel.decompiler.languages.java.ast.AstNodeCollection; -import com.strobel.decompiler.languages.java.ast.CastExpression; -import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; -import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; -import com.strobel.decompiler.languages.java.ast.Expression; -import com.strobel.decompiler.languages.java.ast.InvocationExpression; -import com.strobel.decompiler.languages.java.ast.JavaResolver; -import com.strobel.decompiler.languages.java.ast.Keys; -import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; -import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; -import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; -import com.strobel.decompiler.semantics.ResolveResult; - -import java.util.ArrayList; -import java.util.List; - -/** - * Created by Thiakil on 12/07/2018. - */ -public class VarargsFixer implements IAstTransform { - private final DecompilerContext _context; - - public VarargsFixer(final DecompilerContext context) { - _context = VerifyArgument.notNull(context, "context"); - } - - @Override - public void run(AstNode compilationUnit) { - compilationUnit.acceptVisitor(new Visitor(_context), null); - } - - class Visitor extends ContextTrackingVisitor { - private final JavaResolver _resolver; - protected Visitor(DecompilerContext context) { - super(context); - _resolver = new JavaResolver(context); - } - - //remove `new Object[0]` on varagrs as the normal tranformer doesnt do them - @Override - public Void visitInvocationExpression(InvocationExpression node, Void data) { - super.visitInvocationExpression(node, data); - MemberReference definition = node.getUserData(Keys.MEMBER_REFERENCE); - if (definition instanceof MethodDefinition && ((MethodDefinition) definition).isVarArgs()){ - AstNodeCollection arguments = node.getArguments(); - Expression lastParam = arguments.lastOrNullObject(); - if (!lastParam.isNull() && lastParam instanceof ArrayCreationExpression){ - ArrayCreationExpression varargArray = (ArrayCreationExpression)lastParam; - if (varargArray.getInitializer().isNull() || varargArray.getInitializer().getElements().isEmpty()){ - lastParam.remove(); - } else { - for (Expression e : varargArray.getInitializer().getElements()){ - arguments.insertBefore(varargArray, e.clone()); - } - varargArray.remove(); - } - } - } - return null; - } - - //applies the vararg transform to object creation - @Override - public Void visitObjectCreationExpression(ObjectCreationExpression node, Void data) { - super.visitObjectCreationExpression(node, data); - final AstNodeCollection arguments = node.getArguments(); - final Expression lastArgument = arguments.lastOrNullObject(); - - Expression arrayArg = lastArgument; - - if (arrayArg instanceof CastExpression) - arrayArg = ((CastExpression) arrayArg).getExpression(); - - if (arrayArg == null || - arrayArg.isNull() || - !(arrayArg instanceof ArrayCreationExpression && - node.getTarget() instanceof MemberReferenceExpression)) { - - return null; - } - - final ArrayCreationExpression newArray = (ArrayCreationExpression) arrayArg; - final MemberReferenceExpression target = (MemberReferenceExpression) node.getTarget(); - - if (!newArray.getAdditionalArraySpecifiers().hasSingleElement()) { - return null; - } - - final MethodReference method = (MethodReference) node.getUserData(Keys.MEMBER_REFERENCE); - - if (method == null) { - return null; - } - - final MethodDefinition resolved = method.resolve(); - - if (resolved == null || !resolved.isVarArgs()) { - return null; - } - - final List candidates; - final Expression invocationTarget = target.getTarget(); - - if (invocationTarget == null || invocationTarget.isNull()) { - candidates = MetadataHelper.findMethods( - context.getCurrentType(), - MetadataFilters.matchName(resolved.getName()) - ); - } - else { - final ResolveResult targetResult = _resolver.apply(invocationTarget); - - if (targetResult == null || targetResult.getType() == null) { - return null; - } - - candidates = MetadataHelper.findMethods( - targetResult.getType(), - MetadataFilters.matchName(resolved.getName()) - ); - } - - final List argTypes = new ArrayList<>(); - - for (final Expression argument : arguments) { - final ResolveResult argResult = _resolver.apply(argument); - - if (argResult == null || argResult.getType() == null) { - return null; - } - - argTypes.add(argResult.getType()); - } - - final MethodBinder.BindResult c1 = MethodBinder.selectMethod(candidates, argTypes); - - if (c1.isFailure() || c1.isAmbiguous()) { - return null; - } - - argTypes.remove(argTypes.size() - 1); - - final ArrayInitializerExpression initializer = newArray.getInitializer(); - final boolean hasElements = !initializer.isNull() && !initializer.getElements().isEmpty(); - - if (hasElements) { - for (final Expression argument : initializer.getElements()) { - final ResolveResult argResult = _resolver.apply(argument); - - if (argResult == null || argResult.getType() == null) { - return null; - } - - argTypes.add(argResult.getType()); - } - } - - final MethodBinder.BindResult c2 = MethodBinder.selectMethod(candidates, argTypes); - - if (c2.isFailure() || - c2.isAmbiguous() || - !StringUtilities.equals(c2.getMethod().getErasedSignature(), c1.getMethod().getErasedSignature())) { - - return null; - } - - lastArgument.remove(); - - if (!hasElements) { - lastArgument.remove(); - return null; - } - - for (final Expression newArg : initializer.getElements()) { - newArg.remove(); - arguments.add(newArg); - } - - return null; - } - } -} diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java deleted file mode 100644 index e702956e..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java +++ /dev/null @@ -1,33 +0,0 @@ -package cuchaz.enigma.source.procyon.typeloader; - -import com.strobel.assembler.metadata.Buffer; -import com.strobel.assembler.metadata.ClasspathTypeLoader; -import com.strobel.assembler.metadata.ITypeLoader; - -/** - * Caching version of {@link ClasspathTypeLoader} - */ -public class CachingClasspathTypeLoader extends CachingTypeLoader { - private static ITypeLoader extraClassPathLoader = null; - - public static void setExtraClassPathLoader(ITypeLoader loader){ - extraClassPathLoader = loader; - } - - private final ITypeLoader classpathLoader = new ClasspathTypeLoader(); - - @Override - protected byte[] doLoad(String className) { - Buffer parentBuf = new Buffer(); - if (classpathLoader.tryLoadType(className, parentBuf)) { - return parentBuf.array(); - } - if (extraClassPathLoader != null){ - parentBuf.reset(); - if (extraClassPathLoader.tryLoadType(className, parentBuf)){ - return parentBuf.array(); - } - } - return EMPTY_ARRAY;//need to return *something* as null means no store - } -} diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java deleted file mode 100644 index 5be5ddd9..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java +++ /dev/null @@ -1,38 +0,0 @@ -package cuchaz.enigma.source.procyon.typeloader; - -import com.google.common.collect.Maps; -import com.strobel.assembler.metadata.Buffer; -import com.strobel.assembler.metadata.ITypeLoader; - -import java.util.Map; - -/** - * Common cache functions - */ -public abstract class CachingTypeLoader implements ITypeLoader { - protected static final byte[] EMPTY_ARRAY = {}; - - private final Map cache = Maps.newHashMap(); - - protected abstract byte[] doLoad(String className); - - @Override - public boolean tryLoadType(String className, Buffer out) { - - // check the cache - byte[] data = this.cache.computeIfAbsent(className, this::doLoad); - - if (data == EMPTY_ARRAY) { - return false; - } - - out.reset(data.length); - System.arraycopy(data, 0, out.array(), out.position(), data.length); - out.position(0); - return true; - } - - public void clearCache() { - this.cache.clear(); - } -} diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java deleted file mode 100644 index e703d3b3..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java +++ /dev/null @@ -1,140 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.source.procyon.typeloader; - -import com.google.common.collect.Lists; -import com.strobel.assembler.metadata.Buffer; -import com.strobel.assembler.metadata.ITypeLoader; -import cuchaz.enigma.ClassProvider; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.MethodNode; - -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; -import java.util.function.Function; - -public class CompiledSourceTypeLoader extends CachingTypeLoader { - //Store one instance as the classpath shouldn't change during load - private static final ITypeLoader CLASSPATH_TYPE_LOADER = new CachingClasspathTypeLoader(); - - private final ClassProvider compiledSource; - private final LinkedList> visitors = new LinkedList<>(); - - public CompiledSourceTypeLoader(ClassProvider compiledSource) { - this.compiledSource = compiledSource; - } - - public void addVisitor(Function visitor) { - this.visitors.addFirst(visitor); - } - - @Override - protected byte[] doLoad(String className) { - byte[] data = loadType(className); - if (data == null) { - return loadClasspath(className); - } - - return data; - } - - private byte[] loadClasspath(String name) { - Buffer parentBuf = new Buffer(); - if (CLASSPATH_TYPE_LOADER.tryLoadType(name, parentBuf)) { - return parentBuf.array(); - } - return EMPTY_ARRAY; - } - - private byte[] loadType(String className) { - ClassEntry entry = new ClassEntry(className); - - // find the class in the jar - ClassNode node = findClassNode(entry); - if (node == null) { - // couldn't find it - return null; - } - - removeRedundantClassCalls(node); - - ClassWriter writer = new ClassWriter(0); - - ClassVisitor visitor = writer; - for (Function visitorFunction : this.visitors) { - visitor = visitorFunction.apply(visitor); - } - - node.accept(visitor); - - // we have a transformed class! - return writer.toByteArray(); - } - - private void removeRedundantClassCalls(ClassNode node) { - // remove .getClass() calls that are seemingly injected - // DUP - // INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class; - // POP - for (MethodNode methodNode : node.methods) { - AbstractInsnNode insnNode = methodNode.instructions.getFirst(); - while (insnNode != null) { - if (insnNode instanceof MethodInsnNode && insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) { - MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; - if (methodInsnNode.name.equals("getClass") && methodInsnNode.owner.equals("java/lang/Object") && methodInsnNode.desc.equals("()Ljava/lang/Class;")) { - AbstractInsnNode previous = methodInsnNode.getPrevious(); - AbstractInsnNode next = methodInsnNode.getNext(); - if (previous.getOpcode() == Opcodes.DUP && next.getOpcode() == Opcodes.POP) { - insnNode = previous.getPrevious();//reset the iterator so it gets the new next instruction - methodNode.instructions.remove(previous); - methodNode.instructions.remove(methodInsnNode); - methodNode.instructions.remove(next); - } - } - } - insnNode = insnNode.getNext(); - } - } - } - - private ClassNode findClassNode(ClassEntry entry) { - // try to find the class in the jar - for (String className : getClassNamesToTry(entry)) { - ClassNode node = compiledSource.getClassNode(className); - if (node != null) { - return node; - } - } - - // didn't find it ;_; - return null; - } - - private Collection getClassNamesToTry(ClassEntry entry) { - List classNamesToTry = Lists.newArrayList(); - classNamesToTry.add(entry.getFullName()); - - ClassEntry outerClass = entry.getOuterClass(); - if (outerClass != null) { - classNamesToTry.addAll(getClassNamesToTry(outerClass)); - } - - return classNamesToTry; - } -} diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java deleted file mode 100644 index c4732b04..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java +++ /dev/null @@ -1,38 +0,0 @@ -package cuchaz.enigma.source.procyon.typeloader; - -import com.strobel.assembler.metadata.ITypeLoader; -import com.strobel.assembler.metadata.MetadataSystem; -import com.strobel.assembler.metadata.TypeDefinition; -import com.strobel.assembler.metadata.TypeReference; - -import java.util.Collections; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -public final class NoRetryMetadataSystem extends MetadataSystem { - private final Set failedTypes = Collections.newSetFromMap(new ConcurrentHashMap<>()); - - public NoRetryMetadataSystem(final ITypeLoader typeLoader) { - super(typeLoader); - } - - @Override - protected synchronized TypeDefinition resolveType(final String descriptor, final boolean mightBePrimitive) { - if (failedTypes.contains(descriptor)) { - return null; - } - - final TypeDefinition result = super.resolveType(descriptor, mightBePrimitive); - - if (result == null) { - failedTypes.add(descriptor); - } - - return result; - } - - @Override - public synchronized TypeDefinition resolve(final TypeReference type) { - return super.resolve(type); - } -} diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java deleted file mode 100644 index 86c6ecc6..00000000 --- a/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java +++ /dev/null @@ -1,20 +0,0 @@ -package cuchaz.enigma.source.procyon.typeloader; - -import com.strobel.assembler.metadata.Buffer; -import com.strobel.assembler.metadata.ITypeLoader; - -/** - * Typeloader with synchronized tryLoadType method - */ -public class SynchronizedTypeLoader implements ITypeLoader { - private final ITypeLoader delegate; - - public SynchronizedTypeLoader(ITypeLoader delegate) { - this.delegate = delegate; - } - - @Override - public synchronized boolean tryLoadType(String internalName, Buffer buffer) { - return delegate.tryLoadType(internalName, buffer); - } -} diff --git a/src/main/java/cuchaz/enigma/throwables/IllegalNameException.java b/src/main/java/cuchaz/enigma/throwables/IllegalNameException.java deleted file mode 100644 index 0155ad27..00000000 --- a/src/main/java/cuchaz/enigma/throwables/IllegalNameException.java +++ /dev/null @@ -1,39 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.throwables; - -public class IllegalNameException extends RuntimeException { - - private String name; - private String reason; - - public IllegalNameException(String name, String reason) { - this.name = name; - this.reason = reason; - } - - public String getReason() { - return this.reason; - } - - @Override - public String getMessage() { - StringBuilder buf = new StringBuilder(); - buf.append("Illegal name: "); - buf.append(this.name); - if (this.reason != null) { - buf.append(" because "); - buf.append(this.reason); - } - return buf.toString(); - } -} diff --git a/src/main/java/cuchaz/enigma/throwables/MappingConflict.java b/src/main/java/cuchaz/enigma/throwables/MappingConflict.java deleted file mode 100644 index 95cd4494..00000000 --- a/src/main/java/cuchaz/enigma/throwables/MappingConflict.java +++ /dev/null @@ -1,7 +0,0 @@ -package cuchaz.enigma.throwables; - -public class MappingConflict extends Exception { - public MappingConflict(String clazz, String name, String nameExisting) { - super(String.format("Conflicting mappings found for %s. The mapping file is %s and the second is %s", clazz, name, nameExisting)); - } -} diff --git a/src/main/java/cuchaz/enigma/throwables/MappingParseException.java b/src/main/java/cuchaz/enigma/throwables/MappingParseException.java deleted file mode 100644 index b7e6d426..00000000 --- a/src/main/java/cuchaz/enigma/throwables/MappingParseException.java +++ /dev/null @@ -1,39 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.throwables; - -import java.io.File; -import java.util.function.Supplier; - -public class MappingParseException extends Exception { - - private int line; - private String message; - private String filePath; - - public MappingParseException(File file, int line, String message) { - this.line = line; - this.message = message; - filePath = file.getAbsolutePath(); - } - - public MappingParseException(Supplier filenameProvider, int line, String message) { - this.line = line; - this.message = message; - filePath = filenameProvider.get(); - } - - @Override - public String getMessage() { - return "Line " + line + ": " + message + " in file " + filePath; - } -} diff --git a/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java b/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java deleted file mode 100644 index 18c966cd..00000000 --- a/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java +++ /dev/null @@ -1,44 +0,0 @@ -package cuchaz.enigma.translation; - -import cuchaz.enigma.translation.mapping.NameValidator; -import cuchaz.enigma.translation.representation.TypeDescriptor; - -import java.util.Collection; -import java.util.Locale; - -public class LocalNameGenerator { - public static String generateArgumentName(int index, TypeDescriptor desc, Collection arguments) { - boolean uniqueType = arguments.stream().filter(desc::equals).count() <= 1; - String translatedName; - int nameIndex = index + 1; - StringBuilder nameBuilder = new StringBuilder(getTypeName(desc)); - if (!uniqueType || NameValidator.isReserved(nameBuilder.toString())) { - nameBuilder.append(nameIndex); - } - translatedName = nameBuilder.toString(); - return translatedName; - } - - public static String generateLocalVariableName(int index, TypeDescriptor desc) { - int nameIndex = index + 1; - return getTypeName(desc) + nameIndex; - } - - private static String getTypeName(TypeDescriptor desc) { - // Unfortunately each of these have different name getters, so they have different code paths - if (desc.isPrimitive()) { - TypeDescriptor.Primitive argCls = desc.getPrimitive(); - return argCls.name().toLowerCase(Locale.ROOT); - } else if (desc.isArray()) { - // List types would require this whole block again, so just go with aListx - return "arr"; - } else if (desc.isType()) { - String typeName = desc.getTypeEntry().getSimpleName().replace("$", ""); - typeName = typeName.substring(0, 1).toLowerCase(Locale.ROOT) + typeName.substring(1); - return typeName; - } else { - System.err.println("Encountered invalid argument type descriptor " + desc.toString()); - return "var"; - } - } -} diff --git a/src/main/java/cuchaz/enigma/translation/MappingTranslator.java b/src/main/java/cuchaz/enigma/translation/MappingTranslator.java deleted file mode 100644 index 529d0edb..00000000 --- a/src/main/java/cuchaz/enigma/translation/MappingTranslator.java +++ /dev/null @@ -1,24 +0,0 @@ -package cuchaz.enigma.translation; - -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.mapping.EntryMap; - -public class MappingTranslator implements Translator { - private final EntryMap mappings; - private final EntryResolver resolver; - - public MappingTranslator(EntryMap mappings, EntryResolver resolver) { - this.mappings = mappings; - this.resolver = resolver; - } - - @SuppressWarnings("unchecked") - @Override - public T translate(T translatable) { - if (translatable == null) { - return null; - } - return (T) translatable.translate(this, resolver, mappings); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java b/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java deleted file mode 100644 index 37830535..00000000 --- a/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java +++ /dev/null @@ -1,92 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.translation; - -import com.google.common.collect.Lists; - -import java.io.IOException; -import java.io.StringReader; -import java.util.List; - -public class SignatureUpdater { - - public static String update(String signature, ClassNameUpdater updater) { - try { - StringBuilder buf = new StringBuilder(); - - // read the signature character-by-character - StringReader reader = new StringReader(signature); - int i; - while ((i = reader.read()) != -1) { - char c = (char) i; - - // does this character start a class name? - if (c == 'L') { - // update the class name and add it to the buffer - buf.append('L'); - String className = readClass(reader); - if (className == null) { - throw new IllegalArgumentException("Malformed signature: " + signature); - } - buf.append(updater.update(className)); - buf.append(';'); - } else { - // copy the character into the buffer - buf.append(c); - } - } - - return buf.toString(); - } catch (IOException ex) { - // I'm pretty sure a StringReader will never throw one of these - throw new Error(ex); - } - } - - private static String readClass(StringReader reader) throws IOException { - // read all the characters in the buffer until we hit a ';' - // remember to treat generics correctly - StringBuilder buf = new StringBuilder(); - int depth = 0; - int i; - while ((i = reader.read()) != -1) { - char c = (char) i; - - if (c == '<') { - depth++; - } else if (c == '>') { - depth--; - } else if (depth == 0) { - if (c == ';') { - return buf.toString(); - } else { - buf.append(c); - } - } - } - - return null; - } - - public static List getClasses(String signature) { - final List classNames = Lists.newArrayList(); - update(signature, className -> { - classNames.add(className); - return className; - }); - return classNames; - } - - public interface ClassNameUpdater { - String update(String className); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/Translatable.java b/src/main/java/cuchaz/enigma/translation/Translatable.java deleted file mode 100644 index 0370ef13..00000000 --- a/src/main/java/cuchaz/enigma/translation/Translatable.java +++ /dev/null @@ -1,9 +0,0 @@ -package cuchaz.enigma.translation; - -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.mapping.EntryMap; - -public interface Translatable { - Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings); -} diff --git a/src/main/java/cuchaz/enigma/translation/TranslationDirection.java b/src/main/java/cuchaz/enigma/translation/TranslationDirection.java deleted file mode 100644 index 2ecb30be..00000000 --- a/src/main/java/cuchaz/enigma/translation/TranslationDirection.java +++ /dev/null @@ -1,36 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.translation; - -public enum TranslationDirection { - - DEOBFUSCATING { - @Override - public T choose(T deobfChoice, T obfChoice) { - if (deobfChoice == null) { - return obfChoice; - } - return deobfChoice; - } - }, - OBFUSCATING { - @Override - public T choose(T deobfChoice, T obfChoice) { - if (obfChoice == null) { - return deobfChoice; - } - return obfChoice; - } - }; - - public abstract T choose(T deobfChoice, T obfChoice); -} diff --git a/src/main/java/cuchaz/enigma/translation/Translator.java b/src/main/java/cuchaz/enigma/translation/Translator.java deleted file mode 100644 index c70141f2..00000000 --- a/src/main/java/cuchaz/enigma/translation/Translator.java +++ /dev/null @@ -1,61 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.translation; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -public interface Translator { - T translate(T translatable); - - default Collection translate(Collection translatable) { - return translatable.stream() - .map(this::translate) - .collect(Collectors.toList()); - } - - default Set translate(Set translatable) { - return translatable.stream() - .map(this::translate) - .collect(Collectors.toSet()); - } - - default Map translateKeys(Map translatable) { - Map result = new HashMap<>(translatable.size()); - for (Map.Entry entry : translatable.entrySet()) { - result.put(translate(entry.getKey()), entry.getValue()); - } - return result; - } - - default Map translate(Map translatable) { - Map result = new HashMap<>(translatable.size()); - for (Map.Entry entry : translatable.entrySet()) { - result.put(translate(entry.getKey()), translate(entry.getValue())); - } - return result; - } - - default Multimap translate(Multimap translatable) { - Multimap result = HashMultimap.create(translatable.size(), 1); - for (Map.Entry> entry : translatable.asMap().entrySet()) { - result.putAll(translate(entry.getKey()), translate(entry.getValue())); - } - return result; - } -} diff --git a/src/main/java/cuchaz/enigma/translation/VoidTranslator.java b/src/main/java/cuchaz/enigma/translation/VoidTranslator.java deleted file mode 100644 index c010833b..00000000 --- a/src/main/java/cuchaz/enigma/translation/VoidTranslator.java +++ /dev/null @@ -1,10 +0,0 @@ -package cuchaz.enigma.translation; - -public enum VoidTranslator implements Translator { - INSTANCE; - - @Override - public T translate(T translatable) { - return translatable; - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java b/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java deleted file mode 100644 index 5b79b794..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java +++ /dev/null @@ -1,25 +0,0 @@ -package cuchaz.enigma.translation.mapping; - -import cuchaz.enigma.translation.representation.AccessFlags; - -public enum AccessModifier { - UNCHANGED, PUBLIC, PROTECTED, PRIVATE; - - public String getFormattedName() { - return "ACC:" + super.toString(); - } - - public AccessFlags transform(AccessFlags access) { - switch (this) { - case PUBLIC: - return access.setPublic(); - case PROTECTED: - return access.setProtected(); - case PRIVATE: - return access.setPrivate(); - case UNCHANGED: - default: - return access; - } - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java deleted file mode 100644 index e1a32533..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java +++ /dev/null @@ -1,24 +0,0 @@ -package cuchaz.enigma.translation.mapping; - -import cuchaz.enigma.translation.representation.entry.Entry; - -import javax.annotation.Nullable; -import java.util.stream.Stream; - -public interface EntryMap { - void insert(Entry entry, T value); - - @Nullable - T remove(Entry entry); - - @Nullable - T get(Entry entry); - - default boolean contains(Entry entry) { - return get(entry) != null; - } - - Stream> getAllEntries(); - - boolean isEmpty(); -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java deleted file mode 100644 index c607817c..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java +++ /dev/null @@ -1,75 +0,0 @@ -package cuchaz.enigma.translation.mapping; - -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 EntryMapping(@Nonnull String targetName) { - this(targetName, AccessModifier.UNCHANGED); - } - - public EntryMapping(@Nonnull String targetName, @Nullable String javadoc) { - this(targetName, AccessModifier.UNCHANGED, javadoc); - } - - public EntryMapping(@Nonnull 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); - } - - public EntryMapping withModifier(AccessModifier newModifier) { - return new EntryMapping(targetName, newModifier, javadoc); - } - - 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/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java deleted file mode 100644 index ad36c97f..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java +++ /dev/null @@ -1,105 +0,0 @@ -package cuchaz.enigma.translation.mapping; - -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.translation.MappingTranslator; -import cuchaz.enigma.translation.Translatable; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree; -import cuchaz.enigma.translation.mapping.tree.EntryTree; -import cuchaz.enigma.translation.mapping.tree.HashEntryTree; -import cuchaz.enigma.translation.representation.entry.Entry; - -import javax.annotation.Nullable; -import java.util.Collection; -import java.util.function.UnaryOperator; -import java.util.stream.Stream; - -public class EntryRemapper { - private final DeltaTrackingTree obfToDeobf; - - private final EntryResolver obfResolver; - private final Translator deobfuscator; - - private final MappingValidator validator; - - private EntryRemapper(JarIndex jarIndex, EntryTree obfToDeobf) { - this.obfToDeobf = new DeltaTrackingTree<>(obfToDeobf); - - this.obfResolver = jarIndex.getEntryResolver(); - - this.deobfuscator = new MappingTranslator(obfToDeobf, obfResolver); - - this.validator = new MappingValidator(obfToDeobf, deobfuscator, jarIndex); - } - - public static EntryRemapper mapped(JarIndex index, EntryTree obfToDeobf) { - return new EntryRemapper(index, obfToDeobf); - } - - public static EntryRemapper empty(JarIndex index) { - return new EntryRemapper(index, new HashEntryTree<>()); - } - - public > void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping) { - mapFromObf(obfuscatedEntry, deobfMapping, true); - } - - public > void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping, boolean renaming) { - Collection resolvedEntries = obfResolver.resolveEntry(obfuscatedEntry, renaming ? ResolutionStrategy.RESOLVE_ROOT : ResolutionStrategy.RESOLVE_CLOSEST); - - if (renaming && deobfMapping != null) { - for (E resolvedEntry : resolvedEntries) { - validator.validateRename(resolvedEntry, deobfMapping.getTargetName()); - } - } - - for (E resolvedEntry : resolvedEntries) { - obfToDeobf.insert(resolvedEntry, deobfMapping); - } - } - - public void removeByObf(Entry obfuscatedEntry) { - mapFromObf(obfuscatedEntry, null); - } - - @Nullable - public EntryMapping getDeobfMapping(Entry entry) { - return obfToDeobf.get(entry); - } - - public boolean hasDeobfMapping(Entry obfEntry) { - return obfToDeobf.contains(obfEntry); - } - - public T deobfuscate(T translatable) { - return deobfuscator.translate(translatable); - } - - public Translator getDeobfuscator() { - return deobfuscator; - } - - public Stream> getObfEntries() { - return obfToDeobf.getAllEntries(); - } - - public Collection> getObfChildren(Entry obfuscatedEntry) { - return obfToDeobf.getChildren(obfuscatedEntry); - } - - public DeltaTrackingTree getObfToDeobf() { - return obfToDeobf; - } - - public MappingDelta takeMappingDelta() { - return obfToDeobf.takeDelta(); - } - - public boolean isDirty() { - return obfToDeobf.isDirty(); - } - - public EntryResolver getObfResolver() { - return obfResolver; - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java deleted file mode 100644 index 521f72d0..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java +++ /dev/null @@ -1,41 +0,0 @@ -package cuchaz.enigma.translation.mapping; - -import com.google.common.collect.Streams; -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; - -import java.util.Collection; -import java.util.Set; -import java.util.stream.Collectors; - -public interface EntryResolver { - > Collection resolveEntry(E entry, ResolutionStrategy strategy); - - default > E resolveFirstEntry(E entry, ResolutionStrategy strategy) { - return resolveEntry(entry, strategy).stream().findFirst().orElse(entry); - } - - default , C extends Entry> Collection> resolveReference(EntryReference reference, ResolutionStrategy strategy) { - Collection entry = resolveEntry(reference.entry, strategy); - if (reference.context != null) { - Collection context = resolveEntry(reference.context, strategy); - return Streams.zip(entry.stream(), context.stream(), (e, c) -> new EntryReference<>(e, c, reference)) - .collect(Collectors.toList()); - } else { - return entry.stream() - .map(e -> new EntryReference<>(e, null, reference)) - .collect(Collectors.toList()); - } - } - - default , C extends Entry> EntryReference resolveFirstReference(EntryReference reference, ResolutionStrategy strategy) { - E entry = resolveFirstEntry(reference.entry, strategy); - C context = resolveFirstEntry(reference.context, strategy); - return new EntryReference<>(entry, context, reference); - } - - Set> resolveEquivalentEntries(Entry entry); - - Set resolveEquivalentMethods(MethodEntry methodEntry); -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java b/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java deleted file mode 100644 index 78231ddd..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java +++ /dev/null @@ -1,227 +0,0 @@ -package cuchaz.enigma.translation.mapping; - -import com.google.common.collect.Sets; -import cuchaz.enigma.analysis.IndexTreeBuilder; -import cuchaz.enigma.analysis.MethodImplementationsTreeNode; -import cuchaz.enigma.analysis.MethodInheritanceTreeNode; -import cuchaz.enigma.analysis.index.BridgeMethodIndex; -import cuchaz.enigma.analysis.index.EntryIndex; -import cuchaz.enigma.analysis.index.InheritanceIndex; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.translation.VoidTranslator; -import cuchaz.enigma.translation.representation.AccessFlags; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; - -import javax.annotation.Nullable; -import java.util.*; -import java.util.stream.Collectors; - -public class IndexEntryResolver implements EntryResolver { - private final EntryIndex entryIndex; - private final InheritanceIndex inheritanceIndex; - private final BridgeMethodIndex bridgeMethodIndex; - - private final IndexTreeBuilder treeBuilder; - - public IndexEntryResolver(JarIndex index) { - this.entryIndex = index.getEntryIndex(); - this.inheritanceIndex = index.getInheritanceIndex(); - this.bridgeMethodIndex = index.getBridgeMethodIndex(); - - this.treeBuilder = new IndexTreeBuilder(index); - } - - @Override - @SuppressWarnings("unchecked") - public > Collection resolveEntry(E entry, ResolutionStrategy strategy) { - if (entry == null) { - return Collections.emptySet(); - } - - Entry classChild = getClassChild(entry); - if (classChild != null && !(classChild instanceof ClassEntry)) { - AccessFlags access = entryIndex.getEntryAccess(classChild); - - // If we're looking for the closest and this entry exists, we're done looking - if (strategy == ResolutionStrategy.RESOLVE_CLOSEST && access != null) { - return Collections.singleton(entry); - } - - if (access == null || !access.isPrivate()) { - Collection> resolvedChildren = resolveChildEntry(classChild, strategy); - if (!resolvedChildren.isEmpty()) { - return resolvedChildren.stream() - .map(resolvedChild -> (E) entry.replaceAncestor(classChild, resolvedChild)) - .collect(Collectors.toList()); - } - } - } - - return Collections.singleton(entry); - } - - @Nullable - private Entry getClassChild(Entry entry) { - if (entry instanceof ClassEntry) { - return null; - } - - // get the entry in the hierarchy that is the child of a class - List> ancestry = entry.getAncestry(); - for (int i = ancestry.size() - 1; i > 0; i--) { - Entry child = ancestry.get(i); - Entry cast = child.castParent(ClassEntry.class); - if (cast != null && !(cast instanceof ClassEntry)) { - // we found the entry which is a child of a class, we are now able to resolve the owner of this entry - return cast; - } - } - - return null; - } - - private Set> resolveChildEntry(Entry entry, ResolutionStrategy strategy) { - ClassEntry ownerClass = entry.getParent(); - - if (entry instanceof MethodEntry) { - MethodEntry bridgeMethod = bridgeMethodIndex.getBridgeFromSpecialized((MethodEntry) entry); - if (bridgeMethod != null && ownerClass.equals(bridgeMethod.getParent())) { - Set> resolvedBridge = resolveChildEntry(bridgeMethod, strategy); - if (!resolvedBridge.isEmpty()) { - return resolvedBridge; - } else { - return Collections.singleton(bridgeMethod); - } - } - } - - Set> resolvedEntries = new HashSet<>(); - - for (ClassEntry parentClass : inheritanceIndex.getParents(ownerClass)) { - Entry parentEntry = entry.withParent(parentClass); - - if (strategy == ResolutionStrategy.RESOLVE_ROOT) { - resolvedEntries.addAll(resolveRoot(parentEntry, strategy)); - } else { - resolvedEntries.addAll(resolveClosest(parentEntry, strategy)); - } - } - - return resolvedEntries; - } - - private Collection> resolveRoot(Entry entry, ResolutionStrategy strategy) { - // When resolving root, we want to first look for the lowest entry before returning ourselves - Set> parentResolution = resolveChildEntry(entry, strategy); - - if (parentResolution.isEmpty()) { - AccessFlags parentAccess = entryIndex.getEntryAccess(entry); - if (parentAccess != null && !parentAccess.isPrivate()) { - return Collections.singleton(entry); - } - } - - return parentResolution; - } - - private Collection> resolveClosest(Entry entry, ResolutionStrategy strategy) { - // When resolving closest, we want to first check if we exist before looking further down - AccessFlags parentAccess = entryIndex.getEntryAccess(entry); - if (parentAccess != null && !parentAccess.isPrivate()) { - return Collections.singleton(entry); - } else { - return resolveChildEntry(entry, strategy); - } - } - - @Override - public Set> resolveEquivalentEntries(Entry entry) { - MethodEntry relevantMethod = entry.findAncestor(MethodEntry.class); - if (relevantMethod == null || !entryIndex.hasMethod(relevantMethod)) { - return Collections.singleton(entry); - } - - Set equivalentMethods = resolveEquivalentMethods(relevantMethod); - Set> equivalentEntries = new HashSet<>(equivalentMethods.size()); - - for (MethodEntry equivalentMethod : equivalentMethods) { - Entry equivalentEntry = entry.replaceAncestor(relevantMethod, equivalentMethod); - equivalentEntries.add(equivalentEntry); - } - - return equivalentEntries; - } - - @Override - public Set resolveEquivalentMethods(MethodEntry methodEntry) { - AccessFlags access = entryIndex.getMethodAccess(methodEntry); - if (access == null) { - throw new IllegalArgumentException("Could not find method " + methodEntry); - } - - if (!canInherit(methodEntry, access)) { - return Collections.singleton(methodEntry); - } - - Set methodEntries = Sets.newHashSet(); - resolveEquivalentMethods(methodEntries, treeBuilder.buildMethodInheritance(VoidTranslator.INSTANCE, methodEntry)); - return methodEntries; - } - - private void resolveEquivalentMethods(Set methodEntries, MethodInheritanceTreeNode node) { - MethodEntry methodEntry = node.getMethodEntry(); - if (methodEntries.contains(methodEntry)) { - return; - } - - AccessFlags flags = entryIndex.getMethodAccess(methodEntry); - if (flags != null && canInherit(methodEntry, flags)) { - // collect the entry - methodEntries.add(methodEntry); - } - - // look at bridge methods! - MethodEntry bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(methodEntry); - while (bridgedMethod != null) { - methodEntries.addAll(resolveEquivalentMethods(bridgedMethod)); - bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(bridgedMethod); - } - - // look at interface methods too - for (MethodImplementationsTreeNode implementationsNode : treeBuilder.buildMethodImplementations(VoidTranslator.INSTANCE, methodEntry)) { - resolveEquivalentMethods(methodEntries, implementationsNode); - } - - // recurse - for (int i = 0; i < node.getChildCount(); i++) { - resolveEquivalentMethods(methodEntries, (MethodInheritanceTreeNode) node.getChildAt(i)); - } - } - - private void resolveEquivalentMethods(Set methodEntries, MethodImplementationsTreeNode node) { - MethodEntry methodEntry = node.getMethodEntry(); - AccessFlags flags = entryIndex.getMethodAccess(methodEntry); - if (flags != null && !flags.isPrivate() && !flags.isStatic()) { - // collect the entry - methodEntries.add(methodEntry); - } - - // look at bridge methods! - MethodEntry bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(methodEntry); - while (bridgedMethod != null) { - methodEntries.addAll(resolveEquivalentMethods(bridgedMethod)); - bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(bridgedMethod); - } - - // recurse - for (int i = 0; i < node.getChildCount(); i++) { - resolveEquivalentMethods(methodEntries, (MethodImplementationsTreeNode) node.getChildAt(i)); - } - } - - private boolean canInherit(MethodEntry entry, AccessFlags access) { - return !entry.isConstructor() && !access.isPrivate() && !access.isStatic() && !access.isFinal(); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java deleted file mode 100644 index 1407bb60..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java +++ /dev/null @@ -1,54 +0,0 @@ -package cuchaz.enigma.translation.mapping; - -import cuchaz.enigma.translation.Translatable; -import cuchaz.enigma.translation.Translator; -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; - -import java.util.stream.Stream; - -public class MappingDelta implements Translatable { - public static final Object PLACEHOLDER = new Object(); - - private final EntryTree baseMappings; - - private final EntryTree changes; - - public MappingDelta(EntryTree baseMappings, EntryTree changes) { - this.baseMappings = baseMappings; - this.changes = changes; - } - - public MappingDelta(EntryTree baseMappings) { - this(baseMappings, new HashEntryTree<>()); - } - - public static MappingDelta added(EntryTree mappings) { - EntryTree changes = new HashEntryTree<>(); - mappings.getAllEntries().forEach(entry -> changes.insert(entry, PLACEHOLDER)); - - return new MappingDelta<>(new HashEntryTree<>(), changes); - } - - public EntryTree getBaseMappings() { - return baseMappings; - } - - public EntryTree getChanges() { - return changes; - } - - public Stream> getChangedRoots() { - return changes.getRootNodes().map(EntryTreeNode::getEntry); - } - - @Override - public MappingDelta translate(Translator translator, EntryResolver resolver, EntryMap mappings) { - return new MappingDelta<>( - translator.translate(baseMappings), - translator.translate(changes) - ); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingFileNameFormat.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingFileNameFormat.java deleted file mode 100644 index e40bfe75..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/MappingFileNameFormat.java +++ /dev/null @@ -1,10 +0,0 @@ -package cuchaz.enigma.translation.mapping; - -import com.google.gson.annotations.SerializedName; - -public enum MappingFileNameFormat { - @SerializedName("by_obf") - BY_OBF, - @SerializedName("by_deobf") - BY_DEOBF -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java deleted file mode 100644 index 5d39e3d2..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java +++ /dev/null @@ -1,32 +0,0 @@ -package cuchaz.enigma.translation.mapping; - -import cuchaz.enigma.translation.representation.entry.Entry; - -import javax.annotation.Nullable; - -public class MappingPair, M> { - private final E entry; - private M mapping; - - public MappingPair(E entry, @Nullable M mapping) { - this.entry = entry; - this.mapping = mapping; - } - - public MappingPair(E entry) { - this(entry, null); - } - - public E getEntry() { - return entry; - } - - @Nullable - public M getMapping() { - return mapping; - } - - public void setMapping(M mapping) { - this.mapping = mapping; - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingSaveParameters.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingSaveParameters.java deleted file mode 100644 index 07065d6a..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/MappingSaveParameters.java +++ /dev/null @@ -1,16 +0,0 @@ -package cuchaz.enigma.translation.mapping; - -import com.google.gson.annotations.SerializedName; - -public class MappingSaveParameters { - @SerializedName("file_name_format") - private final MappingFileNameFormat fileNameFormat; - - public MappingSaveParameters(MappingFileNameFormat fileNameFormat) { - this.fileNameFormat = fileNameFormat; - } - - public MappingFileNameFormat getFileNameFormat() { - return fileNameFormat; - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java deleted file mode 100644 index dffcb0c6..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java +++ /dev/null @@ -1,76 +0,0 @@ -package cuchaz.enigma.translation.mapping; - -import cuchaz.enigma.analysis.index.InheritanceIndex; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.throwables.IllegalNameException; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.tree.EntryTree; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.Entry; - -import java.util.Collection; -import java.util.HashSet; -import java.util.stream.Collectors; - -public class MappingValidator { - private final EntryTree obfToDeobf; - private final Translator deobfuscator; - private final JarIndex index; - - public MappingValidator(EntryTree obfToDeobf, Translator deobfuscator, JarIndex index) { - this.obfToDeobf = obfToDeobf; - this.deobfuscator = deobfuscator; - this.index = index; - } - - public void validateRename(Entry entry, String name) throws IllegalNameException { - Collection> equivalentEntries = index.getEntryResolver().resolveEquivalentEntries(entry); - for (Entry equivalentEntry : equivalentEntries) { - equivalentEntry.validateName(name); - validateUnique(equivalentEntry, name); - } - } - - private void validateUnique(Entry entry, String name) { - ClassEntry containingClass = entry.getContainingClass(); - Collection relatedClasses = getRelatedClasses(containingClass); - - for (ClassEntry relatedClass : relatedClasses) { - Entry relatedEntry = entry.replaceAncestor(containingClass, relatedClass); - Entry translatedEntry = deobfuscator.translate(relatedEntry); - - Collection> translatedSiblings = obfToDeobf.getSiblings(relatedEntry).stream() - .map(deobfuscator::translate) - .collect(Collectors.toList()); - - if (!isUnique(translatedEntry, translatedSiblings, name)) { - Entry parent = translatedEntry.getParent(); - if (parent != null) { - throw new IllegalNameException(name, "Name is not unique in " + parent + "!"); - } else { - throw new IllegalNameException(name, "Name is not unique!"); - } - } - } - } - - private Collection getRelatedClasses(ClassEntry classEntry) { - InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); - - Collection relatedClasses = new HashSet<>(); - relatedClasses.add(classEntry); - relatedClasses.addAll(inheritanceIndex.getChildren(classEntry)); - relatedClasses.addAll(inheritanceIndex.getAncestors(classEntry)); - - return relatedClasses; - } - - private boolean isUnique(Entry entry, Collection> siblings, String name) { - for (Entry sibling : siblings) { - if (entry.canConflictWith(sibling) && sibling.getName().equals(name)) { - return false; - } - } - return true; - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java deleted file mode 100644 index 5d9794fb..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java +++ /dev/null @@ -1,99 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.translation.mapping; - -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.analysis.index.JarIndex; -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 java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; - -public class MappingsChecker { - private final JarIndex index; - private final EntryTree mappings; - - public MappingsChecker(JarIndex index, EntryTree mappings) { - this.index = index; - this.mappings = mappings; - } - - public Dropped dropBrokenMappings(ProgressListener progress) { - Dropped dropped = new Dropped(); - - Collection> obfEntries = mappings.getAllEntries() - .filter(e -> e instanceof ClassEntry || e instanceof MethodEntry || e instanceof FieldEntry || e instanceof LocalVariableEntry) - .collect(Collectors.toList()); - - progress.init(obfEntries.size(), "Checking for dropped mappings"); - - int steps = 0; - for (Entry entry : obfEntries) { - progress.step(steps++, entry.toString()); - tryDropEntry(dropped, entry); - } - - dropped.apply(mappings); - - return dropped; - } - - private void tryDropEntry(Dropped dropped, Entry entry) { - if (shouldDropEntry(entry)) { - EntryMapping mapping = mappings.get(entry); - if (mapping != null) { - dropped.drop(entry, mapping); - } - } - } - - private boolean shouldDropEntry(Entry entry) { - if (!index.getEntryIndex().hasEntry(entry)) { - return true; - } - Collection> resolvedEntries = index.getEntryResolver().resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT); - return !resolvedEntries.contains(entry); - } - - public static class Dropped { - private final Map, String> droppedMappings = new HashMap<>(); - - public void drop(Entry entry, EntryMapping mapping) { - droppedMappings.put(entry, mapping.getTargetName()); - } - - void apply(EntryTree mappings) { - for (Entry entry : droppedMappings.keySet()) { - EntryTreeNode node = mappings.findNode(entry); - if (node == null) { - continue; - } - - for (Entry childEntry : node.getChildrenRecursively()) { - mappings.remove(childEntry); - } - } - } - - public Map, String> getDroppedMappings() { - return droppedMappings; - } - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java b/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java deleted file mode 100644 index 5bc2f67a..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java +++ /dev/null @@ -1,53 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.translation.mapping; - -import cuchaz.enigma.throwables.IllegalNameException; -import cuchaz.enigma.translation.representation.entry.ClassEntry; - -import java.util.Arrays; -import java.util.List; -import java.util.regex.Pattern; - -public class NameValidator { - private static final Pattern IDENTIFIER_PATTERN; - private static final Pattern CLASS_PATTERN; - private static final List ILLEGAL_IDENTIFIERS = Arrays.asList( - "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", - "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", - "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", - "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally", - "long", "strictfp", "volatile", "const", "float", "native", "super", "while", "_" - ); - - static { - String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*"; - IDENTIFIER_PATTERN = Pattern.compile(identifierRegex); - CLASS_PATTERN = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex)); - } - - public static void validateClassName(String name) { - if (!CLASS_PATTERN.matcher(name).matches() || ILLEGAL_IDENTIFIERS.contains(name)) { - throw new IllegalNameException(name, "This doesn't look like a legal class name"); - } - } - - public static void validateIdentifier(String name) { - if (!IDENTIFIER_PATTERN.matcher(name).matches() || ILLEGAL_IDENTIFIERS.contains(name)) { - throw new IllegalNameException(name, "This doesn't look like a legal identifier"); - } - } - - public static boolean isReserved(String name) { - return ILLEGAL_IDENTIFIERS.contains(name); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java b/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java deleted file mode 100644 index 1c28e028..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java +++ /dev/null @@ -1,6 +0,0 @@ -package cuchaz.enigma.translation.mapping; - -public enum ResolutionStrategy { - RESOLVE_ROOT, - RESOLVE_CLOSEST -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java b/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java deleted file mode 100644 index 2eab55fd..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java +++ /dev/null @@ -1,27 +0,0 @@ -package cuchaz.enigma.translation.mapping; - -import cuchaz.enigma.translation.representation.entry.Entry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; - -import java.util.Collection; -import java.util.Collections; -import java.util.Set; - -public enum VoidEntryResolver implements EntryResolver { - INSTANCE; - - @Override - public > Collection resolveEntry(E entry, ResolutionStrategy strategy) { - return Collections.singleton(entry); - } - - @Override - public Set> resolveEquivalentEntries(Entry entry) { - return Collections.singleton(entry); - } - - @Override - public Set resolveEquivalentMethods(MethodEntry methodEntry) { - return Collections.singleton(methodEntry); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaFormat.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaFormat.java deleted file mode 100644 index af92ffbe..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaFormat.java +++ /dev/null @@ -1,9 +0,0 @@ -package cuchaz.enigma.translation.mapping.serde; - -public class EnigmaFormat { - public static final String COMMENT = "COMMENT"; - public static final String CLASS = "CLASS"; - public static final String FIELD = "FIELD"; - public static final String METHOD = "METHOD"; - public static final String PARAMETER = "ARG"; -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java deleted file mode 100644 index 53bbaa3b..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java +++ /dev/null @@ -1,319 +0,0 @@ -package cuchaz.enigma.translation.mapping.serde; - -import com.google.common.base.Charsets; -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.throwables.MappingParseException; -import cuchaz.enigma.translation.mapping.AccessModifier; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.MappingPair; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; -import cuchaz.enigma.translation.mapping.tree.EntryTree; -import cuchaz.enigma.translation.mapping.tree.HashEntryTree; -import cuchaz.enigma.translation.representation.MethodDescriptor; -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; -import java.util.stream.Collectors; - -public enum EnigmaMappingsReader implements MappingsReader { - FILE { - @Override - public EntryTree read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException { - progress.init(1, I18n.translate("progress.mappings.enigma_file.loading")); - - EntryTree mappings = new HashEntryTree<>(); - readFile(path, mappings); - - progress.step(1, I18n.translate("progress.mappings.enigma_file.done")); - - return mappings; - } - }, - DIRECTORY { - @Override - public EntryTree read(Path root, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException { - EntryTree mappings = new HashEntryTree<>(); - - List files = Files.walk(root) - .filter(f -> !Files.isDirectory(f)) - .filter(f -> f.toString().endsWith(".mapping")) - .collect(Collectors.toList()); - - progress.init(files.size(), I18n.translate("progress.mappings.enigma_directory.loading")); - int step = 0; - - for (Path file : files) { - progress.step(step++, root.relativize(file).toString()); - if (Files.isHidden(file)) { - continue; - } - readFile(file, mappings); - } - - return mappings; - } - }, - ZIP { - @Override - public EntryTree read(Path zip, ProgressListener progress, MappingSaveParameters saveParameters) throws MappingParseException, IOException { - try (FileSystem fs = FileSystems.newFileSystem(zip, (ClassLoader) null)) { - return DIRECTORY.read(fs.getPath("/"), progress, saveParameters); - } - } - }; - - protected void readFile(Path path, EntryTree mappings) throws IOException, MappingParseException { - List lines = Files.readAllLines(path, Charsets.UTF_8); - Deque> mappingStack = new ArrayDeque<>(); - - for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) { - String line = lines.get(lineNumber); - int indentation = countIndentation(line); - - line = formatLine(line); - if (line == null) { - continue; - } - - cleanMappingStack(indentation, mappingStack, mappings); - - try { - MappingPair pair = parseLine(mappingStack.peek(), line); - if (pair != null) { - mappingStack.push(pair); - if (pair.getMapping() != null) { - - } - } - } catch (Throwable t) { - t.printStackTrace(); - throw new MappingParseException(path::toString, lineNumber, t.toString()); - } - } - - // Clean up rest - cleanMappingStack(0, mappingStack, mappings); - } - - private void cleanMappingStack(int indentation, Deque> mappingStack, EntryTree mappings) { - while (indentation < mappingStack.size()) { - MappingPair pair = mappingStack.pop(); - if (pair.getMapping() != null) { - mappings.insert(pair.getEntry(), pair.getMapping().bake()); - } - } - } - - @Nullable - private String formatLine(String line) { - line = stripComment(line); - line = line.trim(); - - if (line.isEmpty()) { - return null; - } - - return line; - } - - private String stripComment(String line) { - //Dont support comments on javadoc lines - if (line.trim().startsWith(EnigmaFormat.COMMENT)) { - return line; - } - - int commentPos = line.indexOf('#'); - if (commentPos >= 0) { - return line.substring(0, commentPos); - } - return line; - } - - private int countIndentation(String line) { - int indent = 0; - for (int i = 0; i < line.length(); i++) { - if (line.charAt(i) != '\t') { - break; - } - indent++; - } - return indent; - } - - private MappingPair parseLine(@Nullable MappingPair parent, String line) { - String[] tokens = line.trim().split("\\s"); - String keyToken = tokens[0].toUpperCase(Locale.ROOT); - Entry parentEntry = parent == null ? null : parent.getEntry(); - - switch (keyToken) { - case EnigmaFormat.CLASS: - return parseClass(parentEntry, tokens); - case EnigmaFormat.FIELD: - return parseField(parentEntry, tokens); - case EnigmaFormat.METHOD: - return parseMethod(parentEntry, tokens); - case EnigmaFormat.PARAMETER: - return parseArgument(parentEntry, tokens); - case EnigmaFormat.COMMENT: - readJavadoc(parent, tokens); - return null; - default: - throw new RuntimeException("Unknown token '" + keyToken + "'"); - } - } - - private 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)) : ""; - if (parent.getMapping() == null) { - parent.setMapping(new RawEntryMapping(parent.getEntry().getName(), AccessModifier.UNCHANGED)); - } - parent.getMapping().addJavadocLine(MappingHelper.unescape(jdLine)); - } - - private MappingPair parseClass(@Nullable Entry parent, String[] tokens) { - String obfuscatedName = ClassEntry.getInnerName(tokens[1]); - ClassEntry obfuscatedEntry; - if (parent instanceof ClassEntry) { - obfuscatedEntry = new ClassEntry((ClassEntry) parent, obfuscatedName); - } else { - obfuscatedEntry = new ClassEntry(obfuscatedName); - } - - String mapping = null; - AccessModifier modifier = AccessModifier.UNCHANGED; - - if (tokens.length == 3) { - AccessModifier parsedModifier = parseModifier(tokens[2]); - if (parsedModifier != null) { - modifier = parsedModifier; - mapping = obfuscatedName; - } else { - mapping = tokens[2]; - } - } else if (tokens.length == 4) { - mapping = tokens[2]; - modifier = parseModifier(tokens[3]); - } - - if (mapping != null) { - return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier)); - } else { - return new MappingPair<>(obfuscatedEntry); - } - } - - private MappingPair parseField(@Nullable Entry parent, String[] tokens) { - if (!(parent instanceof ClassEntry)) { - throw new RuntimeException("Field must be a child of a class!"); - } - - ClassEntry ownerEntry = (ClassEntry) parent; - - String obfuscatedName = tokens[1]; - String mapping = obfuscatedName; - AccessModifier modifier = AccessModifier.UNCHANGED; - TypeDescriptor descriptor; - - if (tokens.length == 3) { - mapping = tokens[1]; - descriptor = new TypeDescriptor(tokens[2]); - } else if (tokens.length == 4) { - AccessModifier parsedModifier = parseModifier(tokens[3]); - if (parsedModifier != null) { - descriptor = new TypeDescriptor(tokens[2]); - modifier = parsedModifier; - } else { - mapping = tokens[2]; - descriptor = new TypeDescriptor(tokens[3]); - } - } else if (tokens.length == 5) { - descriptor = new TypeDescriptor(tokens[3]); - mapping = tokens[2]; - modifier = parseModifier(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); - } - } - - private MappingPair parseMethod(@Nullable Entry parent, String[] tokens) { - if (!(parent instanceof ClassEntry)) { - throw new RuntimeException("Method must be a child of a class!"); - } - - ClassEntry ownerEntry = (ClassEntry) parent; - - String obfuscatedName = tokens[1]; - String mapping = null; - AccessModifier modifier = AccessModifier.UNCHANGED; - MethodDescriptor descriptor; - - if (tokens.length == 3) { - descriptor = new MethodDescriptor(tokens[2]); - } else if (tokens.length == 4) { - AccessModifier parsedModifier = parseModifier(tokens[3]); - if (parsedModifier != null) { - modifier = parsedModifier; - mapping = obfuscatedName; - descriptor = new MethodDescriptor(tokens[2]); - } else { - mapping = tokens[2]; - descriptor = new MethodDescriptor(tokens[3]); - } - } else if (tokens.length == 5) { - mapping = tokens[2]; - modifier = parseModifier(tokens[4]); - descriptor = new MethodDescriptor(tokens[3]); - } else { - throw new RuntimeException("Invalid method declaration"); - } - - MethodEntry obfuscatedEntry = new MethodEntry(ownerEntry, obfuscatedName, descriptor); - if (mapping != null) { - return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier)); - } else { - return new MappingPair<>(obfuscatedEntry); - } - } - - private MappingPair parseArgument(@Nullable Entry parent, String[] tokens) { - if (!(parent instanceof MethodEntry)) { - throw new RuntimeException("Method arg must be a child of a method!"); - } - - MethodEntry ownerEntry = (MethodEntry) parent; - LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerEntry, Integer.parseInt(tokens[1]), "", true, null); - String mapping = tokens[2]; - - return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping)); - } - - @Nullable - private AccessModifier parseModifier(String token) { - if (token.startsWith("ACC:")) { - return AccessModifier.valueOf(token.substring(4)); - } - return null; - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java deleted file mode 100644 index be0fceb5..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java +++ /dev/null @@ -1,316 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.translation.mapping.serde; - -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.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -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.MappingFileNameFormat; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; -import cuchaz.enigma.translation.mapping.VoidEntryResolver; -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.utils.I18n; -import cuchaz.enigma.utils.LFPrintWriter; - -public enum EnigmaMappingsWriter implements MappingsWriter { - FILE { - @Override - public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { - Collection classes = mappings.getRootNodes() - .filter(entry -> entry instanceof ClassEntry) - .map(entry -> (ClassEntry) entry) - .collect(Collectors.toList()); - - progress.init(classes.size(), I18n.translate("progress.mappings.enigma_file.writing")); - - int steps = 0; - try (PrintWriter writer = new LFPrintWriter(Files.newBufferedWriter(path))) { - for (ClassEntry classEntry : classes) { - progress.step(steps++, classEntry.getFullName()); - writeRoot(writer, mappings, classEntry); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - }, - DIRECTORY { - @Override - public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { - Collection changedClasses = delta.getChangedRoots() - .filter(entry -> entry instanceof ClassEntry) - .map(entry -> (ClassEntry) entry) - .collect(Collectors.toList()); - - applyDeletions(path, changedClasses, mappings, delta.getBaseMappings(), saveParameters.getFileNameFormat()); - - progress.init(changedClasses.size(), I18n.translate("progress.mappings.enigma_directory.writing")); - - AtomicInteger steps = new AtomicInteger(); - - Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE); - changedClasses.parallelStream().forEach(classEntry -> { - progress.step(steps.getAndIncrement(), classEntry.getFullName()); - - try { - ClassEntry fileEntry = classEntry; - if (saveParameters.getFileNameFormat() == MappingFileNameFormat.BY_DEOBF) { - fileEntry = translator.translate(fileEntry); - } - - Path classPath = resolve(path, fileEntry); - Files.createDirectories(classPath.getParent()); - Files.deleteIfExists(classPath); - - try (PrintWriter writer = new LFPrintWriter(Files.newBufferedWriter(classPath))) { - writeRoot(writer, mappings, classEntry); - } - } catch (Throwable t) { - System.err.println("Failed to write class '" + classEntry.getFullName() + "'"); - t.printStackTrace(); - } - }); - } - - private void applyDeletions(Path root, Collection changedClasses, EntryTree mappings, EntryTree oldMappings, MappingFileNameFormat fileNameFormat) { - Translator oldMappingTranslator = new MappingTranslator(oldMappings, VoidEntryResolver.INSTANCE); - - Stream deletedClassStream = changedClasses.stream() - .filter(e -> !Objects.equals(oldMappings.get(e), mappings.get(e))); - - if (fileNameFormat == MappingFileNameFormat.BY_DEOBF) { - deletedClassStream = deletedClassStream.map(oldMappingTranslator::translate); - } - - Collection deletedClasses = deletedClassStream.collect(Collectors.toList()); - - for (ClassEntry classEntry : deletedClasses) { - try { - Files.deleteIfExists(resolve(root, classEntry)); - } catch (IOException e) { - System.err.println("Failed to delete deleted class '" + classEntry + "'"); - e.printStackTrace(); - } - } - - for (ClassEntry classEntry : deletedClasses) { - String packageName = classEntry.getPackageName(); - if (packageName != null) { - Path packagePath = Paths.get(packageName); - try { - deleteDeadPackages(root, packagePath); - } catch (IOException e) { - System.err.println("Failed to delete dead package '" + packageName + "'"); - e.printStackTrace(); - } - } - } - } - - private void deleteDeadPackages(Path root, Path packagePath) throws IOException { - for (int i = packagePath.getNameCount() - 1; i >= 0; i--) { - Path subPath = packagePath.subpath(0, i + 1); - Path packagePart = root.resolve(subPath); - if (isEmpty(packagePart)) { - Files.deleteIfExists(packagePart); - } - } - } - - private boolean isEmpty(Path path) { - try (DirectoryStream stream = Files.newDirectoryStream(path)) { - return !stream.iterator().hasNext(); - } catch (IOException e) { - return false; - } - } - - private Path resolve(Path root, ClassEntry classEntry) { - return root.resolve(classEntry.getFullName() + ".mapping"); - } - }, - ZIP { - @Override - public void write(EntryTree mappings, MappingDelta delta, Path zip, ProgressListener progress, MappingSaveParameters saveParameters) { - try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:file", null, zip.toUri().getPath(), ""), Collections.singletonMap("create", "true"))) { - DIRECTORY.write(mappings, delta, fs.getPath("/"), progress, saveParameters); - } catch (IOException e) { - e.printStackTrace(); - } catch (URISyntaxException e) { - throw new RuntimeException("Unexpected error creating URI for " + zip, e); - } - } - }; - - protected void writeRoot(PrintWriter writer, EntryTree mappings, ClassEntry classEntry) { - Collection> children = groupChildren(mappings.getChildren(classEntry)); - - EntryMapping classEntryMapping = mappings.get(classEntry); - - writer.println(writeClass(classEntry, classEntryMapping).trim()); - if (classEntryMapping != null && classEntryMapping.getJavadoc() != 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(); - if (jd != null) { - for (String line : jd.split("\\R")) { - writer.println(indent(EnigmaFormat.COMMENT + " " + MappingHelper.escape(line), depth + 1)); - } - } - } - - protected void writeEntry(PrintWriter writer, EntryTree mappings, Entry entry, int depth) { - EntryTreeNode node = mappings.findNode(entry); - if (node == null) { - return; - } - - 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); - writer.println(indent(line, depth)); - } - if (mapping != null && mapping.getJavadoc() != null) { - writeDocs(writer, mapping, depth); - } - - Collection> children = groupChildren(node.getChildren()); - for (Entry child : children) { - writeEntry(writer, mappings, child, depth + 1); - } - } - - private Collection> groupChildren(Collection> children) { - Collection> result = new ArrayList<>(children.size()); - - children.stream().filter(e -> e instanceof FieldEntry) - .map(e -> (FieldEntry) e) - .sorted() - .forEach(result::add); - - children.stream().filter(e -> e instanceof MethodEntry) - .map(e -> (MethodEntry) e) - .sorted() - .forEach(result::add); - - children.stream().filter(e -> e instanceof LocalVariableEntry) - .map(e -> (LocalVariableEntry) e) - .sorted() - .forEach(result::add); - - children.stream().filter(e -> e instanceof ClassEntry) - .map(e -> (ClassEntry) e) - .sorted() - .forEach(result::add); - - return result; - } - - protected String writeClass(ClassEntry entry, 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) { - StringBuilder builder = new StringBuilder(EnigmaFormat.METHOD + " "); - builder.append(entry.getName()).append(' '); - if (mapping != null && !mapping.getTargetName().equals(entry.getName())) { - writeMapping(builder, mapping); - } - - builder.append(entry.getDesc().toString()); - - return builder.toString(); - } - - protected String writeField(FieldEntry entry, EntryMapping mapping) { - StringBuilder builder = new StringBuilder(EnigmaFormat.FIELD + " "); - builder.append(entry.getName()).append(' '); - if (mapping != null && !mapping.getTargetName().equals(entry.getName())) { - 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(); - } - - 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(' '); - } - } - } - - private String indent(String line, int depth) { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < depth; i++) { - builder.append("\t"); - } - builder.append(line.trim()); - return builder.toString(); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java deleted file mode 100644 index 6c8c3430..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java +++ /dev/null @@ -1,59 +0,0 @@ -package cuchaz.enigma.translation.mapping.serde; - -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.throwables.MappingParseException; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.MappingDelta; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; -import cuchaz.enigma.translation.mapping.tree.EntryTree; - -import javax.annotation.Nullable; -import java.io.IOException; -import java.nio.file.Path; - -public enum MappingFormat { - ENIGMA_FILE(EnigmaMappingsWriter.FILE, EnigmaMappingsReader.FILE), - ENIGMA_DIRECTORY(EnigmaMappingsWriter.DIRECTORY, EnigmaMappingsReader.DIRECTORY), - ENIGMA_ZIP(EnigmaMappingsWriter.ZIP, EnigmaMappingsReader.ZIP), - TINY_V2(new TinyV2Writer("intermediary", "named"), new TinyV2Reader()), - TINY_FILE(TinyMappingsWriter.INSTANCE, TinyMappingsReader.INSTANCE), - SRG_FILE(SrgMappingsWriter.INSTANCE, null), - PROGUARD(null, ProguardMappingsReader.INSTANCE); - - - private final MappingsWriter writer; - private final MappingsReader reader; - - MappingFormat(MappingsWriter writer, MappingsReader reader) { - this.writer = writer; - this.reader = reader; - } - - public void write(EntryTree mappings, Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) { - write(mappings, MappingDelta.added(mappings), path, progressListener, saveParameters); - } - - public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) { - if (writer == null) { - throw new IllegalStateException(name() + " does not support writing"); - } - writer.write(mappings, delta, path, progressListener, saveParameters); - } - - public EntryTree read(Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) throws IOException, MappingParseException { - if (reader == null) { - throw new IllegalStateException(name() + " does not support reading"); - } - return reader.read(path, progressListener, saveParameters); - } - - @Nullable - public MappingsWriter getWriter() { - return writer; - } - - @Nullable - public MappingsReader getReader() { - return reader; - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java deleted file mode 100644 index 7c8f6cc6..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java +++ /dev/null @@ -1,51 +0,0 @@ -package cuchaz.enigma.translation.mapping.serde; - -public final class MappingHelper { - private static final String TO_ESCAPE = "\\\n\r\0\t"; - private static final String ESCAPED = "\\nr0t"; - - public static String escape(String raw) { - StringBuilder builder = new StringBuilder(raw.length() + 1); - for (int i = 0; i < raw.length(); i++) { - final char c = raw.charAt(i); - final int r = TO_ESCAPE.indexOf(c); - if (r < 0) { - builder.append(c); - } else { - builder.append('\\').append(ESCAPED.charAt(r)); - } - } - return builder.toString(); - } - - public static String unescape(String str) { - int pos = str.indexOf('\\'); - if (pos < 0) return str; - - StringBuilder ret = new StringBuilder(str.length() - 1); - int start = 0; - - do { - ret.append(str, start, pos); - pos++; - int type; - - if (pos >= str.length()) { - throw new RuntimeException("incomplete escape sequence at the end"); - } else if ((type = ESCAPED.indexOf(str.charAt(pos))) < 0) { - throw new RuntimeException("invalid escape character: \\" + str.charAt(pos)); - } else { - ret.append(TO_ESCAPE.charAt(type)); - } - - start = pos + 1; - } while ((pos = str.indexOf('\\', start)) >= 0); - - ret.append(str, start, str.length()); - - return ret.toString(); - } - - private MappingHelper() { - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java deleted file mode 100644 index 4c60787d..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java +++ /dev/null @@ -1,14 +0,0 @@ -package cuchaz.enigma.translation.mapping.serde; - -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.throwables.MappingParseException; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; -import cuchaz.enigma.translation.mapping.tree.EntryTree; - -import java.io.IOException; -import java.nio.file.Path; - -public interface MappingsReader { - EntryTree read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws MappingParseException, IOException; -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java deleted file mode 100644 index 88159867..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java +++ /dev/null @@ -1,17 +0,0 @@ -package cuchaz.enigma.translation.mapping.serde; - -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.MappingDelta; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; -import cuchaz.enigma.translation.mapping.tree.EntryTree; - -import java.nio.file.Path; - -public interface MappingsWriter { - void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters); - - default void write(EntryTree mappings, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { - write(mappings, MappingDelta.added(mappings), path, progress, saveParameters); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/ProguardMappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/ProguardMappingsReader.java deleted file mode 100644 index b5ede394..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/ProguardMappingsReader.java +++ /dev/null @@ -1,134 +0,0 @@ -package cuchaz.enigma.translation.mapping.serde; - -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.command.MappingCommandsUtil; -import cuchaz.enigma.throwables.MappingParseException; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; -import cuchaz.enigma.translation.mapping.tree.EntryTree; -import cuchaz.enigma.translation.mapping.tree.HashEntryTree; -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.FieldEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class ProguardMappingsReader implements MappingsReader { - public static final ProguardMappingsReader INSTANCE = new ProguardMappingsReader(); - private static final String NAME = "[a-zA-Z0-9_\\-.$<>]+"; - private static final String TYPE = NAME + "(?:\\[])*"; - private static final String TYPE_LIST = "|(?:(?:" + TYPE + ",)*" + TYPE + ")"; - private static final Pattern CLASS = Pattern.compile("(" + NAME + ") -> (" + NAME + "):"); - private static final Pattern FIELD = Pattern.compile(" {4}(" + TYPE + ") (" + NAME + ") -> (" + NAME + ")"); - private static final Pattern METHOD = Pattern.compile(" {4}(?:[0-9]+:[0-9]+:)?(" + TYPE + ") (" + NAME + ")\\((" + TYPE_LIST + ")\\) -> (" + NAME + ")"); - - public ProguardMappingsReader() {} - - @Override - public EntryTree read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws MappingParseException, IOException { - EntryTree mappings = new HashEntryTree<>(); - - int lineNumber = 0; - ClassEntry currentClass = null; - for (String line : Files.readAllLines(path, StandardCharsets.UTF_8)) { - lineNumber++; - - if (line.startsWith("#") || line.isEmpty()) { - continue; - } - - Matcher classMatcher = CLASS.matcher(line); - Matcher fieldMatcher = FIELD.matcher(line); - Matcher methodMatcher = METHOD.matcher(line); - - if (classMatcher.matches()) { - String name = classMatcher.group(1); - String targetName = classMatcher.group(2); - - mappings.insert(currentClass = new ClassEntry(name.replace('.', '/')), new EntryMapping(ClassEntry.getInnerName(targetName.replace('.', '/')))); - } else if (fieldMatcher.matches()) { - String type = fieldMatcher.group(1); - String name = fieldMatcher.group(2); - String targetName = fieldMatcher.group(3); - - if (currentClass == null) { - throw new MappingParseException(path::toString, lineNumber, "field mapping not inside class: " + line); - } - - mappings.insert(new FieldEntry(currentClass, name, new TypeDescriptor(getDescriptor(type))), new EntryMapping(targetName)); - } else if (methodMatcher.matches()) { - String returnType = methodMatcher.group(1); - String name = methodMatcher.group(2); - String[] parameterTypes = methodMatcher.group(3).isEmpty() ? new String[0] : methodMatcher.group(3).split(","); - String targetName = methodMatcher.group(4); - - if (currentClass == null) { - throw new MappingParseException(path::toString, lineNumber, "method mapping not inside class: " + line); - } - - mappings.insert(new MethodEntry(currentClass, name, new MethodDescriptor(getDescriptor(returnType, parameterTypes))), new EntryMapping(targetName)); - } else { - throw new MappingParseException(path::toString, lineNumber, "invalid mapping line: " + line); - } - } - - return MappingCommandsUtil.invert(mappings); - } - - private String getDescriptor(String type) { - StringBuilder descriptor = new StringBuilder(); - - while (type.endsWith("[]")) { - descriptor.append("["); - type = type.substring(0, type.length() - 2); - } - - switch (type) { - case "byte": - return descriptor + "B"; - case "char": - return descriptor + "C"; - case "short": - return descriptor + "S"; - case "int": - return descriptor + "I"; - case "long": - return descriptor + "J"; - case "float": - return descriptor + "F"; - case "double": - return descriptor + "D"; - case "boolean": - return descriptor + "Z"; - case "void": - return descriptor + "V"; - } - - descriptor.append("L"); - descriptor.append(type.replace('.', '/')); - descriptor.append(";"); - - return descriptor.toString(); - } - - private String getDescriptor(String returnType, String[] parameterTypes) { - StringBuilder descriptor = new StringBuilder(); - descriptor.append('('); - - for (String parameterType : parameterTypes) { - descriptor.append(getDescriptor(parameterType)); - } - - descriptor.append(')'); - descriptor.append(getDescriptor(returnType)); - - return descriptor.toString(); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java deleted file mode 100644 index afb40e9a..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java +++ /dev/null @@ -1,30 +0,0 @@ -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; - -final class RawEntryMapping { - private final String targetName; - private final AccessModifier access; - private List javadocs = new ArrayList<>(); - - RawEntryMapping(String targetName) { - this(targetName, null); - } - - RawEntryMapping(String targetName, AccessModifier access) { - this.access = access; - this.targetName = targetName; - } - - void addJavadocLine(String line) { - javadocs.add(line); - } - - EntryMapping bake() { - return new EntryMapping(targetName, access, javadocs.isEmpty() ? null : String.join("\n", javadocs)); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java deleted file mode 100644 index f67f8fcd..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java +++ /dev/null @@ -1,118 +0,0 @@ -package cuchaz.enigma.translation.mapping.serde; - -import com.google.common.collect.Lists; -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.translation.MappingTranslator; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.MappingDelta; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; -import cuchaz.enigma.translation.mapping.VoidEntryResolver; -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.MethodEntry; -import cuchaz.enigma.utils.I18n; -import cuchaz.enigma.utils.LFPrintWriter; - -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.List; -import java.util.stream.Collectors; - -public enum SrgMappingsWriter implements MappingsWriter { - INSTANCE; - - @Override - public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { - try { - Files.deleteIfExists(path); - Files.createFile(path); - } catch (IOException e) { - e.printStackTrace(); - } - - List classLines = new ArrayList<>(); - List fieldLines = new ArrayList<>(); - List methodLines = new ArrayList<>(); - - Collection> rootEntries = Lists.newArrayList(mappings).stream() - .map(EntryTreeNode::getEntry) - .collect(Collectors.toList()); - progress.init(rootEntries.size(), I18n.translate("progress.mappings.srg_file.generating")); - - int steps = 0; - for (Entry entry : sorted(rootEntries)) { - progress.step(steps++, entry.getName()); - writeEntry(classLines, fieldLines, methodLines, mappings, entry); - } - - progress.init(3, I18n.translate("progress.mappings.srg_file.writing")); - try (PrintWriter writer = new LFPrintWriter(Files.newBufferedWriter(path))) { - progress.step(0, I18n.translate("type.classes")); - classLines.forEach(writer::println); - progress.step(1, I18n.translate("type.fields")); - fieldLines.forEach(writer::println); - progress.step(2, I18n.translate("type.methods")); - methodLines.forEach(writer::println); - } catch (IOException e) { - e.printStackTrace(); - } - } - - private void writeEntry(List classes, List fields, List methods, EntryTree mappings, Entry entry) { - EntryTreeNode node = mappings.findNode(entry); - if (node == null) { - return; - } - - Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE); - if (entry instanceof ClassEntry) { - classes.add(generateClassLine((ClassEntry) entry, translator)); - } else if (entry instanceof FieldEntry) { - fields.add(generateFieldLine((FieldEntry) entry, translator)); - } else if (entry instanceof MethodEntry) { - methods.add(generateMethodLine((MethodEntry) entry, translator)); - } - - for (Entry child : sorted(node.getChildren())) { - writeEntry(classes, fields, methods, mappings, child); - } - } - - private String generateClassLine(ClassEntry sourceEntry, Translator translator) { - ClassEntry targetEntry = translator.translate(sourceEntry); - return "CL: " + sourceEntry.getFullName() + " " + targetEntry.getFullName(); - } - - private String generateMethodLine(MethodEntry sourceEntry, Translator translator) { - MethodEntry targetEntry = translator.translate(sourceEntry); - return "MD: " + describeMethod(sourceEntry) + " " + describeMethod(targetEntry); - } - - private String describeMethod(MethodEntry entry) { - return entry.getParent().getFullName() + "/" + entry.getName() + " " + entry.getDesc(); - } - - private String generateFieldLine(FieldEntry sourceEntry, Translator translator) { - FieldEntry targetEntry = translator.translate(sourceEntry); - return "FD: " + describeField(sourceEntry) + " " + describeField(targetEntry); - } - - private String describeField(FieldEntry entry) { - return entry.getParent().getFullName() + "/" + entry.getName(); - } - - private Collection> sorted(Iterable> iterable) { - ArrayList> sorted = Lists.newArrayList(iterable); - sorted.sort(Comparator.comparing(Entry::getName)); - return sorted; - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java deleted file mode 100644 index 773c95eb..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java +++ /dev/null @@ -1,115 +0,0 @@ -package cuchaz.enigma.translation.mapping.serde; - -import com.google.common.base.Charsets; -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.throwables.MappingParseException; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.MappingPair; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; -import cuchaz.enigma.translation.mapping.tree.EntryTree; -import cuchaz.enigma.translation.mapping.tree.HashEntryTree; -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.FieldEntry; -import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; -import cuchaz.enigma.utils.I18n; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; - -public enum TinyMappingsReader implements MappingsReader { - INSTANCE; - - @Override - public EntryTree read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException { - return read(path, Files.readAllLines(path, Charsets.UTF_8), progress); - } - - private EntryTree read(Path path, List lines, ProgressListener progress) throws MappingParseException { - EntryTree mappings = new HashEntryTree<>(); - lines.remove(0); - - progress.init(lines.size(), I18n.translate("progress.mappings.tiny_file.loading")); - - for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) { - progress.step(lineNumber, ""); - - String line = lines.get(lineNumber); - - if (line.trim().startsWith("#")) { - continue; - } - - try { - MappingPair mapping = parseLine(line); - mappings.insert(mapping.getEntry(), mapping.getMapping()); - } catch (Throwable t) { - t.printStackTrace(); - throw new MappingParseException(path::toString, lineNumber, t.toString()); - } - } - - return mappings; - } - - private MappingPair parseLine(String line) { - String[] tokens = line.split("\t"); - - String key = tokens[0]; - switch (key) { - case "CLASS": - return parseClass(tokens); - case "FIELD": - return parseField(tokens); - case "METHOD": - return parseMethod(tokens); - case "MTH-ARG": - return parseArgument(tokens); - default: - throw new RuntimeException("Unknown token '" + key + "'!"); - } - } - - private MappingPair parseClass(String[] tokens) { - ClassEntry obfuscatedEntry = new ClassEntry(tokens[1]); - String mapping = tokens[2]; - if (mapping.indexOf('$') > 0) { - // inner classes should map to only the final part - mapping = mapping.substring(mapping.lastIndexOf('$') + 1); - } - return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping)); - } - - private MappingPair parseField(String[] tokens) { - ClassEntry ownerClass = new ClassEntry(tokens[1]); - TypeDescriptor descriptor = new TypeDescriptor(tokens[2]); - - FieldEntry obfuscatedEntry = new FieldEntry(ownerClass, tokens[3], descriptor); - String mapping = tokens[4]; - return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping)); - } - - private MappingPair parseMethod(String[] tokens) { - ClassEntry ownerClass = new ClassEntry(tokens[1]); - MethodDescriptor descriptor = new MethodDescriptor(tokens[2]); - - MethodEntry obfuscatedEntry = new MethodEntry(ownerClass, tokens[3], descriptor); - String mapping = tokens[4]; - return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping)); - } - - private MappingPair parseArgument(String[] tokens) { - ClassEntry ownerClass = new ClassEntry(tokens[1]); - MethodDescriptor ownerDescriptor = new MethodDescriptor(tokens[2]); - MethodEntry ownerMethod = new MethodEntry(ownerClass, tokens[3], ownerDescriptor); - int variableIndex = Integer.parseInt(tokens[4]); - - String mapping = tokens[5]; - LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerMethod, variableIndex, "", true, null); - return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping)); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsWriter.java deleted file mode 100644 index c82f2623..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsWriter.java +++ /dev/null @@ -1,148 +0,0 @@ -package cuchaz.enigma.translation.mapping.serde; - -import com.google.common.base.Joiner; -import com.google.common.collect.Lists; -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.translation.MappingTranslator; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.MappingDelta; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; -import cuchaz.enigma.translation.mapping.VoidEntryResolver; -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.MethodEntry; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Comparator; -import java.util.HashSet; -import java.util.Set; - -public class TinyMappingsWriter implements MappingsWriter { - private static final String VERSION_CONSTANT = "v1"; - private static final Joiner TAB_JOINER = Joiner.on('\t'); - - //Possibly add a gui or a way to select the namespaces when exporting from the gui - public static final TinyMappingsWriter INSTANCE = new TinyMappingsWriter("intermediary", "named"); - - // HACK: as of enigma 0.13.1, some fields seem to appear duplicated? - private final Set writtenLines = new HashSet<>(); - private final String nameObf; - private final String nameDeobf; - - public TinyMappingsWriter(String nameObf, String nameDeobf) { - this.nameObf = nameObf; - this.nameDeobf = nameDeobf; - } - - @Override - public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) { - try { - Files.deleteIfExists(path); - Files.createFile(path); - } catch (IOException e) { - e.printStackTrace(); - } - - try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) { - writeLine(writer, new String[]{VERSION_CONSTANT, nameObf, nameDeobf}); - - Lists.newArrayList(mappings).stream() - .map(EntryTreeNode::getEntry).sorted(Comparator.comparing(Object::toString)) - .forEach(entry -> writeEntry(writer, mappings, entry)); - } catch (IOException e) { - e.printStackTrace(); - } - } - - private void writeEntry(Writer writer, EntryTree mappings, Entry entry) { - EntryTreeNode node = mappings.findNode(entry); - if (node == null) { - return; - } - - Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE); - - EntryMapping mapping = mappings.get(entry); - if (mapping != null && !entry.getName().equals(mapping.getTargetName())) { - if (entry instanceof ClassEntry) { - writeClass(writer, (ClassEntry) entry, translator); - } else if (entry instanceof FieldEntry) { - writeLine(writer, serializeEntry(entry, mapping.getTargetName())); - } else if (entry instanceof MethodEntry) { - writeLine(writer, serializeEntry(entry, mapping.getTargetName())); - } - } - - writeChildren(writer, mappings, node); - } - - private void writeChildren(Writer writer, EntryTree mappings, EntryTreeNode node) { - node.getChildren().stream() - .filter(e -> e instanceof FieldEntry).sorted() - .forEach(child -> writeEntry(writer, mappings, child)); - - node.getChildren().stream() - .filter(e -> e instanceof MethodEntry).sorted() - .forEach(child -> writeEntry(writer, mappings, child)); - - node.getChildren().stream() - .filter(e -> e instanceof ClassEntry).sorted() - .forEach(child -> writeEntry(writer, mappings, child)); - } - - private void writeClass(Writer writer, ClassEntry entry, Translator translator) { - ClassEntry translatedEntry = translator.translate(entry); - - String obfClassName = entry.getFullName(); - String deobfClassName = translatedEntry.getFullName(); - writeLine(writer, new String[]{"CLASS", obfClassName, deobfClassName}); - } - - private void writeLine(Writer writer, String[] data) { - try { - String line = TAB_JOINER.join(data) + "\n"; - if (writtenLines.add(line)) { - writer.write(line); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private String[] serializeEntry(Entry entry, String... extraFields) { - String[] data = null; - - if (entry instanceof FieldEntry) { - data = new String[4 + extraFields.length]; - data[0] = "FIELD"; - data[1] = entry.getContainingClass().getFullName(); - data[2] = ((FieldEntry) entry).getDesc().toString(); - data[3] = entry.getName(); - } else if (entry instanceof MethodEntry) { - data = new String[4 + extraFields.length]; - data[0] = "METHOD"; - data[1] = entry.getContainingClass().getFullName(); - data[2] = ((MethodEntry) entry).getDesc().toString(); - data[3] = entry.getName(); - } else if (entry instanceof ClassEntry) { - data = new String[2 + extraFields.length]; - data[0] = "CLASS"; - data[1] = ((ClassEntry) entry).getFullName(); - } - - if (data != null) { - System.arraycopy(extraFields, 0, data, data.length - extraFields.length, extraFields.length); - } - - return data; - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Reader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Reader.java deleted file mode 100644 index d81cbdb9..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Reader.java +++ /dev/null @@ -1,295 +0,0 @@ -package cuchaz.enigma.translation.mapping.serde; - -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.throwables.MappingParseException; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.MappingPair; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; -import cuchaz.enigma.translation.mapping.tree.EntryTree; -import cuchaz.enigma.translation.mapping.tree.HashEntryTree; -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 cuchaz.enigma.utils.I18n; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.BitSet; -import java.util.List; - -final class TinyV2Reader implements MappingsReader { - - private static final String MINOR_VERSION = "0"; - // 0 indent - private static final int IN_HEADER = 0; - private static final int IN_CLASS = IN_HEADER + 1; - // 1 indent - private static final int IN_METHOD = IN_CLASS + 1; - private static final int IN_FIELD = IN_METHOD + 1; - // 2 indent - private static final int IN_PARAMETER = IN_FIELD + 1; - // general properties - private static final int STATE_SIZE = IN_PARAMETER + 1; - private static final int[] INDENT_CLEAR_START = {IN_HEADER, IN_METHOD, IN_PARAMETER, STATE_SIZE}; - - @Override - public EntryTree read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException { - return read(path, Files.readAllLines(path, StandardCharsets.UTF_8), progress); - } - - private EntryTree read(Path path, List lines, ProgressListener progress) throws MappingParseException { - EntryTree mappings = new HashEntryTree<>(); - - progress.init(lines.size(), I18n.translate("progress.mappings.tiny_v2.loading")); - - BitSet state = new BitSet(STATE_SIZE); - @SuppressWarnings({"unchecked", "rawtypes"}) - MappingPair, RawEntryMapping>[] holds = new MappingPair[STATE_SIZE]; - boolean escapeNames = false; - - for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) { - try { - progress.step(lineNumber, ""); - String line = lines.get(lineNumber); - - int indent = 0; - while (line.charAt(indent) == '\t') - indent++; - - String[] parts = line.substring(indent).split("\t", -1); - if (parts.length == 0 || indent >= INDENT_CLEAR_START.length) - throw new IllegalArgumentException("Invalid format"); - - // clean and register stuff in stack - for (int i = INDENT_CLEAR_START[indent]; i < STATE_SIZE; i++) { - state.clear(i); - if (holds[i] != null) { - RawEntryMapping mapping = holds[i].getMapping(); - if (mapping != null) { - EntryMapping baked = mapping.bake(); - if (baked != null) { - mappings.insert(holds[i].getEntry(), baked); - } - } - holds[i] = null; - } - } - - switch (indent) { - case 0: - switch (parts[0]) { - case "tiny": // header - if (lineNumber != 0) { - throw new IllegalArgumentException("Header can only be on the first line"); - } - if (parts.length < 5) { - throw new IllegalArgumentException("Not enough header columns, needs at least 5"); - } - if (!"2".equals(parts[1]) || !MINOR_VERSION.equals(parts[2])) { - throw new IllegalArgumentException("Unsupported TinyV2 version, requires major " + "2" + " and minor " + MINOR_VERSION + ""); - } - state.set(IN_HEADER); - break; - case "c": // class - state.set(IN_CLASS); - holds[IN_CLASS] = parseClass(parts, escapeNames); - break; - default: - unsupportKey(parts); - } - - break; - case 1: - if (state.get(IN_HEADER)) { - if (parts[0].equals("esacpe-names")) { - escapeNames = true; - } - - break; - } - - if (state.get(IN_CLASS)) { - switch (parts[0]) { - case "m": // method - state.set(IN_METHOD); - holds[IN_METHOD] = parseMethod(holds[IN_CLASS], parts, escapeNames); - break; - case "f": // field - state.set(IN_FIELD); - holds[IN_FIELD] = parseField(holds[IN_CLASS], parts, escapeNames); - break; - case "c": // class javadoc - addJavadoc(holds[IN_CLASS], parts); - break; - default: - unsupportKey(parts); - } - break; - } - - unsupportKey(parts); - case 2: - if (state.get(IN_METHOD)) { - switch (parts[0]) { - case "p": // parameter - state.set(IN_PARAMETER); - holds[IN_PARAMETER] = parseArgument(holds[IN_METHOD], parts, escapeNames); - break; - case "v": // local variable - // TODO add local var mapping - break; - case "c": // method javadoc - addJavadoc(holds[IN_METHOD], parts); - break; - default: - unsupportKey(parts); - } - break; - } - - if (state.get(IN_FIELD)) { - switch (parts[0]) { - case "c": // field javadoc - addJavadoc(holds[IN_FIELD], parts); - break; - default: - unsupportKey(parts); - } - break; - } - unsupportKey(parts); - case 3: - if (state.get(IN_PARAMETER)) { - switch (parts[0]) { - case "c": - addJavadoc(holds[IN_PARAMETER], parts); - break; - default: - unsupportKey(parts); - } - break; - } - unsupportKey(parts); - default: - unsupportKey(parts); - } - - } catch (Throwable t) { - t.printStackTrace(); - throw new MappingParseException(path::toString, lineNumber + 1, t.toString()); - } - } - - return mappings; - } - - private void unsupportKey(String[] parts) { - throw new IllegalArgumentException("Unsupported key " + parts[0]); - } - - private void addJavadoc(MappingPair pair, String[] parts) { - if (parts.length != 2) { - throw new IllegalArgumentException("Invalid javadoc declaration"); - } - - addJavadoc(pair, parts[1]); - } - - private MappingPair parseClass(String[] tokens, boolean escapeNames) { - ClassEntry obfuscatedEntry = new ClassEntry(unescapeOpt(tokens[1], escapeNames)); - if (tokens.length <= 2) - return new MappingPair<>(obfuscatedEntry); - String token2 = unescapeOpt(tokens[2], escapeNames); - String mapping = token2.substring(token2.lastIndexOf('$') + 1); - return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping)); - } - - private MappingPair parseField(MappingPair parent, String[] tokens, boolean escapeNames) { - ClassEntry ownerClass = (ClassEntry) parent.getEntry(); - TypeDescriptor descriptor = new TypeDescriptor(unescapeOpt(tokens[1], escapeNames)); - - FieldEntry obfuscatedEntry = new FieldEntry(ownerClass, unescapeOpt(tokens[2], escapeNames), descriptor); - if (tokens.length <= 3) - return new MappingPair<>(obfuscatedEntry); - String mapping = unescapeOpt(tokens[3], escapeNames); - return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping)); - } - - private MappingPair parseMethod(MappingPair parent, String[] tokens, boolean escapeNames) { - ClassEntry ownerClass = (ClassEntry) parent.getEntry(); - MethodDescriptor descriptor = new MethodDescriptor(unescapeOpt(tokens[1], escapeNames)); - - MethodEntry obfuscatedEntry = new MethodEntry(ownerClass, unescapeOpt(tokens[2], escapeNames), descriptor); - if (tokens.length <= 3) - return new MappingPair<>(obfuscatedEntry); - String mapping = unescapeOpt(tokens[3], escapeNames); - return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping)); - } - - - - private void addJavadoc(MappingPair pair, String javadoc) { - RawEntryMapping mapping = pair.getMapping(); - if (mapping == null) { - throw new IllegalArgumentException("Javadoc requires a mapping in enigma!"); - } - mapping.addJavadocLine(unescape(javadoc)); - } - - - - private MappingPair parseArgument(MappingPair parent, String[] tokens, boolean escapeNames) { - MethodEntry ownerMethod = (MethodEntry) parent.getEntry(); - int variableIndex = Integer.parseInt(tokens[1]); - - // tokens[2] is the useless obf name - - LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerMethod, variableIndex, "", true, null); - if (tokens.length <= 3) - return new MappingPair<>(obfuscatedEntry); - String mapping = unescapeOpt(tokens[3], escapeNames); - return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping)); - } - - private static final String TO_ESCAPE = "\\\n\r\0\t"; - private static final String ESCAPED = "\\nr0t"; - - private static String unescapeOpt(String raw, boolean escapedStrings) { - return escapedStrings ? unescape(raw) : raw; - } - - private static String unescape(String str) { - // copied from matcher, lazy! - int pos = str.indexOf('\\'); - if (pos < 0) return str; - - StringBuilder ret = new StringBuilder(str.length() - 1); - int start = 0; - - do { - ret.append(str, start, pos); - pos++; - int type; - - if (pos >= str.length()) { - throw new RuntimeException("incomplete escape sequence at the end"); - } else if ((type = ESCAPED.indexOf(str.charAt(pos))) < 0) { - throw new RuntimeException("invalid escape character: \\" + str.charAt(pos)); - } else { - ret.append(TO_ESCAPE.charAt(type)); - } - - start = pos + 1; - } while ((pos = str.indexOf('\\', start)) >= 0); - - ret.append(str, start, str.length()); - - return ret.toString(); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Writer.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Writer.java deleted file mode 100644 index 95e04c34..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Writer.java +++ /dev/null @@ -1,169 +0,0 @@ -package cuchaz.enigma.translation.mapping.serde; - -import com.google.common.base.Strings; -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.translation.mapping.EntryMap; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.MappingDelta; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; -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.utils.LFPrintWriter; - -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Deque; -import java.util.LinkedList; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -public final class TinyV2Writer implements MappingsWriter { - - private static final String MINOR_VERSION = "0"; - private final String obfHeader; - private final String deobfHeader; - - public TinyV2Writer(String obfHeader, String deobfHeader) { - this.obfHeader = obfHeader; - this.deobfHeader = deobfHeader; - } - - @Override - public void write(EntryTree mappings, MappingDelta delta, Path path, ProgressListener progress, MappingSaveParameters parameters) { - List> classes = StreamSupport.stream(mappings.spliterator(), false).filter(node -> node.getEntry() instanceof ClassEntry).collect(Collectors.toList()); - - try (PrintWriter writer = new LFPrintWriter(Files.newBufferedWriter(path))) { - writer.println("tiny\t2\t" + MINOR_VERSION + "\t" + obfHeader + "\t" + deobfHeader); - - // no escape names - - for (EntryTreeNode node : classes) { - writeClass(writer, node, mappings); - } - } catch (IOException ex) { - ex.printStackTrace(); // TODO add some better logging system - } - } - - private void writeClass(PrintWriter writer, EntryTreeNode node, EntryMap tree) { - writer.print("c\t"); - ClassEntry classEntry = (ClassEntry) node.getEntry(); - String fullName = classEntry.getFullName(); - writer.print(fullName); - Deque parts = new LinkedList<>(); - do { - EntryMapping mapping = tree.get(classEntry); - if (mapping != null) { - parts.addFirst(mapping.getTargetName()); - } else { - parts.addFirst(classEntry.getName()); - } - classEntry = classEntry.getOuterClass(); - } while (classEntry != null); - - String mappedName = String.join("$", parts); - - writer.print("\t"); - - writer.print(mappedName); // todo escaping when we have v2 fixed later - - writer.println(); - - writeComment(writer, node.getValue(), 1); - - for (EntryTreeNode child : node.getChildNodes()) { - Entry entry = child.getEntry(); - if (entry instanceof FieldEntry) { - writeField(writer, child); - } else if (entry instanceof MethodEntry) { - writeMethod(writer, child); - } - } - } - - private void writeMethod(PrintWriter writer, EntryTreeNode node) { - writer.print(indent(1)); - writer.print("m\t"); - writer.print(((MethodEntry) node.getEntry()).getDesc().toString()); - writer.print("\t"); - 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()); - - writeComment(writer, mapping, 2); - } - - for (EntryTreeNode child : node.getChildNodes()) { - Entry entry = child.getEntry(); - if (entry instanceof LocalVariableEntry) { - writeParameter(writer, child); - } - // TODO write actual local variables - } - } - - private void writeField(PrintWriter writer, EntryTreeNode node) { - if (node.getValue() == null) - return; // Shortcut - - writer.print(indent(1)); - writer.print("f\t"); - writer.print(((FieldEntry) node.getEntry()).getDesc().toString()); - writer.print("\t"); - 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()); - - writeComment(writer, mapping, 2); - } - } - - private void writeParameter(PrintWriter writer, EntryTreeNode node) { - if (node.getValue() == null) - return; // Shortcut - - writer.print(indent(2)); - writer.print("p\t"); - writer.print(((LocalVariableEntry) node.getEntry()).getIndex()); - writer.print("\t"); - writer.print(node.getEntry().getName()); - writer.print("\t"); - EntryMapping mapping = node.getValue(); - if (mapping == null) { - writer.println(); // todo ??? - } else { - writer.println(mapping.getTargetName()); - - writeComment(writer, mapping, 3); - } - } - - private void writeComment(PrintWriter writer, EntryMapping mapping, int indent) { - if (mapping != null && mapping.getJavadoc() != null) { - writer.print(indent(indent)); - writer.print("c\t"); - writer.print(MappingHelper.escape(mapping.getJavadoc())); - writer.println(); - } - } - - private String indent(int level) { - return Strings.repeat("\t", level); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java deleted file mode 100644 index 255fa5fb..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java +++ /dev/null @@ -1,110 +0,0 @@ -package cuchaz.enigma.translation.mapping.tree; - -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMap; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.mapping.MappingDelta; -import cuchaz.enigma.translation.representation.entry.Entry; - -import javax.annotation.Nullable; -import java.util.Collection; -import java.util.Iterator; -import java.util.stream.Stream; - -public class DeltaTrackingTree implements EntryTree { - private final EntryTree delegate; - - private EntryTree deltaReference; - private EntryTree changes = new HashEntryTree<>(); - - public DeltaTrackingTree(EntryTree delegate) { - this.delegate = delegate; - this.deltaReference = new HashEntryTree<>(delegate); - } - - public DeltaTrackingTree() { - this(new HashEntryTree<>()); - } - - @Override - public void insert(Entry entry, T value) { - trackChange(entry); - delegate.insert(entry, value); - } - - @Nullable - @Override - public T remove(Entry entry) { - trackChange(entry); - return delegate.remove(entry); - } - - public void trackChange(Entry entry) { - changes.insert(entry, MappingDelta.PLACEHOLDER); - } - - @Nullable - @Override - public T get(Entry entry) { - return delegate.get(entry); - } - - @Override - public Collection> getChildren(Entry entry) { - return delegate.getChildren(entry); - } - - @Override - public Collection> getSiblings(Entry entry) { - return delegate.getSiblings(entry); - } - - @Nullable - @Override - public EntryTreeNode findNode(Entry entry) { - return delegate.findNode(entry); - } - - @Override - public Stream> getRootNodes() { - return delegate.getRootNodes(); - } - - @Override - public DeltaTrackingTree translate(Translator translator, EntryResolver resolver, EntryMap mappings) { - DeltaTrackingTree translatedTree = new DeltaTrackingTree<>(delegate.translate(translator, resolver, mappings)); - translatedTree.changes = changes.translate(translator, resolver, mappings); - return translatedTree; - } - - @Override - public Stream> getAllEntries() { - return delegate.getAllEntries(); - } - - @Override - public boolean isEmpty() { - return delegate.isEmpty(); - } - - @Override - public Iterator> iterator() { - return delegate.iterator(); - } - - public MappingDelta takeDelta() { - MappingDelta delta = new MappingDelta<>(deltaReference, changes); - resetDelta(); - return delta; - } - - private void resetDelta() { - deltaReference = new HashEntryTree<>(delegate); - changes = new HashEntryTree<>(); - } - - public boolean isDirty() { - return !changes.isEmpty(); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java deleted file mode 100644 index daaefcc1..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java +++ /dev/null @@ -1,26 +0,0 @@ -package cuchaz.enigma.translation.mapping.tree; - -import cuchaz.enigma.translation.Translatable; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMap; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.representation.entry.Entry; - -import javax.annotation.Nullable; -import java.util.Collection; -import java.util.stream.Stream; - -public interface EntryTree extends EntryMap, Iterable>, Translatable { - Collection> getChildren(Entry entry); - - Collection> getSiblings(Entry entry); - - @Nullable - EntryTreeNode findNode(Entry entry); - - Stream> getRootNodes(); - - @Override - EntryTree translate(Translator translator, EntryResolver resolver, EntryMap mappings); -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java deleted file mode 100644 index affcd504..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java +++ /dev/null @@ -1,40 +0,0 @@ -package cuchaz.enigma.translation.mapping.tree; - -import cuchaz.enigma.translation.representation.entry.Entry; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.stream.Collectors; - -public interface EntryTreeNode { - @Nullable - T getValue(); - - Entry getEntry(); - - boolean isEmpty(); - - Collection> getChildren(); - - Collection> getChildNodes(); - - default Collection> getNodesRecursively() { - Collection> nodes = new ArrayList<>(); - nodes.add(this); - for (EntryTreeNode node : getChildNodes()) { - nodes.addAll(node.getNodesRecursively()); - } - return nodes; - } - - default Collection> getChildrenRecursively() { - return getNodesRecursively().stream() - .map(EntryTreeNode::getEntry) - .collect(Collectors.toList()); - } - - default boolean hasValue() { - return getValue() != null; - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java deleted file mode 100644 index 570941cd..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java +++ /dev/null @@ -1,188 +0,0 @@ -package cuchaz.enigma.translation.mapping.tree; - -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMap; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.representation.entry.Entry; - -import javax.annotation.Nullable; -import java.util.*; -import java.util.function.Function; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -public class HashEntryTree implements EntryTree { - private final Map, HashTreeNode> root = new HashMap<>(); - - public HashEntryTree() { - } - - public HashEntryTree(EntryTree tree) { - for (EntryTreeNode node : tree) { - insert(node.getEntry(), node.getValue()); - } - } - - @Override - public void insert(Entry entry, T value) { - List> path = computePath(entry, true); - path.get(path.size() - 1).putValue(value); - if (value == null) { - removeDeadAlong(path); - } - } - - @Override - @Nullable - public T remove(Entry entry) { - List> path = computePath(entry, false); - if (path.isEmpty()) { - return null; - } - - T value = path.get(path.size() - 1).removeValue(); - - removeDeadAlong(path); - - return value; - } - - @Override - @Nullable - public T get(Entry entry) { - HashTreeNode node = findNode(entry); - if (node == null) { - return null; - } - return node.getValue(); - } - - @Override - public boolean contains(Entry entry) { - return get(entry) != null; - } - - @Override - public Collection> getChildren(Entry entry) { - HashTreeNode leaf = findNode(entry); - if (leaf == null) { - return Collections.emptyList(); - } - return leaf.getChildren(); - } - - @Override - public Collection> getSiblings(Entry entry) { - Entry parent = entry.getParent(); - if (parent == null) { - return getSiblings(entry, root.keySet()); - } - return getSiblings(entry, getChildren(parent)); - } - - private Collection> getSiblings(Entry entry, Collection> generation) { - Set> siblings = new HashSet<>(generation); - siblings.remove(entry); - return siblings; - } - - @Override - @Nullable - public HashTreeNode findNode(Entry target) { - List> parentChain = target.getAncestry(); - if (parentChain.isEmpty()) { - return null; - } - - HashTreeNode node = root.get(parentChain.get(0)); - for (int i = 1; i < parentChain.size(); i++) { - if (node == null) { - return null; - } - node = node.getChild(parentChain.get(i)); - } - - return node; - } - - private List> computePath(Entry target, boolean make) { - List> ancestry = target.getAncestry(); - if (ancestry.isEmpty()) { - return Collections.emptyList(); - } - - List> path = new ArrayList<>(ancestry.size()); - - Entry rootEntry = ancestry.get(0); - HashTreeNode node = make ? root.computeIfAbsent(rootEntry, HashTreeNode::new) : root.get(rootEntry); - if (node == null) { - return Collections.emptyList(); - } - - path.add(node); - - for (int i = 1; i < ancestry.size(); i++) { - Entry ancestor = ancestry.get(i); - node = make ? node.computeChild(ancestor) : node.getChild(ancestor); - if (node == null) { - return Collections.emptyList(); - } - - path.add(node); - } - - return path; - } - - private void removeDeadAlong(List> path) { - for (int i = path.size() - 1; i >= 0; i--) { - HashTreeNode node = path.get(i); - if (node.isEmpty()) { - if (i > 0) { - HashTreeNode parentNode = path.get(i - 1); - parentNode.remove(node.getEntry()); - } else { - root.remove(node.getEntry()); - } - } else { - break; - } - } - } - - @Override - public Iterator> iterator() { - Collection> nodes = new ArrayList<>(); - for (EntryTreeNode node : root.values()) { - nodes.addAll(node.getNodesRecursively()); - } - return nodes.iterator(); - } - - @Override - public Stream> getAllEntries() { - return StreamSupport.stream(spliterator(), false) - .filter(EntryTreeNode::hasValue) - .map(EntryTreeNode::getEntry); - } - - @Override - public Stream> getRootNodes() { - return root.values().stream().map(Function.identity()); - } - - @Override - public boolean isEmpty() { - return root.isEmpty(); - } - - @Override - public HashEntryTree translate(Translator translator, EntryResolver resolver, EntryMap mappings) { - HashEntryTree translatedTree = new HashEntryTree<>(); - for (EntryTreeNode node : this) { - translatedTree.insert(translator.translate(node.getEntry()), node.getValue()); - } - return translatedTree; - } -} diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java deleted file mode 100644 index 0a990bd5..00000000 --- a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java +++ /dev/null @@ -1,75 +0,0 @@ -package cuchaz.enigma.translation.mapping.tree; - -import cuchaz.enigma.translation.representation.entry.Entry; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -public class HashTreeNode implements EntryTreeNode, Iterable> { - private final Entry entry; - private final Map, HashTreeNode> children = new HashMap<>(); - private T value; - - HashTreeNode(Entry entry) { - this.entry = entry; - } - - void putValue(T value) { - this.value = value; - } - - T removeValue() { - T value = this.value; - this.value = null; - return value; - } - - @Nullable - HashTreeNode getChild(Entry entry) { - return children.get(entry); - } - - @Nonnull - HashTreeNode computeChild(Entry entry) { - return children.computeIfAbsent(entry, HashTreeNode::new); - } - - void remove(Entry entry) { - children.remove(entry); - } - - @Override - @Nullable - public T getValue() { - return value; - } - - @Override - public Entry getEntry() { - return entry; - } - - @Override - public boolean isEmpty() { - return children.isEmpty() && value == null; - } - - @Override - public Collection> getChildren() { - return children.keySet(); - } - - @Override - public Collection> getChildNodes() { - return children.values(); - } - - @Override - public Iterator> iterator() { - return children.values().iterator(); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java b/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java deleted file mode 100644 index b280eef2..00000000 --- a/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java +++ /dev/null @@ -1,116 +0,0 @@ -package cuchaz.enigma.translation.representation; - -import cuchaz.enigma.analysis.Access; -import org.objectweb.asm.Opcodes; - -import java.lang.reflect.Modifier; - -public class AccessFlags { - public static final AccessFlags PRIVATE = new AccessFlags(Opcodes.ACC_PRIVATE); - public static final AccessFlags PUBLIC = new AccessFlags(Opcodes.ACC_PUBLIC); - - private int flags; - - public AccessFlags(int flags) { - this.flags = flags; - } - - public boolean isPrivate() { - return Modifier.isPrivate(this.flags); - } - - public boolean isProtected() { - return Modifier.isProtected(this.flags); - } - - public boolean isPublic() { - return Modifier.isPublic(this.flags); - } - - public boolean isSynthetic() { - return (this.flags & Opcodes.ACC_SYNTHETIC) != 0; - } - - public boolean isStatic() { - return Modifier.isStatic(this.flags); - } - - public boolean isEnum() { - return (flags & Opcodes.ACC_ENUM) != 0; - } - - public boolean isBridge() { - return (flags & Opcodes.ACC_BRIDGE) != 0; - } - - public boolean isFinal() { - return (flags & Opcodes.ACC_FINAL) != 0; - } - - public boolean isInterface() { - return (flags & Opcodes.ACC_INTERFACE) != 0; - } - - public AccessFlags setPrivate() { - this.setVisibility(Opcodes.ACC_PRIVATE); - return this; - } - - public AccessFlags setProtected() { - this.setVisibility(Opcodes.ACC_PROTECTED); - return this; - } - - public AccessFlags setPublic() { - this.setVisibility(Opcodes.ACC_PUBLIC); - return this; - } - - public AccessFlags setBridge() { - flags |= Opcodes.ACC_BRIDGE; - return this; - } - - @Deprecated - public AccessFlags setBridged() { - return setBridge(); - } - - public void setVisibility(int visibility) { - this.resetVisibility(); - this.flags |= visibility; - } - - private void resetVisibility() { - this.flags &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC); - } - - public int getFlags() { - return this.flags; - } - - @Override - public boolean equals(Object obj) { - return obj instanceof AccessFlags && ((AccessFlags) obj).flags == flags; - } - - @Override - public int hashCode() { - return flags; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(Access.get(this).toString().toLowerCase()); - if (isStatic()) { - builder.append(" static"); - } - if (isSynthetic()) { - builder.append(" synthetic"); - } - if (isBridge()) { - builder.append(" bridge"); - } - return builder.toString(); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/representation/Lambda.java b/src/main/java/cuchaz/enigma/translation/representation/Lambda.java deleted file mode 100644 index 63eb5630..00000000 --- a/src/main/java/cuchaz/enigma/translation/representation/Lambda.java +++ /dev/null @@ -1,105 +0,0 @@ -package cuchaz.enigma.translation.representation; - -import cuchaz.enigma.translation.Translatable; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMap; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.mapping.ResolutionStrategy; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; -import cuchaz.enigma.translation.representation.entry.ParentedEntry; - -import java.util.Objects; - -public class Lambda implements Translatable { - private final String invokedName; - private final MethodDescriptor invokedType; - private final MethodDescriptor samMethodType; - private final ParentedEntry implMethod; - private final MethodDescriptor instantiatedMethodType; - - public Lambda(String invokedName, MethodDescriptor invokedType, MethodDescriptor samMethodType, ParentedEntry implMethod, MethodDescriptor instantiatedMethodType) { - this.invokedName = invokedName; - this.invokedType = invokedType; - this.samMethodType = samMethodType; - this.implMethod = implMethod; - this.instantiatedMethodType = instantiatedMethodType; - } - - @Override - public Lambda translate(Translator translator, EntryResolver resolver, EntryMap mappings) { - MethodEntry samMethod = new MethodEntry(getInterface(), invokedName, samMethodType); - EntryMapping samMethodMapping = resolveMapping(resolver, mappings, samMethod); - - return new Lambda( - samMethodMapping != null ? samMethodMapping.getTargetName() : invokedName, - invokedType.translate(translator, resolver, mappings), - samMethodType.translate(translator, resolver, mappings), - implMethod.translate(translator, resolver, mappings), - instantiatedMethodType.translate(translator, resolver, mappings) - ); - } - - private EntryMapping resolveMapping(EntryResolver resolver, EntryMap mappings, MethodEntry methodEntry) { - for (MethodEntry entry : resolver.resolveEntry(methodEntry, ResolutionStrategy.RESOLVE_ROOT)) { - EntryMapping mapping = mappings.get(entry); - if (mapping != null) { - return mapping; - } - } - return null; - } - - public ClassEntry getInterface() { - return invokedType.getReturnDesc().getTypeEntry(); - } - - public String getInvokedName() { - return invokedName; - } - - public MethodDescriptor getInvokedType() { - return invokedType; - } - - public MethodDescriptor getSamMethodType() { - return samMethodType; - } - - public ParentedEntry getImplMethod() { - return implMethod; - } - - public MethodDescriptor getInstantiatedMethodType() { - return instantiatedMethodType; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Lambda lambda = (Lambda) o; - return Objects.equals(invokedName, lambda.invokedName) && - Objects.equals(invokedType, lambda.invokedType) && - Objects.equals(samMethodType, lambda.samMethodType) && - Objects.equals(implMethod, lambda.implMethod) && - Objects.equals(instantiatedMethodType, lambda.instantiatedMethodType); - } - - @Override - public int hashCode() { - return Objects.hash(invokedName, invokedType, samMethodType, implMethod, instantiatedMethodType); - } - - @Override - public String toString() { - return "Lambda{" + - "invokedName='" + invokedName + '\'' + - ", invokedType=" + invokedType + - ", samMethodType=" + samMethodType + - ", implMethod=" + implMethod + - ", instantiatedMethodType=" + instantiatedMethodType + - '}'; - } -} diff --git a/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java b/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java deleted file mode 100644 index 37a70148..00000000 --- a/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java +++ /dev/null @@ -1,132 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.translation.representation; - -import com.google.common.collect.Lists; -import cuchaz.enigma.translation.Translatable; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.mapping.EntryMap; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.utils.Utils; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Function; - -public class MethodDescriptor implements Translatable { - - private List argumentDescs; - private TypeDescriptor returnDesc; - - public MethodDescriptor(String desc) { - try { - this.argumentDescs = Lists.newArrayList(); - int i = 0; - while (i < desc.length()) { - char c = desc.charAt(i); - if (c == '(') { - assert (this.argumentDescs.isEmpty()); - assert (this.returnDesc == null); - i++; - } else if (c == ')') { - i++; - break; - } else { - String type = TypeDescriptor.parseFirst(desc.substring(i)); - this.argumentDescs.add(new TypeDescriptor(type)); - i += type.length(); - } - } - this.returnDesc = new TypeDescriptor(TypeDescriptor.parseFirst(desc.substring(i))); - } catch (Exception ex) { - throw new IllegalArgumentException("Unable to parse method descriptor: " + desc, ex); - } - } - - public MethodDescriptor(List argumentDescs, TypeDescriptor returnDesc) { - this.argumentDescs = argumentDescs; - this.returnDesc = returnDesc; - } - - public List getArgumentDescs() { - return this.argumentDescs; - } - - public TypeDescriptor getReturnDesc() { - return this.returnDesc; - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - buf.append("("); - for (TypeDescriptor desc : this.argumentDescs) { - buf.append(desc); - } - buf.append(")"); - buf.append(this.returnDesc); - return buf.toString(); - } - - public Iterable types() { - List descs = Lists.newArrayList(); - descs.addAll(this.argumentDescs); - descs.add(this.returnDesc); - return descs; - } - - @Override - public boolean equals(Object other) { - return other instanceof MethodDescriptor && equals((MethodDescriptor) other); - } - - public boolean equals(MethodDescriptor other) { - return this.argumentDescs.equals(other.argumentDescs) && this.returnDesc.equals(other.returnDesc); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.argumentDescs.hashCode(), this.returnDesc.hashCode()); - } - - public boolean hasClass(ClassEntry classEntry) { - for (TypeDescriptor desc : types()) { - if (desc.containsType() && desc.getTypeEntry().equals(classEntry)) { - return true; - } - } - return false; - } - - public MethodDescriptor remap(Function remapper) { - List argumentDescs = new ArrayList<>(this.argumentDescs.size()); - for (TypeDescriptor desc : this.argumentDescs) { - argumentDescs.add(desc.remap(remapper)); - } - return new MethodDescriptor(argumentDescs, returnDesc.remap(remapper)); - } - - @Override - public MethodDescriptor translate(Translator translator, EntryResolver resolver, EntryMap mappings) { - List translatedArguments = new ArrayList<>(argumentDescs.size()); - for (TypeDescriptor argument : argumentDescs) { - translatedArguments.add(translator.translate(argument)); - } - return new MethodDescriptor(translatedArguments, translator.translate(returnDesc)); - } - - public boolean canConflictWith(MethodDescriptor descriptor) { - return descriptor.argumentDescs.equals(argumentDescs); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/representation/Signature.java b/src/main/java/cuchaz/enigma/translation/representation/Signature.java deleted file mode 100644 index 424088ab..00000000 --- a/src/main/java/cuchaz/enigma/translation/representation/Signature.java +++ /dev/null @@ -1,98 +0,0 @@ -package cuchaz.enigma.translation.representation; - -import cuchaz.enigma.bytecode.translators.TranslationSignatureVisitor; -import cuchaz.enigma.translation.Translatable; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMap; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import org.objectweb.asm.signature.SignatureReader; -import org.objectweb.asm.signature.SignatureVisitor; -import org.objectweb.asm.signature.SignatureWriter; - -import java.util.function.Function; -import java.util.regex.Pattern; - -public class Signature implements Translatable { - private static final Pattern OBJECT_PATTERN = Pattern.compile(".*:Ljava/lang/Object;:.*"); - - private final String signature; - private final boolean isType; - - private Signature(String signature, boolean isType) { - if (signature != null && OBJECT_PATTERN.matcher(signature).matches()) { - signature = signature.replaceAll(":Ljava/lang/Object;:", "::"); - } - - this.signature = signature; - this.isType = isType; - } - - public static Signature createTypedSignature(String signature) { - if (signature != null && !signature.isEmpty()) { - return new Signature(signature, true); - } - return new Signature(null, true); - } - - public static Signature createSignature(String signature) { - if (signature != null && !signature.isEmpty()) { - return new Signature(signature, false); - } - return new Signature(null, false); - } - - public String getSignature() { - return signature; - } - - public boolean isType() { - return isType; - } - - public Signature remap(Function remapper) { - if (signature == null) { - return this; - } - SignatureWriter writer = new SignatureWriter(); - SignatureVisitor visitor = new TranslationSignatureVisitor(remapper, writer); - if (isType) { - new SignatureReader(signature).acceptType(visitor); - } else { - new SignatureReader(signature).accept(visitor); - } - return new Signature(writer.toString(), isType); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof Signature) { - Signature other = (Signature) obj; - return (other.signature == null && signature == null || other.signature != null - && signature != null && other.signature.equals(signature)) - && other.isType == this.isType; - } - return false; - } - - @Override - public int hashCode() { - int hash = (isType ? 1 : 0) << 16; - if (signature != null) { - hash |= signature.hashCode(); - } - - return hash; - } - - @Override - public String toString() { - return signature; - } - - @Override - public Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings) { - return remap(name -> translator.translate(new ClassEntry(name)).getFullName()); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java b/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java deleted file mode 100644 index f7ba849e..00000000 --- a/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java +++ /dev/null @@ -1,268 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.translation.representation; - -import com.google.common.base.Preconditions; -import com.google.common.collect.Maps; -import cuchaz.enigma.translation.Translatable; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.mapping.EntryMap; -import cuchaz.enigma.translation.representation.entry.ClassEntry; - -import java.util.Map; -import java.util.function.Function; - -public class TypeDescriptor implements Translatable { - - protected final String desc; - - public TypeDescriptor(String desc) { - Preconditions.checkNotNull(desc, "Desc cannot be null"); - - // don't deal with generics - // this is just for raw jvm types - if (desc.charAt(0) == 'T' || desc.indexOf('<') >= 0 || desc.indexOf('>') >= 0) { - throw new IllegalArgumentException("don't use with generic types or templates: " + desc); - } - - this.desc = desc; - } - - public static String parseFirst(String in) { - - if (in == null || in.length() <= 0) { - throw new IllegalArgumentException("No desc to parse, input is empty!"); - } - - // read one desc from the input - - char c = in.charAt(0); - - // first check for void - if (c == 'V') { - return "V"; - } - - // then check for primitives - Primitive primitive = Primitive.get(c); - if (primitive != null) { - return in.substring(0, 1); - } - - // then check for classes - if (c == 'L') { - return readClass(in); - } - - // then check for templates - if (c == 'T') { - return readClass(in); - } - - // then check for arrays - int dim = countArrayDimension(in); - if (dim > 0) { - String arrayType = TypeDescriptor.parseFirst(in.substring(dim)); - return in.substring(0, dim + arrayType.length()); - } - - throw new IllegalArgumentException("don't know how to parse: " + in); - } - - private static int countArrayDimension(String in) { - int i = 0; - while (i < in.length() && in.charAt(i) == '[') - i++; - return i; - } - - private static String readClass(String in) { - // read all the characters in the buffer until we hit a ';' - // include the parameters too - StringBuilder buf = new StringBuilder(); - int depth = 0; - for (int i = 0; i < in.length(); i++) { - char c = in.charAt(i); - buf.append(c); - - if (c == '<') { - depth++; - } else if (c == '>') { - depth--; - } else if (depth == 0 && c == ';') { - return buf.toString(); - } - } - return null; - } - - public static TypeDescriptor of(String name) { - return new TypeDescriptor("L" + name + ";"); - } - - @Override - public String toString() { - return this.desc; - } - - public boolean isVoid() { - return this.desc.length() == 1 && this.desc.charAt(0) == 'V'; - } - - public boolean isPrimitive() { - return this.desc.length() == 1 && Primitive.get(this.desc.charAt(0)) != null; - } - - public Primitive getPrimitive() { - if (!isPrimitive()) { - throw new IllegalStateException("not a primitive"); - } - return Primitive.get(this.desc.charAt(0)); - } - - public boolean isType() { - return this.desc.charAt(0) == 'L' && this.desc.charAt(this.desc.length() - 1) == ';'; - } - - public ClassEntry getTypeEntry() { - if (isType()) { - String name = this.desc.substring(1, this.desc.length() - 1); - - int pos = name.indexOf('<'); - if (pos >= 0) { - // remove the parameters from the class name - name = name.substring(0, pos); - } - - return new ClassEntry(name); - - } else if (isArray() && getArrayType().isType()) { - return getArrayType().getTypeEntry(); - } else { - throw new IllegalStateException("desc doesn't have a class"); - } - } - - public boolean isArray() { - return this.desc.charAt(0) == '['; - } - - public int getArrayDimension() { - if (!isArray()) { - throw new IllegalStateException("not an array"); - } - return countArrayDimension(this.desc); - } - - public TypeDescriptor getArrayType() { - if (!isArray()) { - throw new IllegalStateException("not an array"); - } - return new TypeDescriptor(this.desc.substring(getArrayDimension())); - } - - public boolean containsType() { - return isType() || (isArray() && getArrayType().containsType()); - } - - @Override - public boolean equals(Object other) { - return other instanceof TypeDescriptor && equals((TypeDescriptor) other); - } - - public boolean equals(TypeDescriptor other) { - return this.desc.equals(other.desc); - } - - @Override - public int hashCode() { - return this.desc.hashCode(); - } - - public TypeDescriptor remap(Function remapper) { - String desc = this.desc; - if (isType() || (isArray() && containsType())) { - String replacedName = remapper.apply(this.getTypeEntry().getFullName()); - if (replacedName != null) { - if (this.isType()) { - desc = "L" + replacedName + ";"; - } else { - desc = getArrayPrefix(this.getArrayDimension()) + "L" + replacedName + ";"; - } - } - } - return new TypeDescriptor(desc); - } - - private static String getArrayPrefix(int dimension) { - StringBuilder buf = new StringBuilder(); - for (int i = 0; i < dimension; i++) { - buf.append("["); - } - return buf.toString(); - } - - public int getSize() { - switch (desc.charAt(0)) { - case 'J': - case 'D': - if (desc.length() == 1) { - return 2; - } else { - return 1; - } - default: - return 1; - } - } - - @Override - public Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings) { - return remap(name -> translator.translate(new ClassEntry(name)).getFullName()); - } - - public enum Primitive { - BYTE('B'), - CHARACTER('C'), - SHORT('S'), - INTEGER('I'), - LONG('J'), - FLOAT('F'), - DOUBLE('D'), - BOOLEAN('Z'); - - private static final Map lookup; - - static { - lookup = Maps.newTreeMap(); - for (Primitive val : values()) { - lookup.put(val.getCode(), val); - } - } - - private char code; - - Primitive(char code) { - this.code = code; - } - - public static Primitive get(char code) { - return lookup.get(code); - } - - public char getCode() { - return this.code; - } - } -} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java deleted file mode 100644 index 69307651..00000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java +++ /dev/null @@ -1,93 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.translation.representation.entry; - -import com.google.common.base.Preconditions; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.representation.AccessFlags; -import cuchaz.enigma.translation.representation.Signature; - -import javax.annotation.Nullable; -import java.util.Arrays; - -public class ClassDefEntry extends ClassEntry implements DefEntry { - private final AccessFlags access; - private final Signature signature; - private final ClassEntry superClass; - private final ClassEntry[] interfaces; - - public ClassDefEntry(String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, ClassEntry[] interfaces) { - this(getOuterClass(className), getInnerName(className), signature, access, superClass, interfaces, null); - } - - public ClassDefEntry(ClassEntry parent, String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, ClassEntry[] interfaces) { - this(parent, className, signature, access, superClass, interfaces, null); - } - - public ClassDefEntry(ClassEntry parent, String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, - ClassEntry[] interfaces, String javadocs) { - super(parent, className, javadocs); - Preconditions.checkNotNull(signature, "Class signature cannot be null"); - Preconditions.checkNotNull(access, "Class access cannot be null"); - - this.signature = signature; - this.access = access; - this.superClass = superClass; - this.interfaces = interfaces != null ? interfaces : new ClassEntry[0]; - } - - public static ClassDefEntry parse(int access, String name, String signature, String superName, String[] interfaces) { - ClassEntry superClass = superName != null ? new ClassEntry(superName) : null; - ClassEntry[] interfaceClasses = Arrays.stream(interfaces).map(ClassEntry::new).toArray(ClassEntry[]::new); - return new ClassDefEntry(name, Signature.createSignature(signature), new AccessFlags(access), superClass, interfaceClasses); - } - - public Signature getSignature() { - return signature; - } - - @Override - public AccessFlags getAccess() { - return access; - } - - @Nullable - public ClassEntry getSuperClass() { - return superClass; - } - - public ClassEntry[] getInterfaces() { - return interfaces; - } - - @Override - public ClassDefEntry translate(Translator translator, @Nullable EntryMapping mapping) { - Signature translatedSignature = translator.translate(signature); - String translatedName = mapping != null ? mapping.getTargetName() : name; - AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access; - ClassEntry translatedSuper = translator.translate(superClass); - ClassEntry[] translatedInterfaces = Arrays.stream(interfaces).map(translator::translate).toArray(ClassEntry[]::new); - String docs = mapping != null ? mapping.getJavadoc() : null; - return new ClassDefEntry(parent, translatedName, translatedSignature, translatedAccess, translatedSuper, translatedInterfaces, docs); - } - - @Override - public ClassDefEntry withName(String name) { - return new ClassDefEntry(parent, name, signature, access, superClass, interfaces, javadocs); - } - - @Override - public ClassDefEntry withParent(ClassEntry parent) { - return new ClassDefEntry(parent, name, signature, access, superClass, interfaces, javadocs); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java deleted file mode 100644 index d6171f11..00000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java +++ /dev/null @@ -1,214 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.translation.representation.entry; - -import cuchaz.enigma.throwables.IllegalNameException; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.NameValidator; -import cuchaz.enigma.translation.representation.TypeDescriptor; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.List; -import java.util.Objects; - -public class ClassEntry extends ParentedEntry implements Comparable { - private final String fullName; - - public ClassEntry(String className) { - this(getOuterClass(className), getInnerName(className), null); - } - - public ClassEntry(@Nullable ClassEntry parent, String className) { - this(parent, className, null); - } - - public ClassEntry(@Nullable ClassEntry parent, String className, @Nullable String javadocs) { - super(parent, className, javadocs); - if (parent != null) { - fullName = parent.getFullName() + "$" + name; - } else { - fullName = name; - } - - if (parent == null && className.indexOf('.') >= 0) { - throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className); - } - } - - @Override - public Class getParentType() { - return ClassEntry.class; - } - - @Override - public String getName() { - return this.name; - } - - public String getFullName() { - return fullName; - } - - @Override - public ClassEntry translate(Translator translator, @Nullable EntryMapping mapping) { - if (name.charAt(0) == '[') { - String translatedName = translator.translate(new TypeDescriptor(name)).toString(); - return new ClassEntry(parent, translatedName); - } - - String translatedName = mapping != null ? mapping.getTargetName() : name; - String docs = mapping != null ? mapping.getJavadoc() : null; - return new ClassEntry(parent, translatedName, docs); - } - - @Override - public ClassEntry getContainingClass() { - return this; - } - - @Override - public int hashCode() { - return fullName.hashCode(); - } - - @Override - public boolean equals(Object other) { - return other instanceof ClassEntry && equals((ClassEntry) other); - } - - public boolean equals(ClassEntry other) { - return other != null && Objects.equals(parent, other.parent) && this.name.equals(other.name); - } - - @Override - public boolean canConflictWith(Entry entry) { - return true; - } - - @Override - public void validateName(String name) throws IllegalNameException { - NameValidator.validateClassName(name); - } - - @Override - public ClassEntry withName(String name) { - return new ClassEntry(parent, name, javadocs); - } - - @Override - public ClassEntry withParent(ClassEntry parent) { - return new ClassEntry(parent, name, javadocs); - } - - @Override - public String toString() { - return getFullName(); - } - - public String getPackageName() { - return getPackageName(fullName); - } - - public String getSimpleName() { - int packagePos = name.lastIndexOf('/'); - if (packagePos > 0) { - return name.substring(packagePos + 1); - } - return name; - } - - public boolean isInnerClass() { - return parent != null; - } - - @Nullable - public ClassEntry getOuterClass() { - return parent; - } - - @Nonnull - public ClassEntry getOutermostClass() { - if (parent == null) { - return this; - } - return parent.getOutermostClass(); - } - - public ClassEntry buildClassEntry(List classChain) { - assert (classChain.contains(this)); - StringBuilder buf = new StringBuilder(); - for (ClassEntry chainEntry : classChain) { - if (buf.length() == 0) { - buf.append(chainEntry.getFullName()); - } else { - buf.append("$"); - buf.append(chainEntry.getSimpleName()); - } - - if (chainEntry == this) { - break; - } - } - return new ClassEntry(buf.toString()); - } - - public boolean isJre() { - String packageName = getPackageName(); - return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax")); - } - - public static String getPackageName(String name) { - int pos = name.lastIndexOf('/'); - if (pos > 0) { - return name.substring(0, pos); - } - return null; - } - - @Nullable - public static ClassEntry getOuterClass(String name) { - int index = name.lastIndexOf('$'); - if (index >= 0) { - return new ClassEntry(name.substring(0, index)); - } - return null; - } - - public static String getInnerName(String name) { - int innerClassPos = name.lastIndexOf('$'); - if (innerClassPos > 0) { - return name.substring(innerClassPos + 1); - } - return name; - } - - @Override - public String getSourceRemapName() { - ClassEntry outerClass = getOuterClass(); - if (outerClass != null) { - return outerClass.getSourceRemapName() + "." + name; - } - return getSimpleName(); - } - - @Override - public int compareTo(ClassEntry entry) { - String fullName = getFullName(); - String otherFullName = entry.getFullName(); - if (fullName.length() != otherFullName.length()) { - return fullName.length() - otherFullName.length(); - } - return fullName.compareTo(otherFullName); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java deleted file mode 100644 index 82536c73..00000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java +++ /dev/null @@ -1,7 +0,0 @@ -package cuchaz.enigma.translation.representation.entry; - -import cuchaz.enigma.translation.representation.AccessFlags; - -public interface DefEntry

> extends Entry

{ - AccessFlags getAccess(); -} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java deleted file mode 100644 index 72b03913..00000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java +++ /dev/null @@ -1,107 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.translation.representation.entry; - -import cuchaz.enigma.throwables.IllegalNameException; -import cuchaz.enigma.translation.Translatable; -import cuchaz.enigma.translation.mapping.NameValidator; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.List; - -public interface Entry

> extends Translatable { - String getName(); - - String getJavadocs(); - - default String getSourceRemapName() { - return getName(); - } - - @Nullable - P getParent(); - - Class

getParentType(); - - Entry

withName(String name); - - Entry

withParent(P parent); - - boolean canConflictWith(Entry entry); - - @Nullable - default ClassEntry getContainingClass() { - P parent = getParent(); - if (parent == null) { - return null; - } - if (parent instanceof ClassEntry) { - return (ClassEntry) parent; - } - return parent.getContainingClass(); - } - - default List> getAncestry() { - P parent = getParent(); - List> entries = new ArrayList<>(); - if (parent != null) { - entries.addAll(parent.getAncestry()); - } - entries.add(this); - return entries; - } - - @Nullable - @SuppressWarnings("unchecked") - default > E findAncestor(Class type) { - List> ancestry = getAncestry(); - for (int i = ancestry.size() - 1; i >= 0; i--) { - Entry ancestor = ancestry.get(i); - if (type.isAssignableFrom(ancestor.getClass())) { - return (E) ancestor; - } - } - return null; - } - - @SuppressWarnings("unchecked") - default > Entry

replaceAncestor(E target, E replacement) { - if (replacement.equals(target)) { - return this; - } - - if (equals(target)) { - return (Entry

) replacement; - } - - P parent = getParent(); - if (parent == null) { - return this; - } - - return withParent((P) parent.replaceAncestor(target, replacement)); - } - - default void validateName(String name) throws IllegalNameException { - NameValidator.validateIdentifier(name); - } - - @SuppressWarnings("unchecked") - @Nullable - default > Entry castParent(Class parentType) { - if (parentType.equals(getParentType())) { - return (Entry) this; - } - return null; - } -} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java deleted file mode 100644 index f9282b28..00000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java +++ /dev/null @@ -1,71 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.translation.representation.entry; - -import com.google.common.base.Preconditions; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.representation.AccessFlags; -import cuchaz.enigma.translation.representation.Signature; -import cuchaz.enigma.translation.representation.TypeDescriptor; - -import javax.annotation.Nullable; - -public class FieldDefEntry extends FieldEntry implements DefEntry { - private final AccessFlags access; - private final Signature signature; - - public FieldDefEntry(ClassEntry owner, String name, TypeDescriptor desc, Signature signature, AccessFlags access) { - this(owner, name, desc, signature, access, null); - } - - public FieldDefEntry(ClassEntry owner, String name, TypeDescriptor desc, Signature signature, AccessFlags access, String javadocs) { - super(owner, name, desc, javadocs); - Preconditions.checkNotNull(access, "Field access cannot be null"); - Preconditions.checkNotNull(signature, "Field signature cannot be null"); - this.access = access; - this.signature = signature; - } - - public static FieldDefEntry parse(ClassEntry owner, int access, String name, String desc, String signature) { - return new FieldDefEntry(owner, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access), null); - } - - @Override - public AccessFlags getAccess() { - return access; - } - - public Signature getSignature() { - return signature; - } - - @Override - public FieldDefEntry translate(Translator translator, @Nullable 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; - return new FieldDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess, docs); - } - - @Override - public FieldDefEntry withName(String name) { - return new FieldDefEntry(parent, name, desc, signature, access, javadocs); - } - - @Override - public FieldDefEntry withParent(ClassEntry owner) { - return new FieldDefEntry(owner, this.name, this.desc, signature, access, javadocs); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java deleted file mode 100644 index bef0edf4..00000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java +++ /dev/null @@ -1,96 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.translation.representation.entry; - -import com.google.common.base.Preconditions; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.representation.TypeDescriptor; -import cuchaz.enigma.utils.Utils; - -import javax.annotation.Nullable; - -public class FieldEntry extends ParentedEntry implements Comparable { - protected final TypeDescriptor desc; - - public FieldEntry(ClassEntry parent, String name, TypeDescriptor desc) { - this(parent, name, desc, null); - } - - public FieldEntry(ClassEntry parent, String name, TypeDescriptor desc, String javadocs) { - super(parent, name, javadocs); - - Preconditions.checkNotNull(parent, "Owner cannot be null"); - Preconditions.checkNotNull(desc, "Field descriptor cannot be null"); - - this.desc = desc; - } - - public static FieldEntry parse(String owner, String name, String desc) { - return new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc), null); - } - - @Override - public Class getParentType() { - return ClassEntry.class; - } - - public TypeDescriptor getDesc() { - return this.desc; - } - - @Override - public FieldEntry withName(String name) { - return new FieldEntry(parent, name, desc, null); - } - - @Override - public FieldEntry withParent(ClassEntry parent) { - return new FieldEntry(parent, this.name, this.desc, null); - } - - @Override - protected FieldEntry translate(Translator translator, @Nullable EntryMapping mapping) { - String translatedName = mapping != null ? mapping.getTargetName() : name; - String docs = mapping != null ? mapping.getJavadoc() : null; - return new FieldEntry(parent, translatedName, translator.translate(desc), docs); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.parent, this.name, this.desc); - } - - @Override - public boolean equals(Object other) { - return other instanceof FieldEntry && equals((FieldEntry) other); - } - - public boolean equals(FieldEntry other) { - return this.parent.equals(other.parent) && name.equals(other.name) && desc.equals(other.desc); - } - - @Override - public boolean canConflictWith(Entry entry) { - return entry instanceof FieldEntry && ((FieldEntry) entry).parent.equals(parent); - } - - @Override - public String toString() { - return this.parent.getFullName() + "." + this.name + ":" + this.desc; - } - - @Override - public int compareTo(FieldEntry entry) { - return (name + desc.toString()).compareTo(entry.name + entry.desc.toString()); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java deleted file mode 100644 index aad4236d..00000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java +++ /dev/null @@ -1,51 +0,0 @@ -package cuchaz.enigma.translation.representation.entry; - -import com.google.common.base.Preconditions; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.representation.TypeDescriptor; - -import javax.annotation.Nullable; - -/** - * TypeDescriptor... - * Created by Thog - * 19/10/2016 - */ -public class LocalVariableDefEntry extends LocalVariableEntry { - protected final TypeDescriptor desc; - - public LocalVariableDefEntry(MethodEntry ownerEntry, int index, String name, boolean parameter, TypeDescriptor desc, String javadoc) { - super(ownerEntry, index, name, parameter, javadoc); - Preconditions.checkNotNull(desc, "Variable desc cannot be null"); - - this.desc = desc; - } - - public TypeDescriptor getDesc() { - return desc; - } - - @Override - public LocalVariableDefEntry translate(Translator translator, @Nullable EntryMapping mapping) { - TypeDescriptor translatedDesc = translator.translate(desc); - String translatedName = mapping != null ? mapping.getTargetName() : name; - String javadoc = mapping != null ? mapping.getJavadoc() : javadocs; - return new LocalVariableDefEntry(parent, index, translatedName, parameter, translatedDesc, javadoc); - } - - @Override - public LocalVariableDefEntry withName(String name) { - return new LocalVariableDefEntry(parent, index, name, parameter, desc, javadocs); - } - - @Override - public LocalVariableDefEntry withParent(MethodEntry entry) { - return new LocalVariableDefEntry(entry, index, name, parameter, desc, javadocs); - } - - @Override - public String toString() { - return this.parent + "(" + this.index + ":" + this.name + ":" + this.desc + ")"; - } -} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java deleted file mode 100644 index 3ccb1fa1..00000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java +++ /dev/null @@ -1,93 +0,0 @@ -package cuchaz.enigma.translation.representation.entry; - -import com.google.common.base.Preconditions; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.utils.Utils; - -import javax.annotation.Nullable; - -/** - * TypeDescriptor... - * Created by Thog - * 19/10/2016 - */ -public class LocalVariableEntry extends ParentedEntry implements Comparable { - - protected final int index; - protected final boolean parameter; - - public LocalVariableEntry(MethodEntry parent, int index, String name, boolean parameter, String javadoc) { - super(parent, name, javadoc); - - Preconditions.checkNotNull(parent, "Variable owner cannot be null"); - Preconditions.checkArgument(index >= 0, "Index must be positive"); - - this.index = index; - this.parameter = parameter; - } - - @Override - public Class getParentType() { - return MethodEntry.class; - } - - public boolean isArgument() { - return this.parameter; - } - - public int getIndex() { - return index; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public LocalVariableEntry translate(Translator translator, @Nullable EntryMapping mapping) { - String translatedName = mapping != null ? mapping.getTargetName() : name; - String javadoc = mapping != null ? mapping.getJavadoc() : null; - return new LocalVariableEntry(parent, index, translatedName, parameter, javadoc); - } - - @Override - public LocalVariableEntry withName(String name) { - return new LocalVariableEntry(parent, index, name, parameter, javadocs); - } - - @Override - public LocalVariableEntry withParent(MethodEntry parent) { - return new LocalVariableEntry(parent, index, name, parameter, javadocs); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.parent, this.index); - } - - @Override - public boolean equals(Object other) { - return other instanceof LocalVariableEntry && equals((LocalVariableEntry) other); - } - - public boolean equals(LocalVariableEntry other) { - return this.parent.equals(other.parent) && this.index == other.index; - } - - @Override - public boolean canConflictWith(Entry entry) { - return entry instanceof LocalVariableEntry && ((LocalVariableEntry) entry).parent.equals(parent); - } - - @Override - public String toString() { - return this.parent + "(" + this.index + ":" + this.name + ")"; - } - - @Override - public int compareTo(LocalVariableEntry entry) { - return Integer.compare(index, entry.index); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java deleted file mode 100644 index 4e75a5c8..00000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java +++ /dev/null @@ -1,71 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.translation.representation.entry; - -import com.google.common.base.Preconditions; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.representation.AccessFlags; -import cuchaz.enigma.translation.representation.MethodDescriptor; -import cuchaz.enigma.translation.representation.Signature; - -import javax.annotation.Nullable; - -public class MethodDefEntry extends MethodEntry implements DefEntry { - private final AccessFlags access; - private final Signature signature; - - public MethodDefEntry(ClassEntry owner, String name, MethodDescriptor descriptor, Signature signature, AccessFlags access) { - this(owner, name, descriptor, signature, access, null); - } - - public MethodDefEntry(ClassEntry owner, String name, MethodDescriptor descriptor, Signature signature, AccessFlags access, String docs) { - super(owner, name, descriptor, docs); - Preconditions.checkNotNull(access, "Method access cannot be null"); - Preconditions.checkNotNull(signature, "Method signature cannot be null"); - this.access = access; - this.signature = signature; - } - - public static MethodDefEntry parse(ClassEntry owner, int access, String name, String desc, String signature) { - return new MethodDefEntry(owner, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access), null); - } - - @Override - public AccessFlags getAccess() { - return access; - } - - public Signature getSignature() { - return signature; - } - - @Override - public MethodDefEntry translate(Translator translator, @Nullable 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; - return new MethodDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess, docs); - } - - @Override - public MethodDefEntry withName(String name) { - return new MethodDefEntry(parent, name, descriptor, signature, access, javadocs); - } - - @Override - public MethodDefEntry withParent(ClassEntry parent) { - return new MethodDefEntry(new ClassEntry(parent.getFullName()), name, descriptor, signature, access, javadocs); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java deleted file mode 100644 index e1ffad37..00000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java +++ /dev/null @@ -1,105 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.translation.representation.entry; - -import com.google.common.base.Preconditions; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.representation.MethodDescriptor; -import cuchaz.enigma.utils.Utils; - -import javax.annotation.Nullable; - -public class MethodEntry extends ParentedEntry implements Comparable { - - protected final MethodDescriptor descriptor; - - public MethodEntry(ClassEntry parent, String name, MethodDescriptor descriptor) { - this(parent, name, descriptor, null); - } - - public MethodEntry(ClassEntry parent, String name, MethodDescriptor descriptor, String javadocs) { - super(parent, name, javadocs); - - Preconditions.checkNotNull(parent, "Parent cannot be null"); - Preconditions.checkNotNull(descriptor, "Method descriptor cannot be null"); - - this.descriptor = descriptor; - } - - public static MethodEntry parse(String owner, String name, String desc) { - return new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc), null); - } - - @Override - public Class getParentType() { - return ClassEntry.class; - } - - public MethodDescriptor getDesc() { - return this.descriptor; - } - - public boolean isConstructor() { - return name.equals("") || name.equals(""); - } - - @Override - public MethodEntry translate(Translator translator, @Nullable EntryMapping mapping) { - String translatedName = mapping != null ? mapping.getTargetName() : name; - String docs = mapping != null ? mapping.getJavadoc() : null; - return new MethodEntry(parent, translatedName, translator.translate(descriptor), docs); - } - - @Override - public MethodEntry withName(String name) { - return new MethodEntry(parent, name, descriptor, javadocs); - } - - @Override - public MethodEntry withParent(ClassEntry parent) { - return new MethodEntry(new ClassEntry(parent.getFullName()), name, descriptor, javadocs); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.parent, this.name, this.descriptor); - } - - @Override - public boolean equals(Object other) { - return other instanceof MethodEntry && equals((MethodEntry) other); - } - - public boolean equals(MethodEntry other) { - return this.parent.equals(other.getParent()) && this.name.equals(other.getName()) && this.descriptor.equals(other.getDesc()); - } - - @Override - public boolean canConflictWith(Entry entry) { - if (entry instanceof MethodEntry) { - MethodEntry methodEntry = (MethodEntry) entry; - return methodEntry.parent.equals(parent) && methodEntry.descriptor.canConflictWith(descriptor); - } - return false; - } - - @Override - public String toString() { - return this.parent.getFullName() + "." + this.name + this.descriptor; - } - - @Override - public int compareTo(MethodEntry entry) { - return (name + descriptor.toString()).compareTo(entry.name + entry.descriptor.toString()); - } -} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java deleted file mode 100644 index cbc5faf5..00000000 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java +++ /dev/null @@ -1,82 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.translation.representation.entry; - -import com.google.common.base.Preconditions; -import cuchaz.enigma.translation.Translatable; -import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.EntryMap; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.mapping.ResolutionStrategy; - -import javax.annotation.Nullable; - -public abstract class ParentedEntry

> implements Entry

{ - protected final P parent; - protected final String name; - protected final @Nullable String javadocs; - - protected ParentedEntry(P parent, String name, String javadocs) { - this.parent = parent; - this.name = name; - this.javadocs = javadocs; - - Preconditions.checkNotNull(name, "Name cannot be null"); - } - - @Override - public abstract ParentedEntry

withParent(P parent); - - @Override - public abstract ParentedEntry

withName(String name); - - protected abstract ParentedEntry

translate(Translator translator, @Nullable EntryMapping mapping); - - @Override - public String getName() { - return name; - } - - @Override - @Nullable - public P getParent() { - return parent; - } - - @Nullable - @Override - public String getJavadocs() { - return javadocs; - } - - @Override - public ParentedEntry

translate(Translator translator, EntryResolver resolver, EntryMap mappings) { - P parent = getParent(); - EntryMapping mapping = resolveMapping(resolver, mappings); - if (parent == null) { - return translate(translator, mapping); - } - P translatedParent = translator.translate(parent); - return withParent(translatedParent).translate(translator, mapping); - } - - private EntryMapping resolveMapping(EntryResolver resolver, EntryMap mappings) { - for (ParentedEntry

entry : resolver.resolveEntry(this, ResolutionStrategy.RESOLVE_ROOT)) { - EntryMapping mapping = mappings.get(entry); - if (mapping != null) { - return mapping; - } - } - return null; - } -} diff --git a/src/main/java/cuchaz/enigma/utils/I18n.java b/src/main/java/cuchaz/enigma/utils/I18n.java deleted file mode 100644 index f91c916a..00000000 --- a/src/main/java/cuchaz/enigma/utils/I18n.java +++ /dev/null @@ -1,102 +0,0 @@ -package cuchaz.enigma.utils; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Map; -import java.util.stream.Stream; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Maps; -import com.google.common.reflect.ClassPath; -import com.google.common.reflect.ClassPath.ResourceInfo; -import com.google.gson.Gson; - -import cuchaz.enigma.config.Config; - -public class I18n { - public static final String DEFAULT_LANGUAGE = "en_us"; - private static final Gson GSON = new Gson(); - private static Map translations = Maps.newHashMap(); - private static Map defaultTranslations = Maps.newHashMap(); - private static Map languageNames = Maps.newHashMap(); - - static { - translations = load(Config.getInstance().language); - defaultTranslations = load(DEFAULT_LANGUAGE); - } - - @SuppressWarnings("unchecked") - public static Map load(String language) { - try (InputStream inputStream = I18n.class.getResourceAsStream("/lang/" + language + ".json")) { - if (inputStream != null) { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { - return GSON.fromJson(reader, Map.class); - } - } - } catch (IOException e) { - e.printStackTrace(); - } - return Collections.emptyMap(); - } - - public static String translate(String key) { - String value = translations.get(key); - if (value != null) { - return value; - } - value = defaultTranslations.get(key); - if (value != null) { - return value; - } - return key; - } - - public static String getLanguageName(String language) { - return languageNames.get(language); - } - - public static void setLanguage(String language) { - Config.getInstance().language = language; - try { - Config.getInstance().saveConfig(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public static ArrayList getAvailableLanguages() { - ArrayList list = new ArrayList(); - - try { - ImmutableList resources = ClassPath.from(Thread.currentThread().getContextClassLoader()).getResources().asList(); - Stream dirStream = resources.stream(); - dirStream.forEach(context -> { - String file = context.getResourceName(); - if (file.startsWith("lang/") && file.endsWith(".json")) { - String fileName = file.substring(5, file.length() - 5); - list.add(fileName); - loadLanguageName(fileName); - } - }); - } catch (IOException e) { - e.printStackTrace(); - } - return list; - } - - private static void loadLanguageName(String fileName) { - try (InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream("lang/" + fileName + ".json")) { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { - Map map = GSON.fromJson(reader, Map.class); - languageNames.put(fileName, map.get("language").toString()); - } - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/src/main/java/cuchaz/enigma/utils/LFPrintWriter.java b/src/main/java/cuchaz/enigma/utils/LFPrintWriter.java deleted file mode 100644 index c12e9134..00000000 --- a/src/main/java/cuchaz/enigma/utils/LFPrintWriter.java +++ /dev/null @@ -1,16 +0,0 @@ -package cuchaz.enigma.utils; - -import java.io.PrintWriter; -import java.io.Writer; - -public class LFPrintWriter extends PrintWriter { - public LFPrintWriter(Writer out) { - super(out); - } - - @Override - public void println() { - // https://stackoverflow.com/a/14749004 - write('\n'); - } -} diff --git a/src/main/java/cuchaz/enigma/utils/Message.java b/src/main/java/cuchaz/enigma/utils/Message.java deleted file mode 100644 index d7c5f23e..00000000 --- a/src/main/java/cuchaz/enigma/utils/Message.java +++ /dev/null @@ -1,392 +0,0 @@ -package cuchaz.enigma.utils; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.util.Objects; - -import cuchaz.enigma.network.packet.PacketHelper; -import cuchaz.enigma.translation.representation.entry.Entry; - -public abstract class Message { - - public final String user; - - public static Chat chat(String user, String message) { - return new Chat(user, message); - } - - public static Connect connect(String user) { - return new Connect(user); - } - - public static Disconnect disconnect(String user) { - return new Disconnect(user); - } - - public static EditDocs editDocs(String user, Entry entry) { - return new EditDocs(user, entry); - } - - public static MarkDeobf markDeobf(String user, Entry entry) { - return new MarkDeobf(user, entry); - } - - public static RemoveMapping removeMapping(String user, Entry entry) { - return new RemoveMapping(user, entry); - } - - public static Rename rename(String user, Entry entry, String newName) { - return new Rename(user, entry, newName); - } - - public abstract String translate(); - - public abstract Type getType(); - - public static Message read(DataInput input) throws IOException { - byte typeId = input.readByte(); - if (typeId < 0 || typeId >= Type.values().length) { - throw new IOException(String.format("Invalid message type ID %d", typeId)); - } - Type type = Type.values()[typeId]; - String user = input.readUTF(); - switch (type) { - case CHAT: - String message = input.readUTF(); - return chat(user, message); - case CONNECT: - return connect(user); - case DISCONNECT: - return disconnect(user); - case EDIT_DOCS: - Entry entry = PacketHelper.readEntry(input); - return editDocs(user, entry); - case MARK_DEOBF: - entry = PacketHelper.readEntry(input); - return markDeobf(user, entry); - case REMOVE_MAPPING: - entry = PacketHelper.readEntry(input); - return removeMapping(user, entry); - case RENAME: - entry = PacketHelper.readEntry(input); - String newName = input.readUTF(); - return rename(user, entry, newName); - default: - throw new IllegalStateException("unreachable"); - } - } - - public void write(DataOutput output) throws IOException { - output.writeByte(getType().ordinal()); - PacketHelper.writeString(output, user); - } - - private Message(String user) { - this.user = user; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Message message = (Message) o; - return Objects.equals(user, message.user); - } - - @Override - public int hashCode() { - return Objects.hash(user); - } - - public enum Type { - CHAT, - CONNECT, - DISCONNECT, - EDIT_DOCS, - MARK_DEOBF, - REMOVE_MAPPING, - RENAME, - } - - public static final class Chat extends Message { - - public final String message; - - private Chat(String user, String message) { - super(user); - this.message = message; - } - - @Override - public void write(DataOutput output) throws IOException { - super.write(output); - PacketHelper.writeString(output, message); - } - - @Override - public String translate() { - return String.format(I18n.translate("message.chat.text"), user, message); - } - - @Override - public Type getType() { - return Type.CHAT; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - Chat chat = (Chat) o; - return Objects.equals(message, chat.message); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), message); - } - - @Override - public String toString() { - return String.format("Message.Chat { user: '%s', message: '%s' }", user, message); - } - - } - - public static final class Connect extends Message { - - private Connect(String user) { - super(user); - } - - @Override - public String translate() { - return String.format(I18n.translate("message.connect.text"), user); - } - - @Override - public Type getType() { - return Type.CONNECT; - } - - @Override - public String toString() { - return String.format("Message.Connect { user: '%s' }", user); - } - - } - - public static final class Disconnect extends Message { - - private Disconnect(String user) { - super(user); - } - - @Override - public String translate() { - return String.format(I18n.translate("message.disconnect.text"), user); - } - - @Override - public Type getType() { - return Type.DISCONNECT; - } - - @Override - public String toString() { - return String.format("Message.Disconnect { user: '%s' }", user); - } - - } - - public static final class EditDocs extends Message { - - public final Entry entry; - - private EditDocs(String user, Entry entry) { - super(user); - this.entry = entry; - } - - @Override - public void write(DataOutput output) throws IOException { - super.write(output); - PacketHelper.writeEntry(output, entry); - } - - @Override - public String translate() { - return String.format(I18n.translate("message.edit_docs.text"), user, entry); - } - - @Override - public Type getType() { - return Type.EDIT_DOCS; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - EditDocs editDocs = (EditDocs) o; - return Objects.equals(entry, editDocs.entry); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), entry); - } - - @Override - public String toString() { - return String.format("Message.EditDocs { user: '%s', entry: %s }", user, entry); - } - - } - - public static final class MarkDeobf extends Message { - - public final Entry entry; - - private MarkDeobf(String user, Entry entry) { - super(user); - this.entry = entry; - } - - @Override - public void write(DataOutput output) throws IOException { - super.write(output); - PacketHelper.writeEntry(output, entry); - } - - @Override - public String translate() { - return String.format(I18n.translate("message.mark_deobf.text"), user, entry); - } - - @Override - public Type getType() { - return Type.MARK_DEOBF; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - MarkDeobf markDeobf = (MarkDeobf) o; - return Objects.equals(entry, markDeobf.entry); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), entry); - } - - @Override - public String toString() { - return String.format("Message.MarkDeobf { user: '%s', entry: %s }", user, entry); - } - - } - - public static final class RemoveMapping extends Message { - - public final Entry entry; - - private RemoveMapping(String user, Entry entry) { - super(user); - this.entry = entry; - } - - @Override - public void write(DataOutput output) throws IOException { - super.write(output); - PacketHelper.writeEntry(output, entry); - } - - @Override - public String translate() { - return String.format(I18n.translate("message.remove_mapping.text"), user, entry); - } - - @Override - public Type getType() { - return Type.REMOVE_MAPPING; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - RemoveMapping that = (RemoveMapping) o; - return Objects.equals(entry, that.entry); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), entry); - } - - @Override - public String toString() { - return String.format("Message.RemoveMapping { user: '%s', entry: %s }", user, entry); - } - - } - - public static final class Rename extends Message { - - public final Entry entry; - public final String newName; - - private Rename(String user, Entry entry, String newName) { - super(user); - this.entry = entry; - this.newName = newName; - } - - @Override - public void write(DataOutput output) throws IOException { - super.write(output); - PacketHelper.writeEntry(output, entry); - PacketHelper.writeString(output, newName); - } - - @Override - public String translate() { - return String.format(I18n.translate("message.rename.text"), user, entry, newName); - } - - @Override - public Type getType() { - return Type.RENAME; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - Rename rename = (Rename) o; - return Objects.equals(entry, rename.entry) && - Objects.equals(newName, rename.newName); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), entry, newName); - } - - @Override - public String toString() { - return String.format("Message.Rename { user: '%s', entry: %s, newName: '%s' }", user, entry, newName); - } - - } - -} diff --git a/src/main/java/cuchaz/enigma/utils/Pair.java b/src/main/java/cuchaz/enigma/utils/Pair.java deleted file mode 100644 index bf02ceff..00000000 --- a/src/main/java/cuchaz/enigma/utils/Pair.java +++ /dev/null @@ -1,26 +0,0 @@ -package cuchaz.enigma.utils; - -import java.util.Objects; - -public class Pair { - public final A a; - public final B b; - - public Pair(A a, B b) { - this.a = a; - this.b = b; - } - - @Override - public int hashCode() { - return Objects.hashCode(a) * 31 + - Objects.hashCode(b); - } - - @Override - public boolean equals(Object o) { - return o instanceof Pair && - Objects.equals(a, ((Pair) o).a) && - Objects.equals(b, ((Pair) o).b); - } -} diff --git a/src/main/java/cuchaz/enigma/utils/ReadableToken.java b/src/main/java/cuchaz/enigma/utils/ReadableToken.java deleted file mode 100644 index de152fe3..00000000 --- a/src/main/java/cuchaz/enigma/utils/ReadableToken.java +++ /dev/null @@ -1,30 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.utils; - -public class ReadableToken { - - public int line; - public int startColumn; - public int endColumn; - - public ReadableToken(int line, int startColumn, int endColumn) { - this.line = line; - this.startColumn = startColumn; - this.endColumn = endColumn; - } - - @Override - public String toString() { - return "line " + line + " columns " + startColumn + "-" + endColumn; - } -} diff --git a/src/main/java/cuchaz/enigma/utils/Utils.java b/src/main/java/cuchaz/enigma/utils/Utils.java deleted file mode 100644 index b45b00d1..00000000 --- a/src/main/java/cuchaz/enigma/utils/Utils.java +++ /dev/null @@ -1,179 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.utils; - -import com.google.common.io.CharStreams; -import org.objectweb.asm.Opcodes; - -import javax.swing.*; -import javax.swing.text.BadLocationException; -import javax.swing.text.JTextComponent; -import java.awt.*; -import java.awt.event.MouseEvent; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.*; -import java.util.List; -import java.util.stream.Collectors; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -public class Utils { - - public static final int ASM_VERSION = Opcodes.ASM8; - - public static int combineHashesOrdered(Object... objs) { - final int prime = 67; - int result = 1; - for (Object obj : objs) { - result *= prime; - if (obj != null) { - result += obj.hashCode(); - } - } - return result; - } - - public static int combineHashesOrdered(List objs) { - final int prime = 67; - int result = 1; - for (Object obj : objs) { - result *= prime; - if (obj != null) { - result += obj.hashCode(); - } - } - return result; - } - - public static String readStreamToString(InputStream in) throws IOException { - return CharStreams.toString(new InputStreamReader(in, "UTF-8")); - } - - public static String readResourceToString(String path) throws IOException { - InputStream in = Utils.class.getResourceAsStream(path); - if (in == null) { - throw new IllegalArgumentException("Resource not found! " + path); - } - return readStreamToString(in); - } - - public static void openUrl(String url) { - if (Desktop.isDesktopSupported()) { - Desktop desktop = Desktop.getDesktop(); - try { - desktop.browse(new URI(url)); - } catch (IOException ex) { - throw new Error(ex); - } catch (URISyntaxException ex) { - throw new IllegalArgumentException(ex); - } - } - } - - public static JLabel unboldLabel(JLabel label) { - Font font = label.getFont(); - label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD)); - return label; - } - - public static void showToolTipNow(JComponent component) { - // HACKHACK: trick the tooltip manager into showing the tooltip right now - ToolTipManager manager = ToolTipManager.sharedInstance(); - int oldDelay = manager.getInitialDelay(); - manager.setInitialDelay(0); - manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false)); - manager.setInitialDelay(oldDelay); - } - - public static Rectangle safeModelToView(JTextComponent component, int modelPos) { - if (modelPos < 0) { - modelPos = 0; - } else if (modelPos >= component.getText().length()) { - modelPos = component.getText().length(); - } - try { - return component.modelToView(modelPos); - } catch (BadLocationException e) { - throw new RuntimeException(e); - } - } - - public static boolean getSystemPropertyAsBoolean(String property, boolean defValue) { - String value = System.getProperty(property); - return value == null ? defValue : Boolean.parseBoolean(value); - } - - public static void delete(Path path) throws IOException { - if (Files.exists(path)) { - for (Path p : Files.walk(path).sorted(Comparator.reverseOrder()).collect(Collectors.toList())) { - Files.delete(p); - } - } - } - - public static byte[] zipSha1(Path path) throws IOException { - MessageDigest digest; - try { - digest = MessageDigest.getInstance("SHA-1"); - } catch (NoSuchAlgorithmException e) { - // Algorithm guaranteed to be supported - throw new RuntimeException(e); - } - try (ZipFile zip = new ZipFile(path.toFile())) { - List entries = Collections.list(zip.entries()); - // only compare classes (some implementations may not generate directory entries) - entries.removeIf(entry -> !entry.getName().toLowerCase(Locale.ROOT).endsWith(".class")); - // different implementations may add zip entries in a different order - entries.sort(Comparator.comparing(ZipEntry::getName)); - byte[] buffer = new byte[8192]; - for (ZipEntry entry : entries) { - digest.update(entry.getName().getBytes(StandardCharsets.UTF_8)); - try (InputStream in = zip.getInputStream(entry)) { - int n; - while ((n = in.read(buffer)) != -1) { - digest.update(buffer, 0, n); - } - } - } - } - return digest.digest(); - } - - public static String caplisiseCamelCase(String input){ - StringJoiner stringJoiner = new StringJoiner(" "); - for (String word : input.toLowerCase(Locale.ROOT).split("_")) { - stringJoiner.add(word.substring(0, 1).toUpperCase(Locale.ROOT) + word.substring(1)); - } - return stringJoiner.toString(); - } - - public static boolean isBlank(String input) { - if (input == null) { - return true; - } - for (int i = 0; i < input.length(); i++) { - if (!Character.isWhitespace(input.charAt(i))) { - return false; - } - } - return true; - } -} diff --git a/src/main/java/cuchaz/enigma/utils/search/SearchEntry.java b/src/main/java/cuchaz/enigma/utils/search/SearchEntry.java deleted file mode 100644 index 48b255f8..00000000 --- a/src/main/java/cuchaz/enigma/utils/search/SearchEntry.java +++ /dev/null @@ -1,17 +0,0 @@ -package cuchaz.enigma.utils.search; - -import java.util.List; - -public interface SearchEntry { - - List getSearchableNames(); - - /** - * Returns a type that uniquely identifies this search entry across possible changes. - * This is used for tracking the amount of times this entry has been selected. - * - * @return a unique identifier for this search entry - */ - String getIdentifier(); - -} diff --git a/src/main/java/cuchaz/enigma/utils/search/SearchUtil.java b/src/main/java/cuchaz/enigma/utils/search/SearchUtil.java deleted file mode 100644 index a51afbb0..00000000 --- a/src/main/java/cuchaz/enigma/utils/search/SearchUtil.java +++ /dev/null @@ -1,268 +0,0 @@ -package cuchaz.enigma.utils.search; - -import java.util.*; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.BiFunction; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import cuchaz.enigma.utils.Pair; - -public class SearchUtil { - - private final Map> entries = new HashMap<>(); - private final Map hitCount = new HashMap<>(); - private final Executor searchExecutor = Executors.newWorkStealingPool(); - - public void add(T entry) { - Entry e = Entry.from(entry); - entries.put(entry, e); - } - - public void add(Entry entry) { - entries.put(entry.searchEntry, entry); - } - - public void addAll(Collection entries) { - this.entries.putAll(entries.parallelStream().collect(Collectors.toMap(e -> e, Entry::from))); - } - - public void remove(T entry) { - entries.remove(entry); - } - - public void clear() { - entries.clear(); - } - - public void clearHits() { - hitCount.clear(); - } - - public Stream search(String term) { - return entries.values().parallelStream() - .map(e -> new Pair<>(e, e.getScore(term, hitCount.getOrDefault(e.searchEntry.getIdentifier(), 0)))) - .filter(e -> e.b > 0) - .sorted(Comparator.comparingDouble(o -> -o.b)) - .map(e -> e.a.searchEntry) - .sequential(); - } - - public SearchControl asyncSearch(String term, SearchResultConsumer consumer) { - Map hitCount = new HashMap<>(this.hitCount); - Map> entries = new HashMap<>(this.entries); - float[] scores = new float[entries.size()]; - Lock scoresLock = new ReentrantLock(); - AtomicInteger size = new AtomicInteger(); - AtomicBoolean control = new AtomicBoolean(false); - AtomicInteger elapsed = new AtomicInteger(); - for (Entry value : entries.values()) { - searchExecutor.execute(() -> { - try { - if (control.get()) return; - float score = value.getScore(term, hitCount.getOrDefault(value.searchEntry.getIdentifier(), 0)); - if (score <= 0) return; - score = -score; // sort descending - try { - scoresLock.lock(); - if (control.get()) return; - int dataSize = size.getAndIncrement(); - int index = Arrays.binarySearch(scores, 0, dataSize, score); - if (index < 0) { - index = ~index; - } - System.arraycopy(scores, index, scores, index + 1, dataSize - index); - scores[index] = score; - consumer.add(index, value.searchEntry); - } finally { - scoresLock.unlock(); - } - } finally { - elapsed.incrementAndGet(); - } - }); - } - - return new SearchControl() { - @Override - public void stop() { - control.set(true); - } - - @Override - public boolean isFinished() { - return entries.size() == elapsed.get(); - } - - @Override - public float getProgress() { - return (float) elapsed.get() / entries.size(); - } - }; - } - - public void hit(T entry) { - if (entries.containsKey(entry)) { - hitCount.compute(entry.getIdentifier(), (_id, i) -> i == null ? 1 : i + 1); - } - } - - public static final class Entry { - - public final T searchEntry; - private final String[][] components; - - private Entry(T searchEntry, String[][] components) { - this.searchEntry = searchEntry; - this.components = components; - } - - public float getScore(String term, int hits) { - String ucTerm = term.toUpperCase(Locale.ROOT); - float maxScore = (float) Arrays.stream(components) - .mapToDouble(name -> getScoreFor(ucTerm, name)) - .max().orElse(0.0); - return maxScore * (hits + 1); - } - - /** - * Computes the score for the given name against the given search term. - * - * @param term the search term (expected to be upper-case) - * @param name the entry name, split at word boundaries (see {@link Entry#wordwiseSplit(String)}) - * @return the computed score for the entry - */ - private static float getScoreFor(String term, String[] name) { - int totalLength = Arrays.stream(name).mapToInt(String::length).sum(); - float scorePerChar = 1f / totalLength; - - // This map contains a snapshot of all the states the search has - // been in. The keys are the remaining characters of the search - // term, the values are the maximum scores for that remaining - // search term part. - Map snapshots = new HashMap<>(); - snapshots.put(term, 0f); - - // For each component, start at each existing snapshot, searching - // for the next longest match, and calculate the new score for each - // match length until the maximum. Then the new scores are put back - // into the snapshot map. - for (int componentIndex = 0; componentIndex < name.length; componentIndex++) { - String component = name[componentIndex]; - float posMultiplier = (name.length - componentIndex) * 0.3f; - Map newSnapshots = new HashMap<>(); - for (Map.Entry snapshot : snapshots.entrySet()) { - String remaining = snapshot.getKey(); - float score = snapshot.getValue(); - component = component.toUpperCase(Locale.ROOT); - int l = compareEqualLength(remaining, component); - for (int i = 1; i <= l; i++) { - float baseScore = scorePerChar * i; - float chainBonus = (i - 1) * 0.5f; - merge(newSnapshots, Collections.singletonMap(remaining.substring(i), score + baseScore * posMultiplier + chainBonus), Math::max); - } - } - merge(snapshots, newSnapshots, Math::max); - } - - // Only return the score for when the search term was completely - // consumed. - return snapshots.getOrDefault("", 0f); - } - - private static void merge(Map self, Map source, BiFunction combiner) { - source.forEach((k, v) -> self.compute(k, (_k, v1) -> v1 == null ? v : v == null ? v1 : combiner.apply(v, v1))); - } - - public static Entry from(T e) { - String[][] components = e.getSearchableNames().parallelStream() - .map(Entry::wordwiseSplit) - .toArray(String[][]::new); - return new Entry<>(e, components); - } - - private static int compareEqualLength(String s1, String s2) { - int len = 0; - while (len < s1.length() && len < s2.length() && s1.charAt(len) == s2.charAt(len)) { - len += 1; - } - return len; - } - - /** - * Splits the given input into components, trying to detect word parts. - *

- * Example of how words get split (using | as seperator): - *

MinecraftClientGame -> Minecraft|Client|Game

- *

HTTPInputStream -> HTTP|Input|Stream

- *

class_932 -> class|_|932

- *

X11FontManager -> X|11|Font|Manager

- *

openHTTPConnection -> open|HTTP|Connection

- *

open_http_connection -> open|_|http|_|connection

- * - * @param input the input to split - * @return the resulting components - */ - private static String[] wordwiseSplit(String input) { - List list = new ArrayList<>(); - while (!input.isEmpty()) { - int take; - if (Character.isLetter(input.charAt(0))) { - if (input.length() == 1) { - take = 1; - } else { - boolean nextSegmentIsUppercase = Character.isUpperCase(input.charAt(0)) && Character.isUpperCase(input.charAt(1)); - if (nextSegmentIsUppercase) { - int nextLowercase = 1; - while (Character.isUpperCase(input.charAt(nextLowercase))) { - nextLowercase += 1; - if (nextLowercase == input.length()) { - nextLowercase += 1; - break; - } - } - take = nextLowercase - 1; - } else { - int nextUppercase = 1; - while (nextUppercase < input.length() && Character.isLowerCase(input.charAt(nextUppercase))) { - nextUppercase += 1; - } - take = nextUppercase; - } - } - } else if (Character.isDigit(input.charAt(0))) { - int nextNonNum = 1; - while (nextNonNum < input.length() && Character.isLetter(input.charAt(nextNonNum)) && !Character.isLowerCase(input.charAt(nextNonNum))) { - nextNonNum += 1; - } - take = nextNonNum; - } else { - take = 1; - } - list.add(input.substring(0, take)); - input = input.substring(take); - } - return list.toArray(new String[0]); - } - - } - - @FunctionalInterface - public interface SearchResultConsumer { - void add(int index, T entry); - } - - public interface SearchControl { - void stop(); - - boolean isFinished(); - - float getProgress(); - } - -} diff --git a/src/main/resources/META-INF/services/cuchaz.enigma.api.EnigmaPlugin b/src/main/resources/META-INF/services/cuchaz.enigma.api.EnigmaPlugin deleted file mode 100644 index 136a3e78..00000000 --- a/src/main/resources/META-INF/services/cuchaz.enigma.api.EnigmaPlugin +++ /dev/null @@ -1 +0,0 @@ -cuchaz.enigma.analysis.BuiltinPlugin diff --git a/src/main/resources/about.html b/src/main/resources/about.html deleted file mode 100644 index b75c1bf0..00000000 --- a/src/main/resources/about.html +++ /dev/null @@ -1,6 +0,0 @@ - -

%s

-

A tool for debofuscation of Java code

-

-

Version: %s

- \ No newline at end of file diff --git a/src/main/resources/lang/en_us.json b/src/main/resources/lang/en_us.json deleted file mode 100644 index dbf4b935..00000000 --- a/src/main/resources/lang/en_us.json +++ /dev/null @@ -1,164 +0,0 @@ -{ - "language": "English", - - "mapping_format.enigma_file": "Enigma File", - "mapping_format.enigma_directory": "Enigma Directory", - "mapping_format.enigma_zip": "Enigma ZIP", - "mapping_format.tiny_v2": "Tiny v2", - "mapping_format.tiny_file": "Tiny File", - "mapping_format.srg_file": "SRG File", - "mapping_format.proguard": "Proguard", - "type.methods": "Methods", - "type.fields": "Fields", - "type.parameters": "Parameters", - "type.classes": "Classes", - - "menu.file": "File", - "menu.file.jar.open": "Open Jar...", - "menu.file.jar.close": "Close Jar", - "menu.file.mappings.open": "Open Mappings...", - "menu.file.mappings.save": "Save Mappings", - "menu.file.mappings.save_as": "Save Mappings As...", - "menu.file.mappings.close": "Close Mappings", - "menu.file.mappings.drop": "Drop Invalid Mappings", - "menu.file.export.source": "Export Source...", - "menu.file.export.jar": "Export Jar...", - "menu.file.stats": "Mapping Stats...", - "menu.file.stats.title": "Choose Included Members", - "menu.file.stats.generate": "Generate Stats", - "menu.file.exit": "Exit", - "menu.decompiler": "Decompiler", - "menu.view": "View", - "menu.view.themes": "Themes", - "menu.view.themes.default": "Default", - "menu.view.themes.darcula": "Darcula", - "menu.view.themes.system": "System", - "menu.view.themes.none": "None (JVM Default)", - "menu.view.languages": "Languages", - "menu.view.scale": "Scale", - "menu.view.scale.custom": "Custom...", - "menu.view.scale.custom.title": "Custom Scale", - "menu.view.change.title": "Changes", - "menu.view.change.summary": "Changes will be applied after the next restart.", - "menu.view.change.ok": "Ok", - "menu.view.search": "Search", - "menu.collab": "Collab", - "menu.collab.connect": "Connect to server", - "menu.collab.connect.error": "Error connecting to server", - "menu.collab.disconnect": "Disconnect", - "menu.collab.server.start": "Start server", - "menu.collab.server.start.error": "Error starting server", - "menu.collab.server.stop": "Stop server", - "menu.help": "Help", - "menu.help.about": "About", - "menu.help.about.title": "%s - About", - "menu.help.about.ok": "Ok", - "menu.help.github": "Github Page", - - "popup_menu.rename": "Rename", - "popup_menu.javadoc": "Edit Javadoc", - "popup_menu.inheritance": "Show Inheritance", - "popup_menu.implementations": "Show Implementations", - "popup_menu.calls": "Show Calls (All Implementations)", - "popup_menu.calls.specific": "Show Calls (Specific)", - "popup_menu.declaration": "Go to Declaration", - "popup_menu.back": "Go back", - "popup_menu.forward": "Go forward", - "popup_menu.mark_deobfuscated": "Mark as deobfuscated", - "popup_menu.reset_obfuscated": "Reset to obfuscated", - "popup_menu.zoom.in": "Zoom in", - "popup_menu.zoom.out": "Zoom out", - "popup_menu.zoom.reset": "Reset zoom", - - "info_panel.classes.obfuscated": "Obfuscated Classes", - "info_panel.classes.deobfuscated": "De-obfuscated Classes", - "info_panel.identifier": "Identifier Info", - "info_panel.identifier.none": "No identifier selected", - "info_panel.identifier.variable": "Variable", - "info_panel.identifier.field": "Field", - "info_panel.identifier.method": "Method", - "info_panel.identifier.constructor": "Constructor", - "info_panel.identifier.class": "Class", - "info_panel.identifier.type_descriptor": "TypeDescriptor", - "info_panel.identifier.method_descriptor": "MethodDescriptor", - "info_panel.identifier.modifier": "Modifier", - "info_panel.identifier.index": "Index", - "info_panel.editor.class.decompiling": "(decompiling...)", - "info_panel.editor.class.not_found": "Unable to find class:", - "info_panel.tree.inheritance": "Inheritance", - "info_panel.tree.implementations": "Implementations", - "info_panel.tree.calls": "Call Graph", - - "log_panel.messages": "Messages", - "log_panel.users": "Users", - - "progress.operation": "%s - Operation in progress", - "progress.jar.indexing": "Indexing jar", - "progress.jar.indexing.entries": "Entries...", - "progress.jar.indexing.references": "Entry references...", - "progress.jar.indexing.methods": "Bridge methods...", - "progress.jar.indexing.process": "Processing...", - "progress.jar.writing": "Writing jar...", - "progress.sources.writing": "Writing sources...", - "progress.classes.deobfuscating": "Deobfuscating classes...", - "progress.classes.decompiling": "Decompiling classes...", - "progress.mappings.enigma_file.loading": "Loading mapping file", - "progress.mappings.enigma_file.done": "Done!", - "progress.mappings.enigma_file.writing": "Writing classes", - "progress.mappings.enigma_directory.loading": "Loading mapping files", - "progress.mappings.enigma_directory.writing": "Writing classes", - "progress.mappings.tiny_file.loading": "Loading mapping file", - "progress.mappings.tiny_v2.loading": "Loading mapping file", - "progress.mappings.srg_file.generating": "Generating mappings", - "progress.mappings.srg_file.writing": "Writing mappings", - "progress.stats": "Generating stats", - "progress.stats.data": "Generating data", - - "javadocs.edit": "Edit Javadocs", - "javadocs.instruction": "Edit javadocs here.", - "javadocs.cancel": "Cancel", - "javadocs.save": "Save", - - "prompt.close.title": "Save your changes?", - "prompt.close.summary": "Your mappings have not been saved yet. Do you want to save?", - "prompt.close.save": "Save and close", - "prompt.close.discard": "Discard changes", - "prompt.close.cancel": "Cancel", - "prompt.open": "Open", - "prompt.cancel": "Cancel", - "prompt.connect.title": "Connect to server", - "prompt.connect.username": "Username", - "prompt.connect.ip": "IP", - "prompt.port": "Port", - "prompt.port.nan": "Port is not a number", - "prompt.port.invalid": "Port is out of range, should be between 0-65535", - "prompt.password": "Password", - "prompt.password.too_long": "Password is too long, it must be at most 255 characters.", - "prompt.create_server.title": "Create server", - - "disconnect.disconnected": "Disconnected", - "disconnect.server_closed": "Server closed", - "disconnect.wrong_jar": "Jar checksums don't match (you have the wrong jar)!", - "disconnect.wrong_password": "Incorrect password", - "disconnect.username_taken": "Username is taken", - - "message.chat.text": "%s: %s", - "message.connect.text": "[+] %s", - "message.disconnect.text": "[-] %s", - "message.edit_docs.text": "%s edited docs for %s", - "message.mark_deobf.text": "%s marked %s as deobfuscated", - "message.remove_mapping.text": "%s removed mappings for %s", - "message.rename.text": "%s renamed %s to %s", - - "status.disconnected": "Disconnected.", - "status.connected": "Connected.", - "status.connected_user_count": "Connected (%d users).", - "status.ready": "Ready.", - - "crash.title": "%s - Crash Report", - "crash.summary": "%s has crashed! =(", - "crash.export": "Export", - "crash.ignore": "Ignore", - "crash.exit": "Exit", - "crash.exit.warning": "If you choose exit, you will lose any unsaved work." -} diff --git a/src/main/resources/lang/fr_fr.json b/src/main/resources/lang/fr_fr.json deleted file mode 100644 index d169b9a0..00000000 --- a/src/main/resources/lang/fr_fr.json +++ /dev/null @@ -1,164 +0,0 @@ -{ - "language": "Français", - - "mapping_format.enigma_file": "Fichier Enigma", - "mapping_format.enigma_directory": "Répertoire Enigma", - "mapping_format.enigma_zip": "ZIP Enigma", - "mapping_format.tiny_v2": "Tiny v2", - "mapping_format.tiny_file": "Fichier Tiny", - "mapping_format.srg_file": "Fichier SRG", - "mapping_format.proguard": "Proguard", - "type.methods": "Méthodes", - "type.fields": "Champs", - "type.parameters": "Paramètres", - "type.classes": "Classes", - - "menu.file": "Fichier", - "menu.file.jar.open": "Ouvrir le jar...", - "menu.file.jar.close": "Fermer le jar", - "menu.file.mappings.open": "Ouvrir les mappings...", - "menu.file.mappings.save": "Enregistrer les mappings", - "menu.file.mappings.save_as": "Enregistrer les mappings sous...", - "menu.file.mappings.close": "Fermer les mappings", - "menu.file.mappings.drop": "Supprimer les mappings invalides", - "menu.file.export.source": "Exporter la source...", - "menu.file.export.jar": "Exporter le jar...", - "menu.file.stats": "Statistiques des mappings...", - "menu.file.stats.title": "Choisir les membres inclus", - "menu.file.stats.generate": "Générer les statistiques", - "menu.file.exit": "Quitter", - "menu.decompiler": "Décompilateur", - "menu.view": "Affichage", - "menu.view.themes": "Thèmes", - "menu.view.themes.default": "Par défaut", - "menu.view.themes.darcula": "Darcula", - "menu.view.themes.system": "Système", - "menu.view.themes.none": "Aucun (JVM par défaut)", - "menu.view.languages": "Langues", - "menu.view.scale": "Échelle", - "menu.view.scale.custom": "Personnalisée...", - "menu.view.scale.custom.title": "Échelle personnalisée", - "menu.view.change.title": "Modifications", - "menu.view.change.summary": "Les modifications seront appliquées lors du prochain redémarrage.", - "menu.view.change.ok": "Ok", - "menu.view.search": "Rechercher", - "menu.collab": "Collab", - "menu.collab.connect": "Se connecter à un serveur", - "menu.collab.connect.error": "Erreur lors de la connexion au serveur", - "menu.collab.disconnect": "Se déconnecter", - "menu.collab.server.start": "Démarrer le serveur", - "menu.collab.server.start.error": "Erreur lors du démarrage du serveur", - "menu.collab.server.stop": "Arrêter le serveur", - "menu.help": "Aide", - "menu.help.about": "À propos", - "menu.help.about.title": "%s - À propos", - "menu.help.about.ok": "Ok", - "menu.help.github": "Page Github", - - "popup_menu.rename": "Renommer", - "popup_menu.javadoc": "Éditer le Javadoc", - "popup_menu.inheritance": "Afficher l'héritage", - "popup_menu.implementations": "Afficher les implémentations", - "popup_menu.calls": "Afficher les appels (tous)", - "popup_menu.calls.specific": "Afficher les appels (spécifiques)", - "popup_menu.declaration": "Aller à la déclaration", - "popup_menu.back": "Annuler", - "popup_menu.forward": "Restaurer", - "popup_menu.mark_deobfuscated": "Marquer comme déobfusqué", - "popup_menu.reset_obfuscated": "Réinitialiser à obfusqué", - "popup_menu.zoom.in": "Zoomer", - "popup_menu.zoom.out": "Dézoomer", - "popup_menu.zoom.reset": "Réinitialiser le zoom", - - "info_panel.classes.obfuscated": "Classes obfusquées", - "info_panel.classes.deobfuscated": "Classes déobfusquées", - "info_panel.identifier": "Informations sur l'identifiant", - "info_panel.identifier.none": "Aucun identifiant sélectionné", - "info_panel.identifier.variable": "Variable", - "info_panel.identifier.field": "Champ", - "info_panel.identifier.method": "Méthode", - "info_panel.identifier.constructor": "Constructeur", - "info_panel.identifier.class": "Classe", - "info_panel.identifier.type_descriptor": "Descripteur de type", - "info_panel.identifier.method_descriptor": "Descripteur de méthode", - "info_panel.identifier.modifier": "Modificateur", - "info_panel.identifier.index": "Index", - "info_panel.editor.class.decompiling": "(décompilation...)", - "info_panel.editor.class.not_found": "Impossible de trouver la classe :", - "info_panel.tree.inheritance": "Héritage", - "info_panel.tree.implementations": "Implémentations", - "info_panel.tree.calls": "Graphique des appels", - - "log_panel.messages": "Messages", - "log_panel.users": "Utilisateurs", - - "progress.operation": "%s - Opération en cours", - "progress.jar.indexing": "Indexation du jar", - "progress.jar.indexing.entries": "Entrées...", - "progress.jar.indexing.references": "Références des entrées...", - "progress.jar.indexing.methods": "Mise en place des méthodes...", - "progress.jar.indexing.process": "Traitement...", - "progress.jar.writing": "Écriture du jar...", - "progress.sources.writing": "Écriture des sources...", - "progress.classes.deobfuscating": "Déobfuscation des classes...", - "progress.classes.decompiling": "Décompilation des classes...", - "progress.mappings.enigma_file.loading": "Chargement du fichier de mappings", - "progress.mappings.enigma_file.done": "Terminé !", - "progress.mappings.enigma_file.writing": "Écriture des classes", - "progress.mappings.enigma_directory.loading": "Chargement des fichiers de mappings", - "progress.mappings.enigma_directory.writing": "Écriture des classes", - "progress.mappings.tiny_file.loading": "Chargement du fichier de mappings", - "progress.mappings.tiny_v2.loading": "Chargement du fichier de mappings", - "progress.mappings.srg_file.generating": "Génération des mappings", - "progress.mappings.srg_file.writing": "Écriture des mappings", - "progress.stats": "Génération des statistiques", - "progress.stats.data": "Génération des données", - - "javadocs.edit": "Éditer les Javadocs", - "javadocs.instruction": "Éditer les Javadocs ici.", - "javadocs.cancel": "Annuler", - "javadocs.save": "Enregistrer", - - "prompt.close.title": "Enregistrer les modifications ?", - "prompt.close.summary": "Vos mappings n'ont pas encore été enregistrés. Souhaitez-vous enregistrer ?", - "prompt.close.save": "Enregistrer et fermer", - "prompt.close.discard": "Annuler les modifications", - "prompt.close.cancel": "Annuler", - "prompt.open": "Ouvrir", - "prompt.cancel": "Annuler", - "prompt.connect.title": "Se connecter à un serveur", - "prompt.connect.username": "Nom d'utilisateur", - "prompt.connect.ip": "IP", - "prompt.port": "Port", - "prompt.port.nan": "Le port n'est pas un nombre", - "prompt.port.invalid": "Le port est hors de portée. Il doit être compris entre 0 et 65535.", - "prompt.password": "Mot de passe", - "prompt.password.too_long": "Le mot de passe est trop long. Il ne doit pas dépasser 255 caractères.", - "prompt.create_server.title": "Créer un serveur", - - "disconnect.disconnected": "Déconnecté", - "disconnect.server_closed": "Serveur fermé", - "disconnect.wrong_jar": "Les sommes de contrôle du jar ne correspondent pas (vous avez le mauvais jar) !", - "disconnect.wrong_password": "Mot de passe incorrect", - "disconnect.username_taken": "Le nom d'utilisateur est déjà pris", - - "message.chat.text": "%s : %s", - "message.connect.text": "[+] %s", - "message.disconnect.text": "[-] %s", - "message.edit_docs.text": "%s a édité les javadocs de %s", - "message.mark_deobf.text": "%s a marqué %s comme déobfusqué", - "message.remove_mapping.text": "%s a supprimé les mappings de %s", - "message.rename.text": "%s a renommé %s en %s", - - "status.disconnected": "Déconnecté.", - "status.connected": "Connecté.", - "status.connected_user_count": "Connecté (%d utilisateurs).", - "status.ready": "Prêt.", - - "crash.title": "%s - Rapport de plantage", - "crash.summary": "%s a planté ! =(", - "crash.export": "Exporter", - "crash.ignore": "Ignorer", - "crash.exit": "Quitter", - "crash.exit.warning": "Si vous choisissez Quitter, vous perdrez tout travail non sauvegardé." -} diff --git a/src/main/resources/lang/zh_cn.json b/src/main/resources/lang/zh_cn.json deleted file mode 100644 index f3f503aa..00000000 --- a/src/main/resources/lang/zh_cn.json +++ /dev/null @@ -1,118 +0,0 @@ -{ - "language": "Chinese", - - "mapping_format.enigma_file": "Enigma 文件", - "mapping_format.enigma_directory": "Enigma 目录", - "mapping_format.enigma_zip": "Enigma ZIP", - "mapping_format.tiny_v2": "Tiny v2", - "mapping_format.tiny_file": "Tiny File", - "mapping_format.srg_file": "SRG File", - "mapping_format.proguard": "Proguard", - "type.methods": "方法", - "type.fields": "字段", - "type.parameters": "参数", - "type.classes": "类", - - "menu.file": "文件", - "menu.file.jar.open": "打开 Jar...", - "menu.file.jar.close": "关闭 Jar", - "menu.file.mappings.open": "打开映射...", - "menu.file.mappings.save": "保存映射", - "menu.file.mappings.save_as": "将映射另存为...", - "menu.file.mappings.close": "关闭映射", - "menu.file.mappings.drop": "删除无效映射", - "menu.file.export.source": "导出源码...", - "menu.file.export.jar": "导出Jar...", - "menu.file.stats": "映射统计范围...", - "menu.file.stats.title": "选择包括的成员", - "menu.file.stats.generate": "生成统计范围", - "menu.file.exit": "退出", - "menu.decompiler": "反编译", - "menu.view": "查看", - "menu.view.themes": "主题", - "menu.view.themes.default": "Default", - "menu.view.themes.darcula": "Darcula", - "menu.view.themes.system": "System", - "menu.view.themes.none": "None (JVM Default)", - "menu.view.languages": "语言", - "menu.view.languages.title": "更改语言", - "menu.view.languages.summary": "新语言将在下次重新启动后应用.", - "menu.view.languages.ok": "确定", - "menu.view.search": "搜索", - "menu.help": "帮助", - "menu.help.about": "关于", - "menu.help.about.title": "%s - 关于", - "menu.help.about.ok": "确定", - "menu.help.github": "GitHub 页面", - - "popup_menu.rename": "改名", - "popup_menu.javadoc": "编辑注释", - "popup_menu.inheritance": "显示继承", - "popup_menu.implementations": "显示实现", - "popup_menu.calls": "显示 Calls", - "popup_menu.calls.specific": "显示 Calls (具体)", - "popup_menu.declaration": "Go to Declaration", - "popup_menu.back": "Go back", - "popup_menu.forward": "Go forward", - "popup_menu.mark_deobfuscated": "标记为反混淆", - "popup_menu.reset_obfuscated": "重置混淆", - - "info_panel.classes.obfuscated": "混淆类", - "info_panel.classes.deobfuscated": "反混淆类", - "info_panel.identifier": "标识符信息", - "info_panel.identifier.none": "未选择标识符", - "info_panel.identifier.variable": "变量", - "info_panel.identifier.field": "字段", - "info_panel.identifier.method": "方法", - "info_panel.identifier.constructor": "构造器", - "info_panel.identifier.class": "类", - "info_panel.identifier.type_descriptor": "类型描述符", - "info_panel.identifier.method_descriptor": "方法描述符", - "info_panel.identifier.modifier": "修饰语", - "info_panel.identifier.index": "索引", - "info_panel.editor.class.decompiling": "(反编译中...)", - "info_panel.editor.class.not_found": "找不到类:", - "info_panel.tree.inheritance": "继承", - "info_panel.tree.implementations": "实现", - "info_panel.tree.calls": "调用图", - - "progress.operation": "%s - 进行中", - "progress.jar.indexing": "索引jar", - "progress.jar.indexing.entries": "条目...", - "progress.jar.indexing.references": "条目引用...", - "progress.jar.indexing.methods": "桥接方法...", - "progress.jar.indexing.process": "处理中...", - "progress.jar.writing": "写出jar中...", - "progress.sources.writing": "写出源码中...", - "progress.classes.deobfuscating": "反混淆类中...", - "progress.classes.decompiling": "反编译类中...", - "progress.mappings.enigma_file.loading": "加载映射文件", - "progress.mappings.enigma_file.done": "完成!", - "progress.mappings.enigma_file.writing": "写出类", - "progress.mappings.enigma_directory.loading": "加载映射文件", - "progress.mappings.enigma_directory.writing": "写出类", - "progress.mappings.tiny_file.loading": "加载映射文件", - "progress.mappings.tiny_v2.loading": "加载映射文件", - "progress.mappings.srg_file.generating": "生成映射", - "progress.mappings.srg_file.writing": "写出映射", - "progress.stats": "生成统计范围", - "progress.stats.data": "生成数据", - - "javadocs.edit": "编辑注释", - "javadocs.instruction": "在此处编辑编辑注释.", - "javadocs.cancel": "取消", - "javadocs.save": "保存", - - "prompt.close.title": "保存更改?", - "prompt.close.summary": "您的映射尚未保存。你想保存吗?", - "prompt.close.save": "保存并关闭", - "prompt.close.discard": "放弃更改", - "prompt.close.cancel": "取消", - - "crash.title": "%s - 崩溃报告", - "crash.summary": "%s 已经崩溃! =(", - "crash.export": "输出", - "crash.ignore": "忽略", - "crash.exit": "退出", - "crash.exit.warning": "如果选择退出,将丢失所有未保存的工作." -} diff --git a/src/main/resources/profile.json b/src/main/resources/profile.json deleted file mode 100644 index e1af4cdb..00000000 --- a/src/main/resources/profile.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "services": { - "jar_indexer": [ - { - "id": "enigma:enum_initializer_indexer" - }, - { - "id": "enigma:specialized_bridge_method_indexer" - } - ], - "name_proposal": [ - { - "id": "enigma:enum_name_proposer" - }, - { - "id": "enigma:specialized_method_name_proposer" - } - ] - } -} \ No newline at end of file diff --git a/src/main/resources/stats.html b/src/main/resources/stats.html deleted file mode 100644 index fcff7c0f..00000000 --- a/src/main/resources/stats.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - Stats - - - - - -
- - - - - - - diff --git a/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java b/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java deleted file mode 100644 index 1dc9748b..00000000 --- a/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -import cuchaz.enigma.analysis.ClassCache; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.analysis.index.PackageVisibilityIndex; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import org.junit.Test; - -import java.nio.file.Paths; - -import static cuchaz.enigma.TestEntryFactory.newClass; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; - -public class PackageVisibilityIndexTest { - - private static final ClassEntry KEEP = newClass("cuchaz/enigma/inputs/Keep"); - private static final ClassEntry BASE = newClass("a"); - private static final ClassEntry SAME_PACKAGE_CHILD = newClass("b"); - private static final ClassEntry SAME_PACKAGE_CHILD_INNER = newClass("b$a"); - private static final ClassEntry OTHER_PACKAGE_CHILD = newClass("c"); - private static final ClassEntry OTHER_PACKAGE_CHILD_INNER = newClass("c$a"); - private final JarIndex jarIndex; - - public PackageVisibilityIndexTest() throws Exception { - ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/packageAccess.jar")); - jarIndex = classCache.index(ProgressListener.none()); - } - - @Test - public void test() { - PackageVisibilityIndex visibilityIndex = jarIndex.getPackageVisibilityIndex(); - assertThat(visibilityIndex.getPartition(BASE), containsInAnyOrder(BASE, SAME_PACKAGE_CHILD, SAME_PACKAGE_CHILD_INNER)); - System.out.println(visibilityIndex.getPartitions()); - assertThat(visibilityIndex.getPartitions(), containsInAnyOrder( - containsInAnyOrder(BASE, SAME_PACKAGE_CHILD, SAME_PACKAGE_CHILD_INNER), - containsInAnyOrder(OTHER_PACKAGE_CHILD, OTHER_PACKAGE_CHILD_INNER), - contains(KEEP) - )); - } -} diff --git a/src/test/java/cuchaz/enigma/TestDeobfed.java b/src/test/java/cuchaz/enigma/TestDeobfed.java deleted file mode 100644 index d64a745b..00000000 --- a/src/test/java/cuchaz/enigma/TestDeobfed.java +++ /dev/null @@ -1,100 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -import cuchaz.enigma.analysis.ClassCache; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.network.EnigmaServer; -import cuchaz.enigma.source.Decompiler; -import cuchaz.enigma.source.Decompilers; -import cuchaz.enigma.source.SourceSettings; -import org.junit.BeforeClass; -import org.junit.Test; - -import java.nio.file.Paths; - -import static cuchaz.enigma.TestEntryFactory.newClass; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; - -public class TestDeobfed { - - private static Enigma enigma; - private static ClassCache classCache; - private static JarIndex index; - - @BeforeClass - public static void beforeClass() throws Exception { - enigma = Enigma.create(); - - classCache = ClassCache.of(Paths.get("build/test-deobf/translation.jar")); - index = classCache.index(ProgressListener.none()); - } - - @Test - public void obfEntries() { - assertThat(index.getEntryIndex().getClasses(), containsInAnyOrder( - newClass("cuchaz/enigma/inputs/Keep"), - newClass("a"), - newClass("b"), - newClass("c"), - newClass("d"), - newClass("d$1"), - newClass("e"), - newClass("f"), - newClass("g"), - newClass("g$a"), - newClass("g$a$a"), - newClass("g$b"), - newClass("g$b$a"), - newClass("h"), - newClass("h$a"), - newClass("h$a$a"), - newClass("h$b"), - newClass("h$b$a"), - newClass("h$b$a$a"), - newClass("h$b$a$b"), - newClass("i"), - newClass("i$a"), - newClass("i$b") - )); - } - - @Test - public void decompile() { - EnigmaProject project = new EnigmaProject(enigma, classCache, index, new byte[EnigmaServer.CHECKSUM_SIZE]); - Decompiler decompiler = Decompilers.PROCYON.create(project.getClassCache(), new SourceSettings(false, false)); - - decompiler.getSource("a"); - decompiler.getSource("b"); - decompiler.getSource("c"); - decompiler.getSource("d"); - decompiler.getSource("d$1"); - decompiler.getSource("e"); - decompiler.getSource("f"); - decompiler.getSource("g"); - decompiler.getSource("g$a"); - decompiler.getSource("g$a$a"); - decompiler.getSource("g$b"); - decompiler.getSource("g$b$a"); - decompiler.getSource("h"); - decompiler.getSource("h$a"); - decompiler.getSource("h$a$a"); - decompiler.getSource("h$b"); - decompiler.getSource("h$b$a"); - decompiler.getSource("h$b$a$a"); - decompiler.getSource("h$b$a$b"); - decompiler.getSource("i"); - decompiler.getSource("i$a"); - decompiler.getSource("i$b"); - } -} diff --git a/src/test/java/cuchaz/enigma/TestDeobfuscator.java b/src/test/java/cuchaz/enigma/TestDeobfuscator.java deleted file mode 100644 index 6619d26e..00000000 --- a/src/test/java/cuchaz/enigma/TestDeobfuscator.java +++ /dev/null @@ -1,41 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -import cuchaz.enigma.source.Decompiler; -import cuchaz.enigma.source.Decompilers; -import cuchaz.enigma.source.SourceSettings; -import org.junit.Test; - -import java.io.IOException; -import java.nio.file.Paths; - -public class TestDeobfuscator { - private EnigmaProject openProject() throws IOException { - Enigma enigma = Enigma.create(); - return enigma.openJar(Paths.get("build/test-obf/loneClass.jar"), ProgressListener.none()); - } - - @Test - public void loadJar() - throws Exception { - openProject(); - } - - @Test - public void decompileClass() throws Exception { - EnigmaProject project = openProject(); - Decompiler decompiler = Decompilers.PROCYON.create(project.getClassCache(), new SourceSettings(false, false)); - - decompiler.getSource("a").asString(); - } -} diff --git a/src/test/java/cuchaz/enigma/TestEntryFactory.java b/src/test/java/cuchaz/enigma/TestEntryFactory.java deleted file mode 100644 index 9e1425a2..00000000 --- a/src/test/java/cuchaz/enigma/TestEntryFactory.java +++ /dev/null @@ -1,49 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.translation.representation.*; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.FieldEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; - -public class TestEntryFactory { - - public static ClassEntry newClass(String name) { - return new ClassEntry(name); - } - - public static FieldEntry newField(String className, String fieldName, String fieldType) { - return newField(newClass(className), fieldName, fieldType); - } - - public static FieldEntry newField(ClassEntry classEntry, String fieldName, String fieldType) { - return new FieldEntry(classEntry, fieldName, new TypeDescriptor(fieldType)); - } - - public static MethodEntry newMethod(String className, String methodName, String methodSignature) { - return newMethod(newClass(className), methodName, methodSignature); - } - - public static MethodEntry newMethod(ClassEntry classEntry, String methodName, String methodSignature) { - return new MethodEntry(classEntry, methodName, new MethodDescriptor(methodSignature)); - } - - public static EntryReference newFieldReferenceByMethod(FieldEntry fieldEntry, String callerClassName, String callerName, String callerSignature) { - return new EntryReference<>(fieldEntry, "", newMethod(callerClassName, callerName, callerSignature)); - } - - public static EntryReference newBehaviorReferenceByMethod(MethodEntry methodEntry, String callerClassName, String callerName, String callerSignature) { - return new EntryReference<>(methodEntry, "", newMethod(callerClassName, callerName, callerSignature)); - } -} diff --git a/src/test/java/cuchaz/enigma/TestInnerClasses.java b/src/test/java/cuchaz/enigma/TestInnerClasses.java deleted file mode 100644 index 85c72f81..00000000 --- a/src/test/java/cuchaz/enigma/TestInnerClasses.java +++ /dev/null @@ -1,85 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -import cuchaz.enigma.analysis.ClassCache; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.source.Decompiler; -import cuchaz.enigma.source.Decompilers; -import cuchaz.enigma.source.SourceSettings; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import org.junit.Test; - -import java.nio.file.Paths; - -import static cuchaz.enigma.TestEntryFactory.newClass; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - -public class TestInnerClasses { - - private static final ClassEntry SimpleOuter = newClass("d"); - private static final ClassEntry SimpleInner = newClass("d$a"); - private static final ClassEntry ConstructorArgsOuter = newClass("c"); - private static final ClassEntry ConstructorArgsInner = newClass("c$a"); - private static final ClassEntry ClassTreeRoot = newClass("f"); - private static final ClassEntry ClassTreeLevel1 = newClass("f$a"); - private static final ClassEntry ClassTreeLevel2 = newClass("f$a$a"); - private static final ClassEntry ClassTreeLevel3 = newClass("f$a$a$a"); - private final JarIndex index; - private final Decompiler decompiler; - - public TestInnerClasses() throws Exception { - ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/innerClasses.jar")); - index = classCache.index(ProgressListener.none()); - decompiler = Decompilers.PROCYON.create(classCache, new SourceSettings(false, false)); - } - - @Test - public void simple() { - decompile(SimpleOuter); - } - - @Test - public void constructorArgs() { - decompile(ConstructorArgsOuter); - } - - @Test - public void classTree() { - - // root level - assertThat(index.getEntryIndex().hasClass(ClassTreeRoot), is(true)); - - // level 1 - ClassEntry fullClassEntry = new ClassEntry(ClassTreeRoot.getName() - + "$" + ClassTreeLevel1.getSimpleName()); - assertThat(index.getEntryIndex().hasClass(fullClassEntry), is(true)); - - // level 2 - fullClassEntry = new ClassEntry(ClassTreeRoot.getName() - + "$" + ClassTreeLevel1.getSimpleName() - + "$" + ClassTreeLevel2.getSimpleName()); - assertThat(index.getEntryIndex().hasClass(fullClassEntry), is(true)); - - // level 3 - fullClassEntry = new ClassEntry(ClassTreeRoot.getName() - + "$" + ClassTreeLevel1.getSimpleName() - + "$" + ClassTreeLevel2.getSimpleName() - + "$" + ClassTreeLevel3.getSimpleName()); - assertThat(index.getEntryIndex().hasClass(fullClassEntry), is(true)); - } - - private void decompile(ClassEntry classEntry) { - decompiler.getSource(classEntry.getName()); - } -} diff --git a/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java b/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java deleted file mode 100644 index 48975c82..00000000 --- a/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java +++ /dev/null @@ -1,124 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -import cuchaz.enigma.analysis.ClassCache; -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.MethodDefEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; -import org.junit.Test; - -import java.nio.file.Paths; -import java.util.Collection; - -import static cuchaz.enigma.TestEntryFactory.*; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; - -public class TestJarIndexConstructorReferences { - - private JarIndex index; - - private ClassEntry baseClass = newClass("a"); - private ClassEntry subClass = newClass("d"); - private ClassEntry subsubClass = newClass("e"); - private ClassEntry defaultClass = newClass("c"); - private ClassEntry callerClass = newClass("b"); - - public TestJarIndexConstructorReferences() throws Exception { - ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/constructors.jar")); - index = classCache.index(ProgressListener.none()); - } - - @Test - public void obfEntries() { - assertThat(index.getEntryIndex().getClasses(), containsInAnyOrder(newClass("cuchaz/enigma/inputs/Keep"), baseClass, - subClass, subsubClass, defaultClass, callerClass)); - } - - @Test - @SuppressWarnings("unchecked") - public void baseDefault() { - MethodEntry source = newMethod(baseClass, "", "()V"); - Collection> references = index.getReferenceIndex().getReferencesToMethod(source); - assertThat(references, containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "a", "()V"), - newBehaviorReferenceByMethod(source, subClass.getName(), "", "()V"), - newBehaviorReferenceByMethod(source, subClass.getName(), "", "(III)V") - )); - } - - @Test - @SuppressWarnings("unchecked") - public void baseInt() { - MethodEntry source = newMethod(baseClass, "", "(I)V"); - assertThat(index.getReferenceIndex().getReferencesToMethod(source), containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "b", "()V") - )); - } - - @Test - @SuppressWarnings("unchecked") - public void subDefault() { - MethodEntry source = newMethod(subClass, "", "()V"); - assertThat(index.getReferenceIndex().getReferencesToMethod(source), containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "c", "()V"), - newBehaviorReferenceByMethod(source, subClass.getName(), "", "(I)V") - )); - } - - @Test - @SuppressWarnings("unchecked") - public void subInt() { - MethodEntry source = newMethod(subClass, "", "(I)V"); - assertThat(index.getReferenceIndex().getReferencesToMethod(source), containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "d", "()V"), - newBehaviorReferenceByMethod(source, subClass.getName(), "", "(II)V"), - newBehaviorReferenceByMethod(source, subsubClass.getName(), "", "(I)V") - )); - } - - @Test - @SuppressWarnings("unchecked") - public void subIntInt() { - MethodEntry source = newMethod(subClass, "", "(II)V"); - assertThat(index.getReferenceIndex().getReferencesToMethod(source), containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "e", "()V") - )); - } - - @Test - public void subIntIntInt() { - MethodEntry source = newMethod(subClass, "", "(III)V"); - assertThat(index.getReferenceIndex().getReferencesToMethod(source), is(empty())); - } - - @Test - @SuppressWarnings("unchecked") - public void subsubInt() { - MethodEntry source = newMethod(subsubClass, "", "(I)V"); - assertThat(index.getReferenceIndex().getReferencesToMethod(source), containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "f", "()V") - )); - } - - @Test - @SuppressWarnings("unchecked") - public void defaultConstructable() { - MethodEntry source = newMethod(defaultClass, "", "()V"); - assertThat(index.getReferenceIndex().getReferencesToMethod(source), containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "g", "()V") - )); - } -} diff --git a/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java b/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java deleted file mode 100644 index 76e379c3..00000000 --- a/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java +++ /dev/null @@ -1,227 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -import cuchaz.enigma.analysis.ClassCache; -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.analysis.index.EntryIndex; -import cuchaz.enigma.analysis.index.InheritanceIndex; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.mapping.IndexEntryResolver; -import cuchaz.enigma.translation.representation.AccessFlags; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.FieldEntry; -import cuchaz.enigma.translation.representation.entry.MethodDefEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; -import org.junit.Test; -import org.objectweb.asm.Opcodes; - -import java.nio.file.Paths; -import java.util.Collection; - -import static cuchaz.enigma.TestEntryFactory.*; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; - -public class TestJarIndexInheritanceTree { - - private JarIndex index; - - private ClassEntry baseClass = newClass("a"); - private ClassEntry subClassA = newClass("b"); - private ClassEntry subClassAA = newClass("d"); - private ClassEntry subClassB = newClass("c"); - private FieldEntry nameField = newField(baseClass, "a", "Ljava/lang/String;"); - private FieldEntry numThingsField = newField(subClassB, "a", "I"); - - public TestJarIndexInheritanceTree() - throws Exception { - ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/inheritanceTree.jar")); - index = classCache.index(ProgressListener.none()); - } - - @Test - public void obfEntries() { - assertThat(index.getEntryIndex().getClasses(), containsInAnyOrder( - newClass("cuchaz/enigma/inputs/Keep"), baseClass, subClassA, subClassAA, subClassB - )); - } - - @Test - public void translationIndex() { - - InheritanceIndex index = this.index.getInheritanceIndex(); - - // base class - assertThat(index.getParents(baseClass), is(empty())); - assertThat(index.getAncestors(baseClass), is(empty())); - assertThat(index.getChildren(baseClass), containsInAnyOrder(subClassA, subClassB - )); - - // subclass a - assertThat(index.getParents(subClassA), contains(baseClass)); - assertThat(index.getAncestors(subClassA), containsInAnyOrder(baseClass)); - assertThat(index.getChildren(subClassA), contains(subClassAA)); - - // subclass aa - assertThat(index.getParents(subClassAA), contains(subClassA)); - assertThat(index.getAncestors(subClassAA), containsInAnyOrder(subClassA, baseClass)); - assertThat(index.getChildren(subClassAA), is(empty())); - - // subclass b - assertThat(index.getParents(subClassB), contains(baseClass)); - assertThat(index.getAncestors(subClassB), containsInAnyOrder(baseClass)); - assertThat(index.getChildren(subClassB), is(empty())); - } - - @Test - public void access() { - assertThat(index.getEntryIndex().getFieldAccess(nameField), is(new AccessFlags(Opcodes.ACC_PRIVATE))); - assertThat(index.getEntryIndex().getFieldAccess(numThingsField), is(new AccessFlags(Opcodes.ACC_PRIVATE))); - } - - @Test - public void relatedMethodImplementations() { - - Collection entries; - - EntryResolver resolver = new IndexEntryResolver(index); - // getName() - entries = resolver.resolveEquivalentMethods(newMethod(baseClass, "a", "()Ljava/lang/String;")); - assertThat(entries, containsInAnyOrder( - newMethod(baseClass, "a", "()Ljava/lang/String;"), - newMethod(subClassAA, "a", "()Ljava/lang/String;") - )); - entries = resolver.resolveEquivalentMethods(newMethod(subClassAA, "a", "()Ljava/lang/String;")); - assertThat(entries, containsInAnyOrder( - newMethod(baseClass, "a", "()Ljava/lang/String;"), - newMethod(subClassAA, "a", "()Ljava/lang/String;") - )); - - // doBaseThings() - entries = resolver.resolveEquivalentMethods(newMethod(baseClass, "a", "()V")); - assertThat(entries, containsInAnyOrder( - newMethod(baseClass, "a", "()V"), - newMethod(subClassAA, "a", "()V"), - newMethod(subClassB, "a", "()V") - )); - entries = resolver.resolveEquivalentMethods(newMethod(subClassAA, "a", "()V")); - assertThat(entries, containsInAnyOrder( - newMethod(baseClass, "a", "()V"), - newMethod(subClassAA, "a", "()V"), - newMethod(subClassB, "a", "()V") - )); - entries = resolver.resolveEquivalentMethods(newMethod(subClassB, "a", "()V")); - assertThat(entries, containsInAnyOrder( - newMethod(baseClass, "a", "()V"), - newMethod(subClassAA, "a", "()V"), - newMethod(subClassB, "a", "()V") - )); - - // doBThings - entries = resolver.resolveEquivalentMethods(newMethod(subClassB, "b", "()V")); - assertThat(entries, containsInAnyOrder(newMethod(subClassB, "b", "()V"))); - } - - @Test - @SuppressWarnings("unchecked") - public void fieldReferences() { - Collection> references; - - // name - references = index.getReferenceIndex().getReferencesToField(nameField); - assertThat(references, containsInAnyOrder( - newFieldReferenceByMethod(nameField, baseClass.getName(), "", "(Ljava/lang/String;)V"), - newFieldReferenceByMethod(nameField, baseClass.getName(), "a", "()Ljava/lang/String;") - )); - - // numThings - references = index.getReferenceIndex().getReferencesToField(numThingsField); - assertThat(references, containsInAnyOrder( - newFieldReferenceByMethod(numThingsField, subClassB.getName(), "", "()V"), - newFieldReferenceByMethod(numThingsField, subClassB.getName(), "b", "()V") - )); - } - - @Test - @SuppressWarnings("unchecked") - public void behaviorReferences() { - - MethodEntry source; - Collection> references; - - // baseClass constructor - source = newMethod(baseClass, "", "(Ljava/lang/String;)V"); - references = index.getReferenceIndex().getReferencesToMethod(source); - assertThat(references, containsInAnyOrder( - newBehaviorReferenceByMethod(source, subClassA.getName(), "", "(Ljava/lang/String;)V"), - newBehaviorReferenceByMethod(source, subClassB.getName(), "", "()V") - )); - - // subClassA constructor - source = newMethod(subClassA, "", "(Ljava/lang/String;)V"); - references = index.getReferenceIndex().getReferencesToMethod(source); - assertThat(references, containsInAnyOrder( - newBehaviorReferenceByMethod(source, subClassAA.getName(), "", "()V") - )); - - // baseClass.getName() - source = newMethod(baseClass, "a", "()Ljava/lang/String;"); - references = index.getReferenceIndex().getReferencesToMethod(source); - assertThat(references, containsInAnyOrder( - newBehaviorReferenceByMethod(source, subClassAA.getName(), "a", "()Ljava/lang/String;"), - newBehaviorReferenceByMethod(source, subClassB.getName(), "a", "()V") - )); - - // subclassAA.getName() - source = newMethod(subClassAA, "a", "()Ljava/lang/String;"); - references = index.getReferenceIndex().getReferencesToMethod(source); - assertThat(references, containsInAnyOrder( - newBehaviorReferenceByMethod(source, subClassAA.getName(), "a", "()V") - )); - } - - @Test - public void containsEntries() { - EntryIndex entryIndex = index.getEntryIndex(); - // classes - assertThat(entryIndex.hasClass(baseClass), is(true)); - assertThat(entryIndex.hasClass(subClassA), is(true)); - assertThat(entryIndex.hasClass(subClassAA), is(true)); - assertThat(entryIndex.hasClass(subClassB), is(true)); - - // fields - assertThat(entryIndex.hasField(nameField), is(true)); - assertThat(entryIndex.hasField(numThingsField), is(true)); - - // methods - // getName() - assertThat(entryIndex.hasMethod(newMethod(baseClass, "a", "()Ljava/lang/String;")), is(true)); - assertThat(entryIndex.hasMethod(newMethod(subClassA, "a", "()Ljava/lang/String;")), is(false)); - assertThat(entryIndex.hasMethod(newMethod(subClassAA, "a", "()Ljava/lang/String;")), is(true)); - assertThat(entryIndex.hasMethod(newMethod(subClassB, "a", "()Ljava/lang/String;")), is(false)); - - // doBaseThings() - assertThat(entryIndex.hasMethod(newMethod(baseClass, "a", "()V")), is(true)); - assertThat(entryIndex.hasMethod(newMethod(subClassA, "a", "()V")), is(false)); - assertThat(entryIndex.hasMethod(newMethod(subClassAA, "a", "()V")), is(true)); - assertThat(entryIndex.hasMethod(newMethod(subClassB, "a", "()V")), is(true)); - - // doBThings() - assertThat(entryIndex.hasMethod(newMethod(baseClass, "b", "()V")), is(false)); - assertThat(entryIndex.hasMethod(newMethod(subClassA, "b", "()V")), is(false)); - assertThat(entryIndex.hasMethod(newMethod(subClassAA, "b", "()V")), is(false)); - assertThat(entryIndex.hasMethod(newMethod(subClassB, "b", "()V")), is(true)); - - } -} diff --git a/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java b/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java deleted file mode 100644 index 103c366b..00000000 --- a/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java +++ /dev/null @@ -1,157 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -import cuchaz.enigma.analysis.*; -import cuchaz.enigma.analysis.index.EntryIndex; -import cuchaz.enigma.analysis.index.InheritanceIndex; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.translation.VoidTranslator; -import cuchaz.enigma.translation.representation.AccessFlags; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.FieldEntry; -import cuchaz.enigma.translation.representation.entry.MethodDefEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; -import org.junit.Test; - -import java.nio.file.Paths; -import java.util.Collection; -import java.util.List; - -import static cuchaz.enigma.TestEntryFactory.*; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; - -public class TestJarIndexLoneClass { - - private JarIndex index; - - public TestJarIndexLoneClass() throws Exception { - ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/loneClass.jar")); - index = classCache.index(ProgressListener.none()); - } - - @Test - public void obfEntries() { - assertThat(index.getEntryIndex().getClasses(), containsInAnyOrder( - newClass("cuchaz/enigma/inputs/Keep"), - newClass("a") - )); - } - - @Test - public void translationIndex() { - InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); - assertThat(inheritanceIndex.getParents(new ClassEntry("a")), is(empty())); - assertThat(inheritanceIndex.getParents(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(empty())); - assertThat(inheritanceIndex.getAncestors(new ClassEntry("a")), is(empty())); - assertThat(inheritanceIndex.getAncestors(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(empty())); - assertThat(inheritanceIndex.getChildren(new ClassEntry("a")), is(empty())); - assertThat(inheritanceIndex.getChildren(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(empty())); - } - - @Test - public void access() { - EntryIndex entryIndex = index.getEntryIndex(); - assertThat(entryIndex.getFieldAccess(newField("a", "a", "Ljava/lang/String;")), is(AccessFlags.PRIVATE)); - assertThat(entryIndex.getMethodAccess(newMethod("a", "a", "()Ljava/lang/String;")), is(AccessFlags.PUBLIC)); - assertThat(entryIndex.getFieldAccess(newField("a", "b", "Ljava/lang/String;")), is(nullValue())); - assertThat(entryIndex.getFieldAccess(newField("a", "a", "LFoo;")), is(nullValue())); - } - - @Test - public void classInheritance() { - IndexTreeBuilder treeBuilder = new IndexTreeBuilder(index); - ClassInheritanceTreeNode node = treeBuilder.buildClassInheritance(VoidTranslator.INSTANCE, newClass("a")); - assertThat(node, is(not(nullValue()))); - assertThat(node.getObfClassName(), is("a")); - assertThat(node.getChildCount(), is(0)); - } - - @Test - public void methodInheritance() { - IndexTreeBuilder treeBuilder = new IndexTreeBuilder(index); - MethodEntry source = newMethod("a", "a", "()Ljava/lang/String;"); - MethodInheritanceTreeNode node = treeBuilder.buildMethodInheritance(VoidTranslator.INSTANCE, source); - assertThat(node, is(not(nullValue()))); - assertThat(node.getMethodEntry(), is(source)); - assertThat(node.getChildCount(), is(0)); - } - - @Test - public void classImplementations() { - IndexTreeBuilder treeBuilder = new IndexTreeBuilder(index); - ClassImplementationsTreeNode node = treeBuilder.buildClassImplementations(VoidTranslator.INSTANCE, newClass("a")); - assertThat(node, is(nullValue())); - } - - @Test - public void methodImplementations() { - IndexTreeBuilder treeBuilder = new IndexTreeBuilder(index); - MethodEntry source = newMethod("a", "a", "()Ljava/lang/String;"); - - List nodes = treeBuilder.buildMethodImplementations(VoidTranslator.INSTANCE, source); - assertThat(nodes, hasSize(1)); - assertThat(nodes.get(0).getMethodEntry(), is(source)); - } - - @Test - public void relatedMethodImplementations() { - Collection entries = index.getEntryResolver().resolveEquivalentMethods(newMethod("a", "a", "()Ljava/lang/String;")); - assertThat(entries, containsInAnyOrder( - newMethod("a", "a", "()Ljava/lang/String;") - )); - } - - @Test - @SuppressWarnings("unchecked") - public void fieldReferences() { - FieldEntry source = newField("a", "a", "Ljava/lang/String;"); - Collection> references = index.getReferenceIndex().getReferencesToField(source); - assertThat(references, containsInAnyOrder( - newFieldReferenceByMethod(source, "a", "", "(Ljava/lang/String;)V"), - newFieldReferenceByMethod(source, "a", "a", "()Ljava/lang/String;") - )); - } - - @Test - public void behaviorReferences() { - assertThat(index.getReferenceIndex().getReferencesToMethod(newMethod("a", "a", "()Ljava/lang/String;")), is(empty())); - } - - @Test - public void interfaces() { - assertThat(index.getInheritanceIndex().getParents(new ClassEntry("a")), is(empty())); - } - - @Test - public void implementingClasses() { - assertThat(index.getInheritanceIndex().getChildren(new ClassEntry("a")), is(empty())); - } - - @Test - public void isInterface() { - assertThat(index.getInheritanceIndex().isParent(new ClassEntry("a")), is(false)); - } - - @Test - public void testContains() { - EntryIndex entryIndex = index.getEntryIndex(); - assertThat(entryIndex.hasClass(newClass("a")), is(true)); - assertThat(entryIndex.hasClass(newClass("b")), is(false)); - assertThat(entryIndex.hasField(newField("a", "a", "Ljava/lang/String;")), is(true)); - assertThat(entryIndex.hasField(newField("a", "b", "Ljava/lang/String;")), is(false)); - assertThat(entryIndex.hasField(newField("a", "a", "LFoo;")), is(false)); - assertThat(entryIndex.hasMethod(newMethod("a", "a", "()Ljava/lang/String;")), is(true)); - assertThat(entryIndex.hasMethod(newMethod("a", "b", "()Ljava/lang/String;")), is(false)); - } -} diff --git a/src/test/java/cuchaz/enigma/TestMethodDescriptor.java b/src/test/java/cuchaz/enigma/TestMethodDescriptor.java deleted file mode 100644 index a73880dd..00000000 --- a/src/test/java/cuchaz/enigma/TestMethodDescriptor.java +++ /dev/null @@ -1,247 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -import cuchaz.enigma.translation.representation.MethodDescriptor; -import cuchaz.enigma.translation.representation.TypeDescriptor; -import org.junit.Test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; - -public class TestMethodDescriptor { - - @Test - public void easiest() { - final MethodDescriptor sig = new MethodDescriptor("()V"); - assertThat(sig.getArgumentDescs(), is(empty())); - assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); - } - - @Test - public void primitives() { - { - final MethodDescriptor sig = new MethodDescriptor("(I)V"); - assertThat(sig.getArgumentDescs(), contains( - new TypeDescriptor("I") - )); - assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); - } - { - final MethodDescriptor sig = new MethodDescriptor("(I)I"); - assertThat(sig.getArgumentDescs(), contains( - new TypeDescriptor("I") - )); - assertThat(sig.getReturnDesc(), is(new TypeDescriptor("I"))); - } - { - final MethodDescriptor sig = new MethodDescriptor("(IBCJ)Z"); - assertThat(sig.getArgumentDescs(), contains( - new TypeDescriptor("I"), - new TypeDescriptor("B"), - new TypeDescriptor("C"), - new TypeDescriptor("J") - )); - assertThat(sig.getReturnDesc(), is(new TypeDescriptor("Z"))); - } - } - - @Test - public void classes() { - { - final MethodDescriptor sig = new MethodDescriptor("([LFoo;)V"); - assertThat(sig.getArgumentDescs().size(), is(1)); - assertThat(sig.getArgumentDescs().get(0), is(new TypeDescriptor("[LFoo;"))); - assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); - } - { - final MethodDescriptor sig = new MethodDescriptor("(LFoo;)LBar;"); - assertThat(sig.getArgumentDescs(), contains( - new TypeDescriptor("LFoo;") - )); - assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LBar;"))); - } - { - final MethodDescriptor sig = new MethodDescriptor("(LFoo;LMoo;LZoo;)LBar;"); - assertThat(sig.getArgumentDescs(), contains( - new TypeDescriptor("LFoo;"), - new TypeDescriptor("LMoo;"), - new TypeDescriptor("LZoo;") - )); - assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LBar;"))); - } - } - - @Test - public void arrays() { - { - final MethodDescriptor sig = new MethodDescriptor("([I)V"); - assertThat(sig.getArgumentDescs(), contains( - new TypeDescriptor("[I") - )); - assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); - } - { - final MethodDescriptor sig = new MethodDescriptor("([I)[J"); - assertThat(sig.getArgumentDescs(), contains( - new TypeDescriptor("[I") - )); - assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[J"))); - } - { - final MethodDescriptor sig = new MethodDescriptor("([I[Z[F)[D"); - assertThat(sig.getArgumentDescs(), contains( - new TypeDescriptor("[I"), - new TypeDescriptor("[Z"), - new TypeDescriptor("[F") - )); - assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[D"))); - } - } - - @Test - public void mixed() { - { - final MethodDescriptor sig = new MethodDescriptor("(I[JLFoo;)Z"); - assertThat(sig.getArgumentDescs(), contains( - new TypeDescriptor("I"), - new TypeDescriptor("[J"), - new TypeDescriptor("LFoo;") - )); - assertThat(sig.getReturnDesc(), is(new TypeDescriptor("Z"))); - } - { - final MethodDescriptor sig = new MethodDescriptor("(III)[LFoo;"); - assertThat(sig.getArgumentDescs(), contains( - new TypeDescriptor("I"), - new TypeDescriptor("I"), - new TypeDescriptor("I") - )); - assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[LFoo;"))); - } - } - - @Test - public void replaceClasses() { - { - final MethodDescriptor oldSig = new MethodDescriptor("()V"); - final MethodDescriptor sig = oldSig.remap(s -> null); - assertThat(sig.getArgumentDescs(), is(empty())); - assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); - } - { - final MethodDescriptor oldSig = new MethodDescriptor("(IJLFoo;)V"); - final MethodDescriptor sig = oldSig.remap(s -> null); - assertThat(sig.getArgumentDescs(), contains( - new TypeDescriptor("I"), - new TypeDescriptor("J"), - new TypeDescriptor("LFoo;") - )); - assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); - } - { - final MethodDescriptor oldSig = new MethodDescriptor("(LFoo;LBar;)LMoo;"); - final MethodDescriptor sig = oldSig.remap(s -> { - if (s.equals("Foo")) { - return "Bar"; - } - return null; - }); - assertThat(sig.getArgumentDescs(), contains( - new TypeDescriptor("LBar;"), - new TypeDescriptor("LBar;") - )); - assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LMoo;"))); - } - { - final MethodDescriptor oldSig = new MethodDescriptor("(LFoo;LBar;)LMoo;"); - final MethodDescriptor sig = oldSig.remap(s -> { - if (s.equals("Moo")) { - return "Cow"; - } - return null; - }); - assertThat(sig.getArgumentDescs(), contains( - new TypeDescriptor("LFoo;"), - new TypeDescriptor("LBar;") - )); - assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LCow;"))); - } - } - - @Test - public void replaceArrayClasses() { - { - final MethodDescriptor oldSig = new MethodDescriptor("([LFoo;)[[[LBar;"); - final MethodDescriptor sig = oldSig.remap(s -> { - if (s.equals("Foo")) { - return "Food"; - } else if (s.equals("Bar")) { - return "Beer"; - } - return null; - }); - assertThat(sig.getArgumentDescs(), contains( - new TypeDescriptor("[LFood;") - )); - assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[[[LBeer;"))); - } - } - - @Test - public void equals() { - - // base - assertThat(new MethodDescriptor("()V"), is(new MethodDescriptor("()V"))); - - // arguments - assertThat(new MethodDescriptor("(I)V"), is(new MethodDescriptor("(I)V"))); - assertThat(new MethodDescriptor("(ZIZ)V"), is(new MethodDescriptor("(ZIZ)V"))); - assertThat(new MethodDescriptor("(LFoo;)V"), is(new MethodDescriptor("(LFoo;)V"))); - assertThat(new MethodDescriptor("(LFoo;LBar;)V"), is(new MethodDescriptor("(LFoo;LBar;)V"))); - assertThat(new MethodDescriptor("([I)V"), is(new MethodDescriptor("([I)V"))); - assertThat(new MethodDescriptor("([[D[[[J)V"), is(new MethodDescriptor("([[D[[[J)V"))); - - assertThat(new MethodDescriptor("()V"), is(not(new MethodDescriptor("(I)V")))); - assertThat(new MethodDescriptor("(I)V"), is(not(new MethodDescriptor("()V")))); - assertThat(new MethodDescriptor("(IJ)V"), is(not(new MethodDescriptor("(JI)V")))); - assertThat(new MethodDescriptor("([[Z)V"), is(not(new MethodDescriptor("([[LFoo;)V")))); - assertThat(new MethodDescriptor("(LFoo;LBar;)V"), is(not(new MethodDescriptor("(LFoo;LCow;)V")))); - assertThat(new MethodDescriptor("([LFoo;LBar;)V"), is(not(new MethodDescriptor("(LFoo;LCow;)V")))); - - // return desc - assertThat(new MethodDescriptor("()I"), is(new MethodDescriptor("()I"))); - assertThat(new MethodDescriptor("()Z"), is(new MethodDescriptor("()Z"))); - assertThat(new MethodDescriptor("()[D"), is(new MethodDescriptor("()[D"))); - assertThat(new MethodDescriptor("()[[[Z"), is(new MethodDescriptor("()[[[Z"))); - assertThat(new MethodDescriptor("()LFoo;"), is(new MethodDescriptor("()LFoo;"))); - assertThat(new MethodDescriptor("()[LFoo;"), is(new MethodDescriptor("()[LFoo;"))); - - assertThat(new MethodDescriptor("()I"), is(not(new MethodDescriptor("()Z")))); - assertThat(new MethodDescriptor("()Z"), is(not(new MethodDescriptor("()I")))); - assertThat(new MethodDescriptor("()[D"), is(not(new MethodDescriptor("()[J")))); - assertThat(new MethodDescriptor("()[[[Z"), is(not(new MethodDescriptor("()[[Z")))); - assertThat(new MethodDescriptor("()LFoo;"), is(not(new MethodDescriptor("()LBar;")))); - assertThat(new MethodDescriptor("()[LFoo;"), is(not(new MethodDescriptor("()[LBar;")))); - } - - @Test - public void testToString() { - assertThat(new MethodDescriptor("()V").toString(), is("()V")); - assertThat(new MethodDescriptor("(I)V").toString(), is("(I)V")); - assertThat(new MethodDescriptor("(ZIZ)V").toString(), is("(ZIZ)V")); - assertThat(new MethodDescriptor("(LFoo;)V").toString(), is("(LFoo;)V")); - assertThat(new MethodDescriptor("(LFoo;LBar;)V").toString(), is("(LFoo;LBar;)V")); - assertThat(new MethodDescriptor("([I)V").toString(), is("([I)V")); - assertThat(new MethodDescriptor("([[D[[[J)V").toString(), is("([[D[[[J)V")); - } -} diff --git a/src/test/java/cuchaz/enigma/TestSourceIndex.java b/src/test/java/cuchaz/enigma/TestSourceIndex.java deleted file mode 100644 index b2016089..00000000 --- a/src/test/java/cuchaz/enigma/TestSourceIndex.java +++ /dev/null @@ -1,74 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -import com.google.common.collect.Sets; -import cuchaz.enigma.analysis.ClassCache; -import cuchaz.enigma.source.*; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import org.junit.Test; - -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Set; - -public class TestSourceIndex { - @Test - public void indexEverything() - throws Exception { - // Figure out where Minecraft is... - final String mcDir = System.getProperty("enigma.test.minecraftdir"); - Path mcJar = null; - if (mcDir == null) { - String osname = System.getProperty("os.name").toLowerCase(); - if (osname.contains("nix") || osname.contains("nux") || osname.contains("solaris")) { - mcJar = Paths.get(System.getProperty("user.home"), ".minecraft/versions/1.8.3/1.8.3.jar"); - } else if (osname.contains("mac") || osname.contains("darwin")) { - mcJar = Paths.get(System.getProperty("user.home"), "Library/Application Support/minecraft/versions/1.8.3/1.8.3.jar"); - } else if (osname.contains("win")) { - mcJar = Paths.get(System.getenv("AppData"), ".minecraft/versions/1.8.3/1.8.3.jar"); - } - } else { - mcJar = Paths.get(mcDir, "versions/1.8.3/1.8.3.jar"); - } - - if (mcJar == null) { - throw new NullPointerException("Couldn't find jar"); - } - - Enigma enigma = Enigma.create(); - EnigmaProject project = enigma.openJar(mcJar, ProgressListener.none()); - - ClassCache classCache = project.getClassCache(); - JarIndex index = project.getJarIndex(); - - Decompiler decompiler = Decompilers.PROCYON.create(classCache, new SourceSettings(false, false)); - - // get all classes that aren't inner classes - Set classEntries = Sets.newHashSet(); - for (ClassEntry obfClassEntry : index.getEntryIndex().getClasses()) { - if (!obfClassEntry.isInnerClass()) { - classEntries.add(obfClassEntry); - } - } - - for (ClassEntry obfClassEntry : classEntries) { - try { - Source source = decompiler.getSource(obfClassEntry.getName()); - source.index(); - } catch (Throwable t) { - throw new Error("Unable to index " + obfClassEntry, t); - } - } - } -} diff --git a/src/test/java/cuchaz/enigma/TestTokensConstructors.java b/src/test/java/cuchaz/enigma/TestTokensConstructors.java deleted file mode 100644 index 0398de4f..00000000 --- a/src/test/java/cuchaz/enigma/TestTokensConstructors.java +++ /dev/null @@ -1,137 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -import cuchaz.enigma.translation.representation.entry.MethodEntry; -import org.junit.Test; - -import java.nio.file.Paths; - -import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByMethod; -import static cuchaz.enigma.TestEntryFactory.newMethod; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; - -public class TestTokensConstructors extends TokenChecker { - - public TestTokensConstructors() - throws Exception { - super(Paths.get("build/test-obf/constructors.jar")); - } - - @Test - public void baseDeclarations() { - assertThat(getDeclarationToken(newMethod("a", "", "()V")), is("a")); - assertThat(getDeclarationToken(newMethod("a", "", "(I)V")), is("a")); - } - - @Test - public void subDeclarations() { - assertThat(getDeclarationToken(newMethod("d", "", "()V")), is("d")); - assertThat(getDeclarationToken(newMethod("d", "", "(I)V")), is("d")); - assertThat(getDeclarationToken(newMethod("d", "", "(II)V")), is("d")); - assertThat(getDeclarationToken(newMethod("d", "", "(III)V")), is("d")); - } - - @Test - public void subsubDeclarations() { - assertThat(getDeclarationToken(newMethod("e", "", "(I)V")), is("e")); - } - - @Test - public void defaultDeclarations() { - assertThat(getDeclarationToken(newMethod("c", "", "()V")), nullValue()); - } - - @Test - public void baseDefaultReferences() { - MethodEntry source = newMethod("a", "", "()V"); - assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "a", "()V")), - containsInAnyOrder("a") - ); - assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "d", "", "()V")), - is(empty()) // implicit call, not decompiled to token - ); - assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "d", "", "(III)V")), - is(empty()) // implicit call, not decompiled to token - ); - } - - @Test - public void baseIntReferences() { - MethodEntry source = newMethod("a", "", "(I)V"); - assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "b", "()V")), - containsInAnyOrder("a") - ); - } - - @Test - public void subDefaultReferences() { - MethodEntry source = newMethod("d", "", "()V"); - assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "c", "()V")), - containsInAnyOrder("d") - ); - assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "d", "", "(I)V")), - containsInAnyOrder("this") - ); - } - - @Test - public void subIntReferences() { - MethodEntry source = newMethod("d", "", "(I)V"); - assertThat(getReferenceTokens( - newBehaviorReferenceByMethod(source, "b", "d", "()V")), - containsInAnyOrder("d") - ); - assertThat(getReferenceTokens( - newBehaviorReferenceByMethod(source, "d", "", "(II)V")), - containsInAnyOrder("this") - ); - assertThat(getReferenceTokens( - newBehaviorReferenceByMethod(source, "e", "", "(I)V")), - containsInAnyOrder("super") - ); - } - - @Test - public void subIntIntReferences() { - MethodEntry source = newMethod("d", "", "(II)V"); - assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "e", "()V")), - containsInAnyOrder("d") - ); - } - - @Test - public void subsubIntReferences() { - MethodEntry source = newMethod("e", "", "(I)V"); - assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "f", "()V")), - containsInAnyOrder("e") - ); - } - - @Test - public void defaultConstructableReferences() { - MethodEntry source = newMethod("c", "", "()V"); - assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "g", "()V")), - containsInAnyOrder("c") - ); - } -} diff --git a/src/test/java/cuchaz/enigma/TestTranslator.java b/src/test/java/cuchaz/enigma/TestTranslator.java deleted file mode 100644 index a420afe1..00000000 --- a/src/test/java/cuchaz/enigma/TestTranslator.java +++ /dev/null @@ -1,155 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -import cuchaz.enigma.translation.representation.entry.Entry; -import org.junit.BeforeClass; -import org.junit.Test; - -import static cuchaz.enigma.TestEntryFactory.*; - -public class TestTranslator { - - @BeforeClass - public static void beforeClass() - throws Exception { - //TODO FIx - //deobfuscator = new Enigma(new JarFile("build/test-obf/translation.jar")); - //try (InputStream in = TestTranslator.class.getResourceAsStream("/cuchaz/enigma/resources/translation.mappings")) { - // mappings = new MappingsJsonReader().read(new InputStreamReader(in)); - // deobfuscator.setMappings(mappings); - // deobfTranslator = deobfuscator.getTranslator(TranslationDirection.Deobfuscating); - // obfTranslator = deobfuscator.getTranslator(TranslationDirection.Obfuscating); - //} - } - - @Test - public void basicClasses() { - assertMapping(newClass("a"), newClass("deobf/A_Basic")); - assertMapping(newClass("b"), newClass("deobf/B_BaseClass")); - assertMapping(newClass("c"), newClass("deobf/C_SubClass")); - } - - @Test - public void basicFields() { - assertMapping(newField("a", "a", "I"), newField("deobf/A_Basic", "f1", "I")); - assertMapping(newField("a", "a", "F"), newField("deobf/A_Basic", "f2", "F")); - assertMapping(newField("a", "a", "Ljava/lang/String;"), newField("deobf/A_Basic", "f3", "Ljava/lang/String;")); - } - - @Test - public void basicMethods() { - assertMapping(newMethod("a", "a", "()V"), newMethod("deobf/A_Basic", "m1", "()V")); - assertMapping(newMethod("a", "a", "()I"), newMethod("deobf/A_Basic", "m2", "()I")); - assertMapping(newMethod("a", "a", "(I)V"), newMethod("deobf/A_Basic", "m3", "(I)V")); - assertMapping(newMethod("a", "a", "(I)I"), newMethod("deobf/A_Basic", "m4", "(I)I")); - } - - // TODO: basic constructors - - @Test - public void inheritanceFields() { - assertMapping(newField("b", "a", "I"), newField("deobf/B_BaseClass", "f1", "I")); - assertMapping(newField("b", "a", "C"), newField("deobf/B_BaseClass", "f2", "C")); - assertMapping(newField("c", "b", "I"), newField("deobf/C_SubClass", "f3", "I")); - assertMapping(newField("c", "c", "I"), newField("deobf/C_SubClass", "f4", "I")); - } - - @Test - public void inheritanceFieldsShadowing() { - assertMapping(newField("c", "b", "C"), newField("deobf/C_SubClass", "f2", "C")); - } - - @Test - public void inheritanceFieldsBySubClass() { - assertMapping(newField("c", "a", "I"), newField("deobf/C_SubClass", "f1", "I")); - // NOTE: can't reference b.C by subclass since it's shadowed - } - - @Test - public void inheritanceMethods() { - assertMapping(newMethod("b", "a", "()I"), newMethod("deobf/B_BaseClass", "m1", "()I")); - assertMapping(newMethod("b", "b", "()I"), newMethod("deobf/B_BaseClass", "m2", "()I")); - assertMapping(newMethod("c", "c", "()I"), newMethod("deobf/C_SubClass", "m3", "()I")); - } - - @Test - public void inheritanceMethodsOverrides() { - assertMapping(newMethod("c", "a", "()I"), newMethod("deobf/C_SubClass", "m1", "()I")); - } - - @Test - public void inheritanceMethodsBySubClass() { - assertMapping(newMethod("c", "b", "()I"), newMethod("deobf/C_SubClass", "m2", "()I")); - } - - @Test - public void innerClasses() { - - // classes - assertMapping(newClass("g"), newClass("deobf/G_OuterClass")); - assertMapping(newClass("g$a"), newClass("deobf/G_OuterClass$A_InnerClass")); - assertMapping(newClass("g$a$a"), newClass("deobf/G_OuterClass$A_InnerClass$A_InnerInnerClass")); - assertMapping(newClass("g$b"), newClass("deobf/G_OuterClass$b")); - assertMapping(newClass("g$b$a"), newClass("deobf/G_OuterClass$b$A_NamedInnerClass")); - - // fields - assertMapping(newField("g$a", "a", "I"), newField("deobf/G_OuterClass$A_InnerClass", "f1", "I")); - assertMapping(newField("g$a", "a", "Ljava/lang/String;"), newField("deobf/G_OuterClass$A_InnerClass", "f2", "Ljava/lang/String;")); - assertMapping(newField("g$a$a", "a", "I"), newField("deobf/G_OuterClass$A_InnerClass$A_InnerInnerClass", "f3", "I")); - assertMapping(newField("g$b$a", "a", "I"), newField("deobf/G_OuterClass$b$A_NamedInnerClass", "f4", "I")); - - // methods - assertMapping(newMethod("g$a", "a", "()V"), newMethod("deobf/G_OuterClass$A_InnerClass", "m1", "()V")); - assertMapping(newMethod("g$a$a", "a", "()V"), newMethod("deobf/G_OuterClass$A_InnerClass$A_InnerInnerClass", "m2", "()V")); - } - - @Test - public void namelessClass() { - assertMapping(newClass("h"), newClass("h")); - } - - @Test - public void testGenerics() { - - // classes - assertMapping(newClass("i"), newClass("deobf/I_Generics")); - assertMapping(newClass("i$a"), newClass("deobf/I_Generics$A_Type")); - assertMapping(newClass("i$b"), newClass("deobf/I_Generics$B_Generic")); - - // fields - assertMapping(newField("i", "a", "Ljava/util/List;"), newField("deobf/I_Generics", "f1", "Ljava/util/List;")); - assertMapping(newField("i", "b", "Ljava/util/List;"), newField("deobf/I_Generics", "f2", "Ljava/util/List;")); - assertMapping(newField("i", "a", "Ljava/util/Map;"), newField("deobf/I_Generics", "f3", "Ljava/util/Map;")); - assertMapping(newField("i$b", "a", "Ljava/lang/Object;"), newField("deobf/I_Generics$B_Generic", "f4", "Ljava/lang/Object;")); - assertMapping(newField("i", "a", "Li$b;"), newField("deobf/I_Generics", "f5", "Ldeobf/I_Generics$B_Generic;")); - assertMapping(newField("i", "b", "Li$b;"), newField("deobf/I_Generics", "f6", "Ldeobf/I_Generics$B_Generic;")); - - // methods - assertMapping(newMethod("i$b", "a", "()Ljava/lang/Object;"), newMethod("deobf/I_Generics$B_Generic", "m1", "()Ljava/lang/Object;")); - } - - private void assertMapping(Entry obf, Entry deobf) { - //assertThat(deobfTranslator.translateEntry(obf), is(deobf)); - //assertThat(obfTranslator.translateEntry(deobf), is(obf)); - - //String deobfName = deobfTranslator.translate(obf); - //if (deobfName != null) { - // assertThat(deobfName, is(deobf.getName())); - //} - - //String obfName = obfTranslator.translate(deobf); - //if (obfName != null) { - // assertThat(obfName, is(obf.getName())); - //} - } -} diff --git a/src/test/java/cuchaz/enigma/TestTypeDescriptor.java b/src/test/java/cuchaz/enigma/TestTypeDescriptor.java deleted file mode 100644 index b9ebe559..00000000 --- a/src/test/java/cuchaz/enigma/TestTypeDescriptor.java +++ /dev/null @@ -1,243 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -import cuchaz.enigma.translation.representation.TypeDescriptor; -import org.junit.Test; - -import static cuchaz.enigma.TestEntryFactory.newClass; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; - -public class TestTypeDescriptor { - - @Test - public void isVoid() { - assertThat(new TypeDescriptor("V").isVoid(), is(true)); - assertThat(new TypeDescriptor("Z").isVoid(), is(false)); - assertThat(new TypeDescriptor("B").isVoid(), is(false)); - assertThat(new TypeDescriptor("C").isVoid(), is(false)); - assertThat(new TypeDescriptor("I").isVoid(), is(false)); - assertThat(new TypeDescriptor("J").isVoid(), is(false)); - assertThat(new TypeDescriptor("F").isVoid(), is(false)); - assertThat(new TypeDescriptor("D").isVoid(), is(false)); - assertThat(new TypeDescriptor("LFoo;").isVoid(), is(false)); - assertThat(new TypeDescriptor("[I").isVoid(), is(false)); - } - - @Test - public void isPrimitive() { - assertThat(new TypeDescriptor("V").isPrimitive(), is(false)); - assertThat(new TypeDescriptor("Z").isPrimitive(), is(true)); - assertThat(new TypeDescriptor("B").isPrimitive(), is(true)); - assertThat(new TypeDescriptor("C").isPrimitive(), is(true)); - assertThat(new TypeDescriptor("I").isPrimitive(), is(true)); - assertThat(new TypeDescriptor("J").isPrimitive(), is(true)); - assertThat(new TypeDescriptor("F").isPrimitive(), is(true)); - assertThat(new TypeDescriptor("D").isPrimitive(), is(true)); - assertThat(new TypeDescriptor("LFoo;").isPrimitive(), is(false)); - assertThat(new TypeDescriptor("[I").isPrimitive(), is(false)); - } - - @Test - public void getPrimitive() { - assertThat(new TypeDescriptor("Z").getPrimitive(), is(TypeDescriptor.Primitive.BOOLEAN)); - assertThat(new TypeDescriptor("B").getPrimitive(), is(TypeDescriptor.Primitive.BYTE)); - assertThat(new TypeDescriptor("C").getPrimitive(), is(TypeDescriptor.Primitive.CHARACTER)); - assertThat(new TypeDescriptor("I").getPrimitive(), is(TypeDescriptor.Primitive.INTEGER)); - assertThat(new TypeDescriptor("J").getPrimitive(), is(TypeDescriptor.Primitive.LONG)); - assertThat(new TypeDescriptor("F").getPrimitive(), is(TypeDescriptor.Primitive.FLOAT)); - assertThat(new TypeDescriptor("D").getPrimitive(), is(TypeDescriptor.Primitive.DOUBLE)); - } - - @Test - public void isClass() { - assertThat(new TypeDescriptor("V").isType(), is(false)); - assertThat(new TypeDescriptor("Z").isType(), is(false)); - assertThat(new TypeDescriptor("B").isType(), is(false)); - assertThat(new TypeDescriptor("C").isType(), is(false)); - assertThat(new TypeDescriptor("I").isType(), is(false)); - assertThat(new TypeDescriptor("J").isType(), is(false)); - assertThat(new TypeDescriptor("F").isType(), is(false)); - assertThat(new TypeDescriptor("D").isType(), is(false)); - assertThat(new TypeDescriptor("LFoo;").isType(), is(true)); - assertThat(new TypeDescriptor("[I").isType(), is(false)); - } - - @Test - public void getClassEntry() { - assertThat(new TypeDescriptor("LFoo;").getTypeEntry(), is(newClass("Foo"))); - assertThat(new TypeDescriptor("Ljava/lang/String;").getTypeEntry(), is(newClass("java/lang/String"))); - } - - @Test - public void getArrayClassEntry() { - assertThat(new TypeDescriptor("[LFoo;").getTypeEntry(), is(newClass("Foo"))); - assertThat(new TypeDescriptor("[[[Ljava/lang/String;").getTypeEntry(), is(newClass("java/lang/String"))); - } - - @Test - public void isArray() { - assertThat(new TypeDescriptor("V").isArray(), is(false)); - assertThat(new TypeDescriptor("Z").isArray(), is(false)); - assertThat(new TypeDescriptor("B").isArray(), is(false)); - assertThat(new TypeDescriptor("C").isArray(), is(false)); - assertThat(new TypeDescriptor("I").isArray(), is(false)); - assertThat(new TypeDescriptor("J").isArray(), is(false)); - assertThat(new TypeDescriptor("F").isArray(), is(false)); - assertThat(new TypeDescriptor("D").isArray(), is(false)); - assertThat(new TypeDescriptor("LFoo;").isArray(), is(false)); - assertThat(new TypeDescriptor("[I").isArray(), is(true)); - } - - @Test - public void getArrayDimension() { - assertThat(new TypeDescriptor("[I").getArrayDimension(), is(1)); - assertThat(new TypeDescriptor("[[I").getArrayDimension(), is(2)); - assertThat(new TypeDescriptor("[[[I").getArrayDimension(), is(3)); - } - - @Test - public void getArrayType() { - assertThat(new TypeDescriptor("[I").getArrayType(), is(new TypeDescriptor("I"))); - assertThat(new TypeDescriptor("[[I").getArrayType(), is(new TypeDescriptor("I"))); - assertThat(new TypeDescriptor("[[[I").getArrayType(), is(new TypeDescriptor("I"))); - assertThat(new TypeDescriptor("[Ljava/lang/String;").getArrayType(), is(new TypeDescriptor("Ljava/lang/String;"))); - } - - @Test - public void hasClass() { - assertThat(new TypeDescriptor("LFoo;").containsType(), is(true)); - assertThat(new TypeDescriptor("Ljava/lang/String;").containsType(), is(true)); - assertThat(new TypeDescriptor("[LBar;").containsType(), is(true)); - assertThat(new TypeDescriptor("[[[LCat;").containsType(), is(true)); - - assertThat(new TypeDescriptor("V").containsType(), is(false)); - assertThat(new TypeDescriptor("[I").containsType(), is(false)); - assertThat(new TypeDescriptor("[[[I").containsType(), is(false)); - assertThat(new TypeDescriptor("Z").containsType(), is(false)); - } - - @Test - public void parseVoid() { - final String answer = "V"; - assertThat(TypeDescriptor.parseFirst("V"), is(answer)); - assertThat(TypeDescriptor.parseFirst("VVV"), is(answer)); - assertThat(TypeDescriptor.parseFirst("VIJ"), is(answer)); - assertThat(TypeDescriptor.parseFirst("V[I"), is(answer)); - assertThat(TypeDescriptor.parseFirst("VLFoo;"), is(answer)); - assertThat(TypeDescriptor.parseFirst("V[LFoo;"), is(answer)); - } - - @Test - public void parsePrimitive() { - final String answer = "I"; - assertThat(TypeDescriptor.parseFirst("I"), is(answer)); - assertThat(TypeDescriptor.parseFirst("III"), is(answer)); - assertThat(TypeDescriptor.parseFirst("IJZ"), is(answer)); - assertThat(TypeDescriptor.parseFirst("I[I"), is(answer)); - assertThat(TypeDescriptor.parseFirst("ILFoo;"), is(answer)); - assertThat(TypeDescriptor.parseFirst("I[LFoo;"), is(answer)); - } - - @Test - public void parseClass() { - { - final String answer = "LFoo;"; - assertThat(TypeDescriptor.parseFirst("LFoo;"), is(answer)); - assertThat(TypeDescriptor.parseFirst("LFoo;I"), is(answer)); - assertThat(TypeDescriptor.parseFirst("LFoo;JZ"), is(answer)); - assertThat(TypeDescriptor.parseFirst("LFoo;[I"), is(answer)); - assertThat(TypeDescriptor.parseFirst("LFoo;LFoo;"), is(answer)); - assertThat(TypeDescriptor.parseFirst("LFoo;[LFoo;"), is(answer)); - } - { - final String answer = "Ljava/lang/String;"; - assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;"), is(answer)); - assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;I"), is(answer)); - assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;JZ"), is(answer)); - assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;[I"), is(answer)); - assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;LFoo;"), is(answer)); - assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;[LFoo;"), is(answer)); - } - } - - @Test - public void parseArray() { - { - final String answer = "[I"; - assertThat(TypeDescriptor.parseFirst("[I"), is(answer)); - assertThat(TypeDescriptor.parseFirst("[III"), is(answer)); - assertThat(TypeDescriptor.parseFirst("[IJZ"), is(answer)); - assertThat(TypeDescriptor.parseFirst("[I[I"), is(answer)); - assertThat(TypeDescriptor.parseFirst("[ILFoo;"), is(answer)); - } - { - final String answer = "[[I"; - assertThat(TypeDescriptor.parseFirst("[[I"), is(answer)); - assertThat(TypeDescriptor.parseFirst("[[III"), is(answer)); - assertThat(TypeDescriptor.parseFirst("[[IJZ"), is(answer)); - assertThat(TypeDescriptor.parseFirst("[[I[I"), is(answer)); - assertThat(TypeDescriptor.parseFirst("[[ILFoo;"), is(answer)); - } - { - final String answer = "[LFoo;"; - assertThat(TypeDescriptor.parseFirst("[LFoo;"), is(answer)); - assertThat(TypeDescriptor.parseFirst("[LFoo;II"), is(answer)); - assertThat(TypeDescriptor.parseFirst("[LFoo;JZ"), is(answer)); - assertThat(TypeDescriptor.parseFirst("[LFoo;[I"), is(answer)); - assertThat(TypeDescriptor.parseFirst("[LFoo;LFoo;"), is(answer)); - } - } - - @Test - public void equals() { - assertThat(new TypeDescriptor("V"), is(new TypeDescriptor("V"))); - assertThat(new TypeDescriptor("Z"), is(new TypeDescriptor("Z"))); - assertThat(new TypeDescriptor("B"), is(new TypeDescriptor("B"))); - assertThat(new TypeDescriptor("C"), is(new TypeDescriptor("C"))); - assertThat(new TypeDescriptor("I"), is(new TypeDescriptor("I"))); - assertThat(new TypeDescriptor("J"), is(new TypeDescriptor("J"))); - assertThat(new TypeDescriptor("F"), is(new TypeDescriptor("F"))); - assertThat(new TypeDescriptor("D"), is(new TypeDescriptor("D"))); - assertThat(new TypeDescriptor("LFoo;"), is(new TypeDescriptor("LFoo;"))); - assertThat(new TypeDescriptor("[I"), is(new TypeDescriptor("[I"))); - assertThat(new TypeDescriptor("[[[I"), is(new TypeDescriptor("[[[I"))); - assertThat(new TypeDescriptor("[LFoo;"), is(new TypeDescriptor("[LFoo;"))); - - assertThat(new TypeDescriptor("V"), is(not(new TypeDescriptor("I")))); - assertThat(new TypeDescriptor("I"), is(not(new TypeDescriptor("J")))); - assertThat(new TypeDescriptor("I"), is(not(new TypeDescriptor("LBar;")))); - assertThat(new TypeDescriptor("I"), is(not(new TypeDescriptor("[I")))); - assertThat(new TypeDescriptor("LFoo;"), is(not(new TypeDescriptor("LBar;")))); - assertThat(new TypeDescriptor("[I"), is(not(new TypeDescriptor("[Z")))); - assertThat(new TypeDescriptor("[[[I"), is(not(new TypeDescriptor("[I")))); - assertThat(new TypeDescriptor("[LFoo;"), is(not(new TypeDescriptor("[LBar;")))); - } - - @Test - public void testToString() { - assertThat(new TypeDescriptor("V").toString(), is("V")); - assertThat(new TypeDescriptor("Z").toString(), is("Z")); - assertThat(new TypeDescriptor("B").toString(), is("B")); - assertThat(new TypeDescriptor("C").toString(), is("C")); - assertThat(new TypeDescriptor("I").toString(), is("I")); - assertThat(new TypeDescriptor("J").toString(), is("J")); - assertThat(new TypeDescriptor("F").toString(), is("F")); - assertThat(new TypeDescriptor("D").toString(), is("D")); - assertThat(new TypeDescriptor("LFoo;").toString(), is("LFoo;")); - assertThat(new TypeDescriptor("[I").toString(), is("[I")); - assertThat(new TypeDescriptor("[[[I").toString(), is("[[[I")); - assertThat(new TypeDescriptor("[LFoo;").toString(), is("[LFoo;")); - } -} diff --git a/src/test/java/cuchaz/enigma/TokenChecker.java b/src/test/java/cuchaz/enigma/TokenChecker.java deleted file mode 100644 index 48d0c830..00000000 --- a/src/test/java/cuchaz/enigma/TokenChecker.java +++ /dev/null @@ -1,65 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma; - -import com.google.common.collect.Lists; -import cuchaz.enigma.analysis.ClassCache; -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.source.SourceIndex; -import cuchaz.enigma.source.*; -import cuchaz.enigma.analysis.Token; -import cuchaz.enigma.translation.representation.entry.Entry; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Collection; -import java.util.List; - -public class TokenChecker { - private final Decompiler decompiler; - - protected TokenChecker(Path path) throws IOException { - ClassCache classCache = ClassCache.of(path); - decompiler = Decompilers.PROCYON.create(classCache, new SourceSettings(false, false)); - } - - protected String getDeclarationToken(Entry entry) { - // decompile the class - Source source = decompiler.getSource(entry.getContainingClass().getFullName()); - // DEBUG - // tree.acceptVisitor( new TreeDumpVisitor( new File( "tree." + entry.getClassName().replace( '/', '.' ) + ".txt" ) ), null ); - String string = source.asString(); - SourceIndex index = source.index(); - - // get the token value - Token token = index.getDeclarationToken(entry); - if (token == null) { - return null; - } - return string.substring(token.start, token.end); - } - - @SuppressWarnings("unchecked") - protected Collection getReferenceTokens(EntryReference, ? extends Entry> reference) { - // decompile the class - Source source = decompiler.getSource(reference.context.getContainingClass().getFullName()); - String string = source.asString(); - SourceIndex index = source.index(); - - // get the token values - List values = Lists.newArrayList(); - for (Token token : index.getReferenceTokens((EntryReference, Entry>) reference)) { - values.add(string.substring(token.start, token.end)); - } - return values; - } -} diff --git a/src/test/java/cuchaz/enigma/command/CheckMappingsCommandTest.java b/src/test/java/cuchaz/enigma/command/CheckMappingsCommandTest.java deleted file mode 100644 index f3b9f85e..00000000 --- a/src/test/java/cuchaz/enigma/command/CheckMappingsCommandTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package cuchaz.enigma.command; - -import org.junit.Test; - -import java.io.File; - -public class CheckMappingsCommandTest { - - @Test(expected = IllegalStateException.class) - public void testWrong() throws Exception { - new CheckMappingsCommand().run(new File("build/test-obf/packageAccess.jar").getAbsolutePath(), new File("src/test/resources" + - "/packageAccess/wrongMappings").getAbsolutePath()); - } - - @Test - public void testRight() throws Exception { - new CheckMappingsCommand().run(new File("build/test-obf/packageAccess.jar").getAbsolutePath(), new File("src/test/resources" + - "/packageAccess/correctMappings").getAbsolutePath()); - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/Keep.java b/src/test/java/cuchaz/enigma/inputs/Keep.java deleted file mode 100644 index 4dbe8e2f..00000000 --- a/src/test/java/cuchaz/enigma/inputs/Keep.java +++ /dev/null @@ -1,18 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs; - -public class Keep { - public static void main(String[] args) { - System.out.println("Keep me!"); - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java b/src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java deleted file mode 100644 index f07e1f8b..00000000 --- a/src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java +++ /dev/null @@ -1,26 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.constructors; - -// a -public class BaseClass { - - // ()V - public BaseClass() { - System.out.println("Default constructor"); - } - - // (I)V - public BaseClass(int i) { - System.out.println("Int constructor " + i); - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/constructors/Caller.java b/src/test/java/cuchaz/enigma/inputs/constructors/Caller.java deleted file mode 100644 index 71439fd1..00000000 --- a/src/test/java/cuchaz/enigma/inputs/constructors/Caller.java +++ /dev/null @@ -1,58 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.constructors; - -// b -public class Caller { - - // a()V - public void callBaseDefault() { - // a.()V - System.out.println(new BaseClass()); - } - - // b()V - public void callBaseInt() { - // a.(I)V - System.out.println(new BaseClass(5)); - } - - // c()V - public void callSubDefault() { - // d.()V - System.out.println(new SubClass()); - } - - // d()V - public void callSubInt() { - // d.(I)V - System.out.println(new SubClass(6)); - } - - // e()V - public void callSubIntInt() { - // d.(II)V - System.out.println(new SubClass(4, 2)); - } - - // f()V - public void callSubSubInt() { - // e.(I)V - System.out.println(new SubSubClass(3)); - } - - // g()V - public void callDefaultConstructable() { - // c.()V - System.out.println(new DefaultConstructable()); - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java b/src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java deleted file mode 100644 index c3d41705..00000000 --- a/src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java +++ /dev/null @@ -1,16 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.constructors; - -public class DefaultConstructable { - // only default constructor -} diff --git a/src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java b/src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java deleted file mode 100644 index bc56b3b2..00000000 --- a/src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java +++ /dev/null @@ -1,39 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.constructors; - -// d extends a -public class SubClass extends BaseClass { - - // ()V - public SubClass() { - // a.()V - } - - // (I)V - public SubClass(int num) { - // ()V - this(); - System.out.println("SubClass " + num); - } - - // (II)V - public SubClass(int a, int b) { - // (I)V - this(a + b); - } - - // (III)V - public SubClass(int a, int b, int c) { - // a.()V - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java b/src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java deleted file mode 100644 index 87b69d32..00000000 --- a/src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java +++ /dev/null @@ -1,22 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.constructors; - -// e extends d -public class SubSubClass extends SubClass { - - // (I)V - public SubSubClass(int i) { - // c.(I)V - super(i); - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java deleted file mode 100644 index b9c4929c..00000000 --- a/src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java +++ /dev/null @@ -1,32 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.inheritanceTree; - -// a -public abstract class BaseClass { - - // a - private String name; - - // (Ljava/lang/String;)V - protected BaseClass(String name) { - this.name = name; - } - - // a()Ljava/lang/String; - public String getName() { - return name; - } - - // a()V - public abstract void doBaseThings(); -} diff --git a/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java deleted file mode 100644 index 50e963c0..00000000 --- a/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java +++ /dev/null @@ -1,22 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.inheritanceTree; - -// b extends a -public abstract class SubclassA extends BaseClass { - - // (Ljava/lang/String;)V - protected SubclassA(String name) { - // call to a.(Ljava/lang/String)V - super(name); - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java deleted file mode 100644 index d0dd664d..00000000 --- a/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java +++ /dev/null @@ -1,41 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.inheritanceTree; - -// c extends a -public class SubclassB extends BaseClass { - - // a - private int numThings; - - // ()V - protected SubclassB() { - // a.(Ljava/lang/String;)V - super("B"); - - // access to a - numThings = 4; - } - - @Override - // a()V - public void doBaseThings() { - // call to a.a()Ljava/lang/String; - System.out.println("Base things by B! " + getName()); - } - - // b()V - public void doBThings() { - // access to a - System.out.println("" + numThings + " B things!"); - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java deleted file mode 100644 index c5845702..00000000 --- a/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java +++ /dev/null @@ -1,35 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.inheritanceTree; - -// d extends b -public class SubsubclassAA extends SubclassA { - - protected SubsubclassAA() { - // call to b.(Ljava/lang/String;)V - super("AA"); - } - - @Override - // a()Ljava/lang/String; - public String getName() { - // call to b.a()Ljava/lang/String; - return "subsub" + super.getName(); - } - - @Override - // a()V - public void doBaseThings() { - // call to d.a()Ljava/lang/String; - System.out.println("Base things by " + getName()); - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java b/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java deleted file mode 100644 index f652d875..00000000 --- a/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java +++ /dev/null @@ -1,25 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.innerClasses; - -public class A_Anonymous { - - public void foo() { - Runnable runnable = new Runnable() { - @Override - public void run() { - // don't care - } - }; - runnable.run(); - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java b/src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java deleted file mode 100644 index d1b7601f..00000000 --- a/src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java +++ /dev/null @@ -1,24 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.innerClasses; - -public class B_AnonymousWithScopeArgs { - - public static void foo(final D_Simple arg) { - System.out.println(new Object() { - @Override - public String toString() { - return arg.toString(); - } - }); - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java b/src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java deleted file mode 100644 index 94061faa..00000000 --- a/src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java +++ /dev/null @@ -1,31 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.innerClasses; - -@SuppressWarnings("unused") -public class C_ConstructorArgs { - - Inner i; - - public void foo() { - i = new Inner(5); - } - - class Inner { - - private int a; - - public Inner(int a) { - this.a = a; - } - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java b/src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java deleted file mode 100644 index 71b3a6d8..00000000 --- a/src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java +++ /dev/null @@ -1,19 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.innerClasses; - -public class D_Simple { - - class Inner { - // nothing to do - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java b/src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java deleted file mode 100644 index 976ec426..00000000 --- a/src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java +++ /dev/null @@ -1,32 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.innerClasses; - -public class E_AnonymousWithOuterAccess { - - // reproduction of error case documented at: - // https://bitbucket.org/cuchaz/enigma/issue/61/stackoverflowerror-when-deobfuscating - - public Object makeInner() { - outerMethod(); - return new Object() { - @Override - public String toString() { - return outerMethod(); - } - }; - } - - private String outerMethod() { - return "foo"; - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java b/src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java deleted file mode 100644 index b1de3c9a..00000000 --- a/src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java +++ /dev/null @@ -1,30 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.innerClasses; - -public class F_ClassTree { - - public class Level1 { - - public int f1; - - public class Level2 { - - public int f2; - - public class Level3 { - - public int f3; - } - } - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/loneClass/LoneClass.java b/src/test/java/cuchaz/enigma/inputs/loneClass/LoneClass.java deleted file mode 100644 index ddc4e319..00000000 --- a/src/test/java/cuchaz/enigma/inputs/loneClass/LoneClass.java +++ /dev/null @@ -1,25 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.loneClass; - -public class LoneClass { - - private String name; - - public LoneClass(String name) { - this.name = name; - } - - public String getName() { - return name; - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/packageAccess/Base.java b/src/test/java/cuchaz/enigma/inputs/packageAccess/Base.java deleted file mode 100644 index 6f5fe304..00000000 --- a/src/test/java/cuchaz/enigma/inputs/packageAccess/Base.java +++ /dev/null @@ -1,7 +0,0 @@ -package cuchaz.enigma.inputs.packageAccess; - -public class Base { - protected int make() { - return 42; - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/packageAccess/SamePackageChild.java b/src/test/java/cuchaz/enigma/inputs/packageAccess/SamePackageChild.java deleted file mode 100644 index cf0f6574..00000000 --- a/src/test/java/cuchaz/enigma/inputs/packageAccess/SamePackageChild.java +++ /dev/null @@ -1,12 +0,0 @@ -package cuchaz.enigma.inputs.packageAccess; - -public class SamePackageChild extends Base { - - class Inner { - final int value; - - Inner() { - value = SamePackageChild.this.make(); // no synthetic method - } - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/packageAccess/sub/OtherPackageChild.java b/src/test/java/cuchaz/enigma/inputs/packageAccess/sub/OtherPackageChild.java deleted file mode 100644 index 19fb19c2..00000000 --- a/src/test/java/cuchaz/enigma/inputs/packageAccess/sub/OtherPackageChild.java +++ /dev/null @@ -1,14 +0,0 @@ -package cuchaz.enigma.inputs.packageAccess.sub; - -import cuchaz.enigma.inputs.packageAccess.Base; - -public class OtherPackageChild extends Base { - - class Inner { - final int value; - - Inner() { - value = OtherPackageChild.this.make(); // synthetic method call - } - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java b/src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java deleted file mode 100644 index 26f3718c..00000000 --- a/src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java +++ /dev/null @@ -1,33 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.translation; - -public class A_Basic { - - public int one; - public float two; - public String three; - - public void m1() { - } - - public int m2() { - return 42; - } - - public void m3(int a1) { - } - - public int m4(int a1) { - return 5; // chosen by fair die roll, guaranteed to be random - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/translation/B_BaseClass.java b/src/test/java/cuchaz/enigma/inputs/translation/B_BaseClass.java deleted file mode 100644 index fd7f6e7e..00000000 --- a/src/test/java/cuchaz/enigma/inputs/translation/B_BaseClass.java +++ /dev/null @@ -1,26 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.translation; - -public class B_BaseClass { - - public int f1; - public char f2; - - public int m1() { - return 5; - } - - public int m2() { - return 42; - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java b/src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java deleted file mode 100644 index 9d74e443..00000000 --- a/src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java +++ /dev/null @@ -1,28 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.translation; - -public class C_SubClass extends B_BaseClass { - - public char f2; // shadows B_BaseClass.f2 - public int f3; - public int f4; - - @Override - public int m1() { - return 32; - } - - public int m3() { - return 7; - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java b/src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java deleted file mode 100644 index 99c83bbf..00000000 --- a/src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java +++ /dev/null @@ -1,29 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.translation; - -import java.util.ArrayList; -import java.util.List; - -public class D_AnonymousTesting { - - public List getObjs() { - List objs = new ArrayList(); - objs.add(new Object() { - @Override - public String toString() { - return "Object!"; - } - }); - return objs; - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java b/src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java deleted file mode 100644 index 0b8cf2a5..00000000 --- a/src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java +++ /dev/null @@ -1,32 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.translation; - -import java.util.Iterator; - -public class E_Bridges implements Iterator { - - @Override - public boolean hasNext() { - return false; - } - - @Override - public String next() { - // the compiler will generate a bridge for this method - return "foo"; - } - - @Override - public void remove() { - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java b/src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java deleted file mode 100644 index 8a92792a..00000000 --- a/src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java +++ /dev/null @@ -1,31 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.translation; - -@SuppressWarnings("FinalizeCalledExplicitly") -public class F_ObjectMethods { - - public void callEmAll() - throws Throwable { - clone(); - equals(this); - finalize(); - getClass(); - hashCode(); - notify(); - notifyAll(); - toString(); - wait(); - wait(0); - wait(0, 0); - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/translation/G_OuterClass.java b/src/test/java/cuchaz/enigma/inputs/translation/G_OuterClass.java deleted file mode 100644 index a1e6a85c..00000000 --- a/src/test/java/cuchaz/enigma/inputs/translation/G_OuterClass.java +++ /dev/null @@ -1,36 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.translation; - -public class G_OuterClass { - - public class A_InnerClass { - - public int f1; - public String f2; - - public void m1() {} - - public class A_InnerInnerClass { - - public int f3; - - public void m2() {} - } - } - - public class B_NamelessClass { - public class A_NamedInnerClass { - public int f4; - } - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java b/src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java deleted file mode 100644 index 013c55ae..00000000 --- a/src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java +++ /dev/null @@ -1,40 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.translation; - -public class H_NamelessClass { - - public class A_InnerClass { - - public int f1; - public String f2; - - public void m1() {} - - public class A_InnerInnerClass { - - public int f3; - - public void m2() {} - } - } - - public class B_NamelessClass { - public class A_NamedInnerClass { - public int f4; - - public class A_AnotherInnerClass {} - - public class B_YetAnotherInnerClass {} - } - } -} diff --git a/src/test/java/cuchaz/enigma/inputs/translation/I_Generics.java b/src/test/java/cuchaz/enigma/inputs/translation/I_Generics.java deleted file mode 100644 index fd2ebdd5..00000000 --- a/src/test/java/cuchaz/enigma/inputs/translation/I_Generics.java +++ /dev/null @@ -1,35 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.inputs.translation; - -import java.util.List; -import java.util.Map; - -public class I_Generics { - - public List f1; - public List f2; - public Map f3; - public B_Generic f5; - public B_Generic f6; - - public class A_Type { - } - - public class B_Generic { - public T f4; - - public T m1() { - return null; - } - } -} diff --git a/src/test/java/cuchaz/enigma/mapping/TestComments.java b/src/test/java/cuchaz/enigma/mapping/TestComments.java deleted file mode 100644 index b3a60056..00000000 --- a/src/test/java/cuchaz/enigma/mapping/TestComments.java +++ /dev/null @@ -1,40 +0,0 @@ -package cuchaz.enigma.mapping; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.nio.file.Path; -import java.nio.file.Paths; - -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.throwables.MappingParseException; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.MappingFileNameFormat; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; -import cuchaz.enigma.translation.mapping.serde.EnigmaMappingsReader; -import cuchaz.enigma.translation.mapping.serde.TinyV2Writer; -import cuchaz.enigma.translation.mapping.tree.EntryTree; -import org.junit.Test; - -public class TestComments { - private static Path DIRECTORY; - - static { - try { - DIRECTORY = Paths.get(TestTinyV2InnerClasses.class.getResource("/comments/").toURI()); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - - @Test - public void testParseAndWrite() throws IOException, MappingParseException { - ProgressListener progressListener = ProgressListener.none(); - MappingSaveParameters params = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); - EntryTree mappings = EnigmaMappingsReader.DIRECTORY.read( - DIRECTORY, progressListener, params); - - new TinyV2Writer("intermediary", "named") - .write(mappings, DIRECTORY.resolve("convertedtiny.tiny"), progressListener, params); - } - -} \ No newline at end of file diff --git a/src/test/java/cuchaz/enigma/mapping/TestTinyV2InnerClasses.java b/src/test/java/cuchaz/enigma/mapping/TestTinyV2InnerClasses.java deleted file mode 100644 index 7cfdacaa..00000000 --- a/src/test/java/cuchaz/enigma/mapping/TestTinyV2InnerClasses.java +++ /dev/null @@ -1,41 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - * - * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.mapping; - -import cuchaz.enigma.Enigma; -import cuchaz.enigma.EnigmaProject; -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.translation.mapping.serde.EnigmaMappingsReader; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import org.junit.Test; - -import java.nio.file.Path; -import java.nio.file.Paths; - -import static cuchaz.enigma.TestEntryFactory.newClass; - -public final class TestTinyV2InnerClasses { - private Path jar; - private Path mappings; - - public TestTinyV2InnerClasses() throws Exception { - jar = Paths.get("build/test-obf/innerClasses.jar"); - mappings = Paths.get(TestTinyV2InnerClasses.class.getResource("/tinyV2InnerClasses/").toURI()); - } - -// @Test - public void testMappings() throws Exception { - EnigmaProject project = Enigma.create().openJar(jar, ProgressListener.none()); - project.setMappings(EnigmaMappingsReader.DIRECTORY.read(mappings, ProgressListener.none(), project.getEnigma().getProfile().getMappingSaveParameters())); - - } -} diff --git a/src/test/java/cuchaz/enigma/mapping/TestV2Main.java b/src/test/java/cuchaz/enigma/mapping/TestV2Main.java deleted file mode 100644 index 021a90cb..00000000 --- a/src/test/java/cuchaz/enigma/mapping/TestV2Main.java +++ /dev/null @@ -1,24 +0,0 @@ -package cuchaz.enigma.mapping; - -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.MappingFileNameFormat; -import cuchaz.enigma.translation.mapping.MappingSaveParameters; -import cuchaz.enigma.translation.mapping.serde.EnigmaMappingsReader; -import cuchaz.enigma.translation.mapping.serde.TinyV2Writer; -import cuchaz.enigma.translation.mapping.tree.EntryTree; - -import java.nio.file.Path; -import java.nio.file.Paths; - -public final class TestV2Main { - public static void main(String... args) throws Exception { - Path path = Paths.get(TestV2Main.class.getResource("/tinyV2InnerClasses/").toURI()); - - MappingSaveParameters parameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); - - EntryTree tree = EnigmaMappingsReader.DIRECTORY.read(path, ProgressListener.none(), parameters); - - new TinyV2Writer("obf", "deobf").write(tree, Paths.get("currentYarn.tiny"), ProgressListener.none(), parameters); - } -} diff --git a/src/test/java/cuchaz/enigma/resources/translation.mappings b/src/test/java/cuchaz/enigma/resources/translation.mappings deleted file mode 100644 index c08765c7..00000000 --- a/src/test/java/cuchaz/enigma/resources/translation.mappings +++ /dev/null @@ -1,41 +0,0 @@ -CLASS a deobf/A_Basic - FIELD a f1 I - FIELD a f2 F - FIELD a f3 Ljava/lang/String; - METHOD a m1 ()V - METHOD a m2 ()I - METHOD a m3 (I)V - METHOD a m4 (I)I -CLASS b deobf/B_BaseClass - FIELD a f1 I - FIELD a f2 C - METHOD a m1 ()I - METHOD b m2 ()I -CLASS c deobf/C_SubClass - FIELD b f2 C - FIELD b f3 I - FIELD c f4 I - METHOD a m1 ()I - METHOD c m3 ()I -CLASS g deobf/G_OuterClass - CLASS g$a A_InnerClass - CLASS g$a$a A_InnerInnerClass - FIELD a f3 I - METHOD a m2 ()V - FIELD a f1 I - FIELD a f2 Ljava/lang/String; - METHOD a m1 ()V - CLASS g$b - CLASS g$b$a A_NamedInnerClass - FIELD a f4 I -CLASS h -CLASS i deobf/I_Generics - CLASS i$a A_Type - CLASS i$b B_Generic - FIELD a f4 Ljava/lang/Object; - METHOD a m1 ()Ljava/lang/Object; - FIELD a f1 Ljava/util/List; - FIELD b f2 Ljava/util/List; - FIELD a f3 Ljava/util/Map; - FIELD a f5 Li$b; - FIELD b f6 Li$b; diff --git a/src/test/resources/comments/test.mapping b/src/test/resources/comments/test.mapping deleted file mode 100644 index d1345583..00000000 --- a/src/test/resources/comments/test.mapping +++ /dev/null @@ -1,18 +0,0 @@ -CLASS net/minecraft/class_1158 net/minecraft/util/math/Quaternion - COMMENT it circel - COMMENT next line - FIELD field_21493 IDENTITY Lnet/minecraft/class_1158; - COMMENT moar comment thing - COMMENT near field - METHOD foo bar (FFFF)V - COMMENT method comment - COMMENT second line - COMMENT third line - ARG 1 b - COMMENT arg comment - CLASS old new - COMMENT inner comment - FIELD field_19263 iterator Lnet/minecraft/class_3980; - METHOD tryAdvance (Ljava/util/function/Consumer;)Z - ARG 1 consumer - COMMENT very inner comment \ No newline at end of file diff --git a/src/test/resources/packageAccess/correctMappings/base/Base.mapping b/src/test/resources/packageAccess/correctMappings/base/Base.mapping deleted file mode 100644 index 0a86def0..00000000 --- a/src/test/resources/packageAccess/correctMappings/base/Base.mapping +++ /dev/null @@ -1 +0,0 @@ -CLASS a base/Base diff --git a/src/test/resources/packageAccess/correctMappings/base/One.mapping b/src/test/resources/packageAccess/correctMappings/base/One.mapping deleted file mode 100644 index dd4c2083..00000000 --- a/src/test/resources/packageAccess/correctMappings/base/One.mapping +++ /dev/null @@ -1 +0,0 @@ -CLASS b base/One diff --git a/src/test/resources/packageAccess/correctMappings/two/Two.mapping b/src/test/resources/packageAccess/correctMappings/two/Two.mapping deleted file mode 100644 index a179349c..00000000 --- a/src/test/resources/packageAccess/correctMappings/two/Two.mapping +++ /dev/null @@ -1 +0,0 @@ -CLASS c two/Two diff --git a/src/test/resources/packageAccess/wrongMappings/base/Base.mapping b/src/test/resources/packageAccess/wrongMappings/base/Base.mapping deleted file mode 100644 index 0a86def0..00000000 --- a/src/test/resources/packageAccess/wrongMappings/base/Base.mapping +++ /dev/null @@ -1 +0,0 @@ -CLASS a base/Base diff --git a/src/test/resources/packageAccess/wrongMappings/one/One.mapping b/src/test/resources/packageAccess/wrongMappings/one/One.mapping deleted file mode 100644 index 15b42cf5..00000000 --- a/src/test/resources/packageAccess/wrongMappings/one/One.mapping +++ /dev/null @@ -1 +0,0 @@ -CLASS b one/One diff --git a/src/test/resources/packageAccess/wrongMappings/two/Two.mapping b/src/test/resources/packageAccess/wrongMappings/two/Two.mapping deleted file mode 100644 index a179349c..00000000 --- a/src/test/resources/packageAccess/wrongMappings/two/Two.mapping +++ /dev/null @@ -1 +0,0 @@ -CLASS c two/Two diff --git a/src/test/resources/proguard-build.conf b/src/test/resources/proguard-build.conf deleted file mode 100644 index ba3575ae..00000000 --- a/src/test/resources/proguard-build.conf +++ /dev/null @@ -1,6 +0,0 @@ --dontoptimize --dontobfuscate --dontwarn --keep class cuchaz.enigma.Main { static void main(java.lang.String[]); } --keep class cuchaz.enigma.CommandMain { static void main(java.lang.String[]); } --keep class de.sciss.syntaxpane.** { *; } diff --git a/src/test/resources/proguard-test.conf b/src/test/resources/proguard-test.conf deleted file mode 100644 index 9411d269..00000000 --- a/src/test/resources/proguard-test.conf +++ /dev/null @@ -1,8 +0,0 @@ --overloadaggressively --repackageclasses --allowaccessmodification --dontoptimize --dontshrink --keepparameternames --keepattributes --keep class cuchaz.enigma.inputs.Keep diff --git a/src/test/resources/tinyV2InnerClasses/c.mapping b/src/test/resources/tinyV2InnerClasses/c.mapping deleted file mode 100644 index f9b04428..00000000 --- a/src/test/resources/tinyV2InnerClasses/c.mapping +++ /dev/null @@ -1,2 +0,0 @@ -CLASS c - CLASS a Kid diff --git a/src/test/resources/tinyV2InnerClasses/cuchaz/enigma/Dad.mapping b/src/test/resources/tinyV2InnerClasses/cuchaz/enigma/Dad.mapping deleted file mode 100644 index 8d43ba90..00000000 --- a/src/test/resources/tinyV2InnerClasses/cuchaz/enigma/Dad.mapping +++ /dev/null @@ -1,5 +0,0 @@ -CLASS f cuchaz/enigma/Dad - CLASS a One - CLASS a Two - CLASS a - FIELD a value I -- cgit v1.2.3