summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cuchaz/enigma/CommandMain.java186
-rw-r--r--src/cuchaz/enigma/ConvertMain.java379
-rw-r--r--src/cuchaz/enigma/Deobfuscator.java551
-rw-r--r--src/cuchaz/enigma/ExceptionIgnorer.java34
-rw-r--r--src/cuchaz/enigma/Main.java51
-rw-r--r--src/cuchaz/enigma/MainFormatConverter.java130
-rw-r--r--src/cuchaz/enigma/TranslatingTypeLoader.java249
-rw-r--r--src/cuchaz/enigma/Util.java104
-rw-r--r--src/cuchaz/enigma/analysis/Access.java43
-rw-r--r--src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java93
-rw-r--r--src/cuchaz/enigma/analysis/BridgeMarker.java43
-rw-r--r--src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java80
-rw-r--r--src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java85
-rw-r--r--src/cuchaz/enigma/analysis/EntryReference.java126
-rw-r--r--src/cuchaz/enigma/analysis/EntryRenamer.java192
-rw-r--r--src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java81
-rw-r--r--src/cuchaz/enigma/analysis/JarClassIterator.java137
-rw-r--r--src/cuchaz/enigma/analysis/JarIndex.java839
-rw-r--r--src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java101
-rw-r--r--src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java114
-rw-r--r--src/cuchaz/enigma/analysis/RelatedMethodChecker.java106
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndex.java184
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java150
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java112
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndexVisitor.java452
-rw-r--r--src/cuchaz/enigma/analysis/Token.java56
-rw-r--r--src/cuchaz/enigma/analysis/TranslationIndex.java298
-rw-r--r--src/cuchaz/enigma/analysis/TreeDumpVisitor.java512
-rw-r--r--src/cuchaz/enigma/bytecode/CheckCastIterator.java127
-rw-r--r--src/cuchaz/enigma/bytecode/ClassProtectifier.java51
-rw-r--r--src/cuchaz/enigma/bytecode/ClassPublifier.java51
-rw-r--r--src/cuchaz/enigma/bytecode/ClassRenamer.java544
-rw-r--r--src/cuchaz/enigma/bytecode/ClassTranslator.java157
-rw-r--r--src/cuchaz/enigma/bytecode/ConstPoolEditor.java263
-rw-r--r--src/cuchaz/enigma/bytecode/InfoType.java317
-rw-r--r--src/cuchaz/enigma/bytecode/InnerClassWriter.java132
-rw-r--r--src/cuchaz/enigma/bytecode/LocalVariableRenamer.java123
-rw-r--r--src/cuchaz/enigma/bytecode/MethodParameterWriter.java70
-rw-r--r--src/cuchaz/enigma/bytecode/MethodParametersAttribute.java86
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java55
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java156
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java74
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java74
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java74
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java55
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java74
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java55
-rw-r--r--src/cuchaz/enigma/convert/ClassForest.java60
-rw-r--r--src/cuchaz/enigma/convert/ClassIdentifier.java54
-rw-r--r--src/cuchaz/enigma/convert/ClassIdentity.java473
-rw-r--r--src/cuchaz/enigma/convert/ClassMatch.java88
-rw-r--r--src/cuchaz/enigma/convert/ClassMatches.java163
-rw-r--r--src/cuchaz/enigma/convert/ClassMatching.java155
-rw-r--r--src/cuchaz/enigma/convert/ClassNamer.java66
-rw-r--r--src/cuchaz/enigma/convert/FieldMatches.java155
-rw-r--r--src/cuchaz/enigma/convert/MappingsConverter.java602
-rw-r--r--src/cuchaz/enigma/convert/MatchesReader.java113
-rw-r--r--src/cuchaz/enigma/convert/MatchesWriter.java121
-rw-r--r--src/cuchaz/enigma/convert/MemberMatches.java159
-rw-r--r--src/cuchaz/enigma/gui/AboutDialog.java86
-rw-r--r--src/cuchaz/enigma/gui/BoxHighlightPainter.java64
-rw-r--r--src/cuchaz/enigma/gui/BrowserCaret.java45
-rw-r--r--src/cuchaz/enigma/gui/ClassMatchingGui.java622
-rw-r--r--src/cuchaz/enigma/gui/ClassSelector.java293
-rw-r--r--src/cuchaz/enigma/gui/ClassSelectorClassNode.java50
-rw-r--r--src/cuchaz/enigma/gui/ClassSelectorPackageNode.java45
-rw-r--r--src/cuchaz/enigma/gui/CodeReader.java222
-rw-r--r--src/cuchaz/enigma/gui/CrashDialog.java101
-rw-r--r--src/cuchaz/enigma/gui/Gui.java1122
-rw-r--r--src/cuchaz/enigma/gui/GuiController.java358
-rw-r--r--src/cuchaz/enigma/gui/GuiTricks.java56
-rw-r--r--src/cuchaz/enigma/gui/MemberMatchingGui.java499
-rw-r--r--src/cuchaz/enigma/gui/ProgressDialog.java105
-rw-r--r--src/cuchaz/enigma/mapping/ArgumentEntry.java116
-rw-r--r--src/cuchaz/enigma/mapping/ArgumentMapping.java49
-rw-r--r--src/cuchaz/enigma/mapping/ClassEntry.java172
-rw-r--r--src/cuchaz/enigma/mapping/ClassMapping.java460
-rw-r--r--src/cuchaz/enigma/mapping/ConstructorEntry.java116
-rw-r--r--src/cuchaz/enigma/mapping/EntryFactory.java166
-rw-r--r--src/cuchaz/enigma/mapping/FieldEntry.java99
-rw-r--r--src/cuchaz/enigma/mapping/FieldMapping.java89
-rw-r--r--src/cuchaz/enigma/mapping/IllegalNameException.java44
-rw-r--r--src/cuchaz/enigma/mapping/Mappings.java216
-rw-r--r--src/cuchaz/enigma/mapping/MappingsChecker.java107
-rw-r--r--src/cuchaz/enigma/mapping/MappingsReader.java134
-rw-r--r--src/cuchaz/enigma/mapping/MappingsRenamer.java237
-rw-r--r--src/cuchaz/enigma/mapping/MappingsWriter.java88
-rw-r--r--src/cuchaz/enigma/mapping/MethodEntry.java104
-rw-r--r--src/cuchaz/enigma/mapping/MethodMapping.java191
-rw-r--r--src/cuchaz/enigma/mapping/NameValidator.java80
-rw-r--r--src/cuchaz/enigma/mapping/ProcyonEntryFactory.java55
-rw-r--r--src/cuchaz/enigma/mapping/Signature.java117
-rw-r--r--src/cuchaz/enigma/mapping/SignatureUpdater.java94
-rw-r--r--src/cuchaz/enigma/mapping/Translator.java289
-rw-r--r--src/cuchaz/enigma/mapping/Type.java247
-rw-r--r--src/main/java/cuchaz/enigma/CommandMain.java186
-rw-r--r--src/main/java/cuchaz/enigma/Constants.java (renamed from src/cuchaz/enigma/Constants.java)16
-rw-r--r--src/main/java/cuchaz/enigma/ConvertMain.java362
-rw-r--r--src/main/java/cuchaz/enigma/Deobfuscator.java530
-rw-r--r--src/main/java/cuchaz/enigma/ExceptionIgnorer.java34
-rw-r--r--src/main/java/cuchaz/enigma/Main.java51
-rw-r--r--src/main/java/cuchaz/enigma/MainFormatConverter.java121
-rw-r--r--src/main/java/cuchaz/enigma/TranslatingTypeLoader.java241
-rw-r--r--src/main/java/cuchaz/enigma/Util.java99
-rw-r--r--src/main/java/cuchaz/enigma/analysis/Access.java43
-rw-r--r--src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java93
-rw-r--r--src/main/java/cuchaz/enigma/analysis/BridgeMarker.java43
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java78
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java83
-rw-r--r--src/main/java/cuchaz/enigma/analysis/EntryReference.java126
-rw-r--r--src/main/java/cuchaz/enigma/analysis/EntryRenamer.java184
-rw-r--r--src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java81
-rw-r--r--src/main/java/cuchaz/enigma/analysis/JarClassIterator.java136
-rw-r--r--src/main/java/cuchaz/enigma/analysis/JarIndex.java802
-rw-r--r--src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java101
-rw-r--r--src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java114
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java (renamed from src/cuchaz/enigma/analysis/ReferenceTreeNode.java)11
-rw-r--r--src/main/java/cuchaz/enigma/analysis/RelatedMethodChecker.java104
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndex.java185
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java131
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java100
-rw-r--r--src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java381
-rw-r--r--src/main/java/cuchaz/enigma/analysis/Token.java56
-rw-r--r--src/main/java/cuchaz/enigma/analysis/TranslationIndex.java282
-rw-r--r--src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java441
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java117
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java51
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java51
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java514
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java151
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java263
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/InfoType.java301
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java132
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java119
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java66
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java86
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java55
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java151
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java74
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java74
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java74
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java55
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java74
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java55
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java28
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassForest.java60
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassIdentifier.java54
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassIdentity.java444
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassMatch.java88
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassMatches.java159
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassMatching.java155
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassNamer.java56
-rw-r--r--src/main/java/cuchaz/enigma/convert/FieldMatches.java151
-rw-r--r--src/main/java/cuchaz/enigma/convert/MappingsConverter.java582
-rw-r--r--src/main/java/cuchaz/enigma/convert/MatchesReader.java109
-rw-r--r--src/main/java/cuchaz/enigma/convert/MatchesWriter.java121
-rw-r--r--src/main/java/cuchaz/enigma/convert/MemberMatches.java155
-rw-r--r--src/main/java/cuchaz/enigma/gui/AboutDialog.java70
-rw-r--r--src/main/java/cuchaz/enigma/gui/BoxHighlightPainter.java64
-rw-r--r--src/main/java/cuchaz/enigma/gui/BrowserCaret.java38
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassListCellRenderer.java (renamed from src/cuchaz/enigma/gui/ClassListCellRenderer.java)34
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java538
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassSelector.java279
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassSelectorClassNode.java50
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassSelectorPackageNode.java45
-rw-r--r--src/main/java/cuchaz/enigma/gui/CodeReader.java222
-rw-r--r--src/main/java/cuchaz/enigma/gui/CrashDialog.java94
-rw-r--r--src/main/java/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java (renamed from src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java)14
-rw-r--r--src/main/java/cuchaz/enigma/gui/Gui.java1100
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiController.java349
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiTricks.java56
-rw-r--r--src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java488
-rw-r--r--src/main/java/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java (renamed from src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java)14
-rw-r--r--src/main/java/cuchaz/enigma/gui/OtherHighlightPainter.java (renamed from src/cuchaz/enigma/gui/OtherHighlightPainter.java)14
-rw-r--r--src/main/java/cuchaz/enigma/gui/ProgressDialog.java100
-rw-r--r--src/main/java/cuchaz/enigma/gui/ReadableToken.java36
-rw-r--r--src/main/java/cuchaz/enigma/gui/RenameListener.java (renamed from src/cuchaz/enigma/gui/RenameListener.java)6
-rw-r--r--src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java (renamed from src/cuchaz/enigma/gui/ScoredClassEntry.java)28
-rw-r--r--src/main/java/cuchaz/enigma/gui/SelectionHighlightPainter.java (renamed from src/cuchaz/enigma/gui/SelectionHighlightPainter.java)31
-rw-r--r--src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java (renamed from src/cuchaz/enigma/gui/TokenListCellRenderer.java)34
-rw-r--r--src/main/java/cuchaz/enigma/json/JsonArgument.java20
-rw-r--r--src/main/java/cuchaz/enigma/json/JsonClass.java58
-rw-r--r--src/main/java/cuchaz/enigma/json/JsonConstructor.java15
-rw-r--r--src/main/java/cuchaz/enigma/json/JsonField.java25
-rw-r--r--src/main/java/cuchaz/enigma/json/JsonMethod.java33
-rw-r--r--src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java116
-rw-r--r--src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java49
-rw-r--r--src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java (renamed from src/cuchaz/enigma/mapping/BehaviorEntry.java)6
-rw-r--r--src/main/java/cuchaz/enigma/mapping/ClassEntry.java172
-rw-r--r--src/main/java/cuchaz/enigma/mapping/ClassMapping.java460
-rw-r--r--src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java (renamed from src/cuchaz/enigma/mapping/ClassNameReplacer.java)6
-rw-r--r--src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java116
-rw-r--r--src/main/java/cuchaz/enigma/mapping/Entry.java21
-rw-r--r--src/main/java/cuchaz/enigma/mapping/EntryFactory.java162
-rw-r--r--src/main/java/cuchaz/enigma/mapping/EntryPair.java22
-rw-r--r--src/main/java/cuchaz/enigma/mapping/FieldEntry.java99
-rw-r--r--src/main/java/cuchaz/enigma/mapping/FieldMapping.java89
-rw-r--r--src/main/java/cuchaz/enigma/mapping/IllegalNameException.java44
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingParseException.java (renamed from src/cuchaz/enigma/mapping/MappingParseException.java)34
-rw-r--r--src/main/java/cuchaz/enigma/mapping/Mappings.java201
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingsChecker.java107
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingsReader.java92
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingsReaderOld.java122
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java237
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MappingsWriter.java82
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MemberMapping.java (renamed from src/cuchaz/enigma/mapping/MemberMapping.java)9
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MethodEntry.java104
-rw-r--r--src/main/java/cuchaz/enigma/mapping/MethodMapping.java191
-rw-r--r--src/main/java/cuchaz/enigma/mapping/NameValidator.java80
-rw-r--r--src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java55
-rw-r--r--src/main/java/cuchaz/enigma/mapping/Signature.java117
-rw-r--r--src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java94
-rw-r--r--src/main/java/cuchaz/enigma/mapping/TranslationDirection.java29
-rw-r--r--src/main/java/cuchaz/enigma/mapping/Translator.java289
-rw-r--r--src/main/java/cuchaz/enigma/mapping/Type.java249
-rw-r--r--src/test/java/cuchaz/enigma/TestDeobfed.java95
-rw-r--r--src/test/java/cuchaz/enigma/TestDeobfuscator.java57
-rw-r--r--src/test/java/cuchaz/enigma/TestEntryFactory.java67
-rw-r--r--src/test/java/cuchaz/enigma/TestInnerClasses.java132
-rw-r--r--src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java124
-rw-r--r--src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java239
-rw-r--r--src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java164
-rw-r--r--src/test/java/cuchaz/enigma/TestSignature.java268
-rw-r--r--src/test/java/cuchaz/enigma/TestSourceIndex.java67
-rw-r--r--src/test/java/cuchaz/enigma/TestTokensConstructors.java136
-rw-r--r--src/test/java/cuchaz/enigma/TestTranslator.java171
-rw-r--r--src/test/java/cuchaz/enigma/TestType.java243
-rw-r--r--src/test/java/cuchaz/enigma/TokenChecker.java65
-rw-r--r--src/test/java/cuchaz/enigma/inputs/Keep.java17
-rw-r--r--src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java25
-rw-r--r--src/test/java/cuchaz/enigma/inputs/constructors/Caller.java57
-rw-r--r--src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java15
-rw-r--r--src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java38
-rw-r--r--src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java21
-rw-r--r--src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java31
-rw-r--r--src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java21
-rw-r--r--src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java40
-rw-r--r--src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java (renamed from src/cuchaz/enigma/gui/ReadableToken.java)34
-rw-r--r--src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java24
-rw-r--r--src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java23
-rw-r--r--src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java30
-rw-r--r--src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java18
-rw-r--r--src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java31
-rw-r--r--src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java30
-rw-r--r--src/test/java/cuchaz/enigma/inputs/loneClass/LoneClass.java (renamed from src/cuchaz/enigma/mapping/EntryPair.java)16
-rw-r--r--src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java32
-rw-r--r--src/test/java/cuchaz/enigma/inputs/translation/B_BaseClass.java (renamed from src/cuchaz/enigma/mapping/Entry.java)19
-rw-r--r--src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java27
-rw-r--r--src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java28
-rw-r--r--src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java32
-rw-r--r--src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java29
-rw-r--r--src/test/java/cuchaz/enigma/inputs/translation/G_OuterClass.java (renamed from src/cuchaz/enigma/mapping/TranslationDirection.java)35
-rw-r--r--src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java38
-rw-r--r--src/test/java/cuchaz/enigma/inputs/translation/I_Generics.java (renamed from src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java)29
-rw-r--r--src/test/java/cuchaz/enigma/resources/translation.mappings41
255 files changed, 19572 insertions, 17359 deletions
diff --git a/src/cuchaz/enigma/CommandMain.java b/src/cuchaz/enigma/CommandMain.java
deleted file mode 100644
index 540cfb95..00000000
--- a/src/cuchaz/enigma/CommandMain.java
+++ /dev/null
@@ -1,186 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.io.FileReader;
15import java.util.jar.JarFile;
16
17import cuchaz.enigma.Deobfuscator.ProgressListener;
18import cuchaz.enigma.mapping.Mappings;
19import cuchaz.enigma.mapping.MappingsReader;
20
21public class CommandMain {
22
23 public static class ConsoleProgressListener implements ProgressListener {
24
25 private static final int ReportTime = 5000; // 5s
26
27 private int m_totalWork;
28 private long m_startTime;
29 private long m_lastReportTime;
30
31 @Override
32 public void init(int totalWork, String title) {
33 m_totalWork = totalWork;
34 m_startTime = System.currentTimeMillis();
35 m_lastReportTime = m_startTime;
36 System.out.println(title);
37 }
38
39 @Override
40 public void onProgress(int numDone, String message) {
41
42 long now = System.currentTimeMillis();
43 boolean isLastUpdate = numDone == m_totalWork;
44 boolean shouldReport = isLastUpdate || now - m_lastReportTime > ReportTime;
45
46 if (shouldReport) {
47 int percent = numDone*100/m_totalWork;
48 System.out.println(String.format("\tProgress: %3d%%", percent));
49 m_lastReportTime = now;
50 }
51 if (isLastUpdate) {
52 double elapsedSeconds = (now - m_startTime)/1000;
53 System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds));
54 }
55 }
56 }
57
58 public static void main(String[] args)
59 throws Exception {
60
61 try {
62
63 // process the command
64 String command = getArg(args, 0, "command", true);
65 if (command.equalsIgnoreCase("deobfuscate")) {
66 deobfuscate(args);
67 } else if (command.equalsIgnoreCase("decompile")) {
68 decompile(args);
69 } else if (command.equalsIgnoreCase("protectify")) {
70 protectify(args);
71 } else if (command.equalsIgnoreCase("publify")) {
72 publify(args);
73 } else {
74 throw new IllegalArgumentException("Command not recognized: " + command);
75 }
76 } catch (IllegalArgumentException ex) {
77 System.out.println(ex.getMessage());
78 printHelp();
79 }
80 }
81
82 private static void printHelp() {
83 System.out.println(String.format("%s - %s", Constants.Name, Constants.Version));
84 System.out.println("Usage:");
85 System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain <command>");
86 System.out.println("\twhere <command> is one of:");
87 System.out.println("\t\tdeobfuscate <in jar> <out jar> [<mappings file>]");
88 System.out.println("\t\tdecompile <in jar> <out folder> [<mappings file>]");
89 System.out.println("\t\tprotectify <in jar> <out jar>");
90 }
91
92 private static void decompile(String[] args)
93 throws Exception {
94 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
95 File fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true));
96 File fileMappings = getReadableFile(getArg(args, 3, "mappings file", false));
97 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn));
98 deobfuscator.writeSources(fileJarOut, new ConsoleProgressListener());
99 }
100
101 private static void deobfuscate(String[] args)
102 throws Exception {
103 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
104 File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true));
105 File fileMappings = getReadableFile(getArg(args, 3, "mappings file", false));
106 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn));
107 deobfuscator.writeJar(fileJarOut, new ConsoleProgressListener());
108 }
109
110 private static void protectify(String[] args)
111 throws Exception {
112 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
113 File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true));
114 Deobfuscator deobfuscator = getDeobfuscator(null, new JarFile(fileJarIn));
115 deobfuscator.protectifyJar(fileJarOut, new ConsoleProgressListener());
116 }
117
118 private static void publify(String[] args)
119 throws Exception {
120 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
121 File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true));
122 Deobfuscator deobfuscator = getDeobfuscator(null, new JarFile(fileJarIn));
123 deobfuscator.publifyJar(fileJarOut, new ConsoleProgressListener());
124 }
125
126 private static Deobfuscator getDeobfuscator(File fileMappings, JarFile jar)
127 throws Exception {
128 System.out.println("Reading jar...");
129 Deobfuscator deobfuscator = new Deobfuscator(jar);
130 if (fileMappings != null) {
131 System.out.println("Reading mappings...");
132 Mappings mappings = new MappingsReader().read(new FileReader(fileMappings));
133 deobfuscator.setMappings(mappings);
134 }
135 return deobfuscator;
136 }
137
138 private static String getArg(String[] args, int i, String name, boolean required) {
139 if (i >= args.length) {
140 if (required) {
141 throw new IllegalArgumentException(name + " is required");
142 } else {
143 return null;
144 }
145 }
146 return args[i];
147 }
148
149 private static File getWritableFile(String path) {
150 if (path == null) {
151 return null;
152 }
153 File file = new File(path).getAbsoluteFile();
154 File dir = file.getParentFile();
155 if (dir == null) {
156 throw new IllegalArgumentException("Cannot write to folder: " + dir);
157 }
158 // quick fix to avoid stupid stuff in Gradle code
159 if (!dir.isDirectory()) {
160 dir.mkdirs();
161 }
162 return file;
163 }
164
165 private static File getWritableFolder(String path) {
166 if (path == null) {
167 return null;
168 }
169 File dir = new File(path).getAbsoluteFile();
170 if (!dir.exists()) {
171 throw new IllegalArgumentException("Cannot write to folder: " + dir);
172 }
173 return dir;
174 }
175
176 private static File getReadableFile(String path) {
177 if (path == null) {
178 return null;
179 }
180 File file = new File(path).getAbsoluteFile();
181 if (!file.exists()) {
182 throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath());
183 }
184 return file;
185 }
186}
diff --git a/src/cuchaz/enigma/ConvertMain.java b/src/cuchaz/enigma/ConvertMain.java
deleted file mode 100644
index 48a15881..00000000
--- a/src/cuchaz/enigma/ConvertMain.java
+++ /dev/null
@@ -1,379 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.io.FileReader;
15import java.io.FileWriter;
16import java.io.IOException;
17import java.util.jar.JarFile;
18
19import cuchaz.enigma.convert.ClassMatches;
20import cuchaz.enigma.convert.MappingsConverter;
21import cuchaz.enigma.convert.MatchesReader;
22import cuchaz.enigma.convert.MatchesWriter;
23import cuchaz.enigma.convert.MemberMatches;
24import cuchaz.enigma.gui.ClassMatchingGui;
25import cuchaz.enigma.gui.MemberMatchingGui;
26import cuchaz.enigma.mapping.BehaviorEntry;
27import cuchaz.enigma.mapping.ClassEntry;
28import cuchaz.enigma.mapping.ClassMapping;
29import cuchaz.enigma.mapping.FieldEntry;
30import cuchaz.enigma.mapping.FieldMapping;
31import cuchaz.enigma.mapping.MappingParseException;
32import cuchaz.enigma.mapping.Mappings;
33import cuchaz.enigma.mapping.MappingsChecker;
34import cuchaz.enigma.mapping.MappingsReader;
35import cuchaz.enigma.mapping.MappingsWriter;
36import cuchaz.enigma.mapping.MethodMapping;
37
38
39public class ConvertMain {
40
41 public static void main(String[] args)
42 throws IOException, MappingParseException {
43 try{
44 //Get all are args
45 String JarOld = getArg(args, 1, "Path to Old Jar", true);
46 String JarNew = getArg(args, 2, "Path to New Jar", true);
47 String OldMappings = getArg(args, 3, "Path to old .mappings file", true);
48 String NewMappings = getArg(args,4,"Path to new .mappings file",true);
49 String ClassMatches = getArg(args, 5, "Path to Class .matches file", true);
50 String FieldMatches = getArg(args, 6, "Path to Field .matches file", true);
51 String MethodMatches = getArg(args, 7, "Path to Method .matches file", true);
52 //OldJar
53 JarFile sourceJar = new JarFile(new File(JarOld));
54 //NewJar
55 JarFile destJar = new JarFile(new File(JarNew));
56 //Get the mapping files
57 File inMappingsFile = new File(OldMappings);
58 File outMappingsFile = new File(NewMappings);
59 Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile));
60 //Make the Match Files..
61 File classMatchesFile = new File(ClassMatches);
62 File fieldMatchesFile = new File(FieldMatches);
63 File methodMatchesFile = new File(MethodMatches);
64
65 String command = getArg(args, 0, "command", true);
66
67 if(command.equalsIgnoreCase("computeClassMatches")){
68 computeClassMatches(classMatchesFile, sourceJar, destJar, mappings);
69 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile);
70 }else if(command.equalsIgnoreCase("editClassMatches")){
71 editClasssMatches(classMatchesFile, sourceJar, destJar, mappings);
72 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile);
73 }else if(command.equalsIgnoreCase("computeFieldMatches")){
74 computeFieldMatches(fieldMatchesFile, destJar, outMappingsFile, classMatchesFile);
75 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile);
76 }else if(command.equalsIgnoreCase("editFieldMatches")){
77 editFieldMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, fieldMatchesFile);
78 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile);
79 }else if(command.equalsIgnoreCase("computeMethodMatches")){
80 computeMethodMatches(methodMatchesFile, destJar, outMappingsFile, classMatchesFile);
81 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile);
82 }else if(command.equalsIgnoreCase("editMethodMatches")){
83 editMethodMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, methodMatchesFile);
84 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile);
85 }else if(command.equalsIgnoreCase("convertMappings")){
86 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile);
87 }
88 }catch (IllegalArgumentException ex) {
89 System.out.println(ex.getMessage());
90 printHelp();
91 }
92 }
93
94 private static void printHelp() {
95 System.out.println(String.format("%s - %s", Constants.Name, Constants.Version));
96 System.out.println("Usage:");
97 System.out.println("\tjava -cp enigma.jar cuchaz.enigma.ConvertMain <command> <old-jar> <new-jar> <old-mappings> <new-mappings> <class-matches> <field-matches> <method-matches>");
98 System.out.println("\tWhere <command> is one of:");
99 System.out.println("\t\tcomputeClassMatches");
100 System.out.println("\t\teditClassMatches");
101 System.out.println("\t\tcomputeFieldMatches");
102 System.out.println("\t\teditFieldMatches");
103 System.out.println("\t\teditMethodMatches");
104 System.out.println("\t\tconvertMappings");
105 System.out.println("\tWhere <old-jar> is the already mapped jar.");
106 System.out.println("\tWhere <new-jar> is the unmapped jar.");
107 System.out.println("\tWhere <old-mappings> is the path to the mappings for the old jar.");
108 System.out.println("\tWhere <new-mappings> is the new mappings. (Where you want to save them and there name)");
109 System.out.println("\tWhere <class-matches> is the class matches file.");
110 System.out.println("\tWhere <field-matches> is the field matches file.");
111 System.out.println("\tWhere <method-matches> is the method matches file.");
112 }
113
114 //Copy of getArg from CommandMain.... Should make a utils class.
115 private static String getArg(String[] args, int i, String name, boolean required) {
116 if (i >= args.length) {
117 if (required) {
118 throw new IllegalArgumentException(name + " is required");
119 } else {
120 return null;
121 }
122 }
123 return args[i];
124 }
125
126 private static void computeClassMatches(File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings)
127 throws IOException {
128 ClassMatches classMatches = MappingsConverter.computeClassMatches(sourceJar, destJar, mappings);
129 MatchesWriter.writeClasses(classMatches, classMatchesFile);
130 System.out.println("Wrote:\n\t" + classMatchesFile.getAbsolutePath());
131 }
132
133 private static void editClasssMatches(final File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings)
134 throws IOException {
135 System.out.println("Reading class matches...");
136 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
137 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
138 deobfuscators.source.setMappings(mappings);
139 System.out.println("Starting GUI...");
140 new ClassMatchingGui(classMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new ClassMatchingGui.SaveListener() {
141 @Override
142 public void save(ClassMatches matches) {
143 try {
144 MatchesWriter.writeClasses(matches, classMatchesFile);
145 } catch (IOException ex) {
146 throw new Error(ex);
147 }
148 }
149 });
150 }
151
152 @SuppressWarnings("unused")
153 private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile)
154 throws IOException {
155 System.out.println("Reading class matches...");
156 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
157 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
158 deobfuscators.source.setMappings(mappings);
159
160 Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
161
162 try (FileWriter out = new FileWriter(outMappingsFile)) {
163 new MappingsWriter().write(out, newMappings);
164 }
165 System.out.println("Write converted mappings to: " + outMappingsFile.getAbsolutePath());
166 }
167
168 private static void computeFieldMatches(File memberMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile)
169 throws IOException, MappingParseException {
170
171 System.out.println("Reading class matches...");
172 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
173 System.out.println("Reading mappings...");
174 Mappings destMappings = new MappingsReader().read(new FileReader(destMappingsFile));
175 System.out.println("Indexing dest jar...");
176 Deobfuscator destDeobfuscator = new Deobfuscator(destJar);
177
178 System.out.println("Writing matches...");
179
180 // get the matched and unmatched mappings
181 MemberMatches<FieldEntry> fieldMatches = MappingsConverter.computeMemberMatches(
182 destDeobfuscator,
183 destMappings,
184 classMatches,
185 MappingsConverter.getFieldDoer()
186 );
187
188 MatchesWriter.writeMembers(fieldMatches, memberMatchesFile);
189 System.out.println("Wrote:\n\t" + memberMatchesFile.getAbsolutePath());
190 }
191
192 private static void editFieldMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File fieldMatchesFile)
193 throws IOException, MappingParseException {
194
195 System.out.println("Reading matches...");
196 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
197 MemberMatches<FieldEntry> fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
198
199 // prep deobfuscators
200 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
201 deobfuscators.source.setMappings(sourceMappings);
202 Mappings destMappings = new MappingsReader().read(new FileReader(destMappingsFile));
203 MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
204 checker.dropBrokenMappings(destMappings);
205 deobfuscators.dest.setMappings(destMappings);
206
207 new MemberMatchingGui<FieldEntry>(classMatches, fieldMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new MemberMatchingGui.SaveListener<FieldEntry>() {
208 @Override
209 public void save(MemberMatches<FieldEntry> matches) {
210 try {
211 MatchesWriter.writeMembers(matches, fieldMatchesFile);
212 } catch (IOException ex) {
213 throw new Error(ex);
214 }
215 }
216 });
217 }
218
219 @SuppressWarnings("unused")
220 private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile)
221 throws IOException {
222
223 System.out.println("Reading matches...");
224 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
225 MemberMatches<FieldEntry> fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
226
227 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
228 deobfuscators.source.setMappings(mappings);
229
230 // apply matches
231 Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
232 MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer());
233
234 // write out the converted mappings
235 try (FileWriter out = new FileWriter(outMappingsFile)) {
236 new MappingsWriter().write(out, newMappings);
237 }
238 System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
239 }
240
241
242 private static void computeMethodMatches(File methodMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile)
243 throws IOException, MappingParseException {
244
245 System.out.println("Reading class matches...");
246 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
247 System.out.println("Reading mappings...");
248 Mappings destMappings = new MappingsReader().read(new FileReader(destMappingsFile));
249 System.out.println("Indexing dest jar...");
250 Deobfuscator destDeobfuscator = new Deobfuscator(destJar);
251
252 System.out.println("Writing method matches...");
253
254 // get the matched and unmatched mappings
255 MemberMatches<BehaviorEntry> methodMatches = MappingsConverter.computeMemberMatches(
256 destDeobfuscator,
257 destMappings,
258 classMatches,
259 MappingsConverter.getMethodDoer()
260 );
261
262 MatchesWriter.writeMembers(methodMatches, methodMatchesFile);
263 System.out.println("Wrote:\n\t" + methodMatchesFile.getAbsolutePath());
264 }
265
266 private static void editMethodMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File methodMatchesFile)
267 throws IOException, MappingParseException {
268
269 System.out.println("Reading matches...");
270 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
271 MemberMatches<BehaviorEntry> methodMatches = MatchesReader.readMembers(methodMatchesFile);
272
273 // prep deobfuscators
274 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
275 deobfuscators.source.setMappings(sourceMappings);
276 Mappings destMappings = new MappingsReader().read(new FileReader(destMappingsFile));
277 MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
278 checker.dropBrokenMappings(destMappings);
279 deobfuscators.dest.setMappings(destMappings);
280
281 new MemberMatchingGui<BehaviorEntry>(classMatches, methodMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new MemberMatchingGui.SaveListener<BehaviorEntry>() {
282 @Override
283 public void save(MemberMatches<BehaviorEntry> matches) {
284 try {
285 MatchesWriter.writeMembers(matches, methodMatchesFile);
286 } catch (IOException ex) {
287 throw new Error(ex);
288 }
289 }
290 });
291 }
292
293 private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile, File methodMatchesFile)
294 throws IOException {
295
296 System.out.println("Reading matches...");
297 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
298 MemberMatches<FieldEntry> fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
299 MemberMatches<BehaviorEntry> methodMatches = MatchesReader.readMembers(methodMatchesFile);
300
301 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
302 deobfuscators.source.setMappings(mappings);
303
304 // apply matches
305 Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
306 MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer());
307 MappingsConverter.applyMemberMatches(newMappings, classMatches, methodMatches, MappingsConverter.getMethodDoer());
308
309 // check the final mappings
310 MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
311 checker.dropBrokenMappings(newMappings);
312
313 for (java.util.Map.Entry<ClassEntry,ClassMapping> mapping : checker.getDroppedClassMappings().entrySet()) {
314 System.out.println("WARNING: Broken class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
315 }
316 for (java.util.Map.Entry<ClassEntry,ClassMapping> mapping : checker.getDroppedInnerClassMappings().entrySet()) {
317 System.out.println("WARNING: Broken inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
318 }
319 for (java.util.Map.Entry<FieldEntry,FieldMapping> mapping : checker.getDroppedFieldMappings().entrySet()) {
320 System.out.println("WARNING: Broken field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
321 }
322 for (java.util.Map.Entry<BehaviorEntry,MethodMapping> mapping : checker.getDroppedMethodMappings().entrySet()) {
323 System.out.println("WARNING: Broken behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
324 }
325
326 // write out the converted mappings
327 try (FileWriter out = new FileWriter(outMappingsFile)) {
328 new MappingsWriter().write(out, newMappings);
329 }
330 System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
331 }
332
333 private static class Deobfuscators {
334
335 public Deobfuscator source;
336 public Deobfuscator dest;
337
338 public Deobfuscators(JarFile sourceJar, JarFile destJar) {
339 System.out.println("Indexing source jar...");
340 IndexerThread sourceIndexer = new IndexerThread(sourceJar);
341 sourceIndexer.start();
342 System.out.println("Indexing dest jar...");
343 IndexerThread destIndexer = new IndexerThread(destJar);
344 destIndexer.start();
345 sourceIndexer.joinOrBail();
346 destIndexer.joinOrBail();
347 source = sourceIndexer.deobfuscator;
348 dest = destIndexer.deobfuscator;
349 }
350 }
351
352 private static class IndexerThread extends Thread {
353
354 private JarFile m_jarFile;
355 public Deobfuscator deobfuscator;
356
357 public IndexerThread(JarFile jarFile) {
358 m_jarFile = jarFile;
359 deobfuscator = null;
360 }
361
362 public void joinOrBail() {
363 try {
364 join();
365 } catch (InterruptedException ex) {
366 throw new Error(ex);
367 }
368 }
369
370 @Override
371 public void run() {
372 try {
373 deobfuscator = new Deobfuscator(m_jarFile);
374 } catch (IOException ex) {
375 throw new Error(ex);
376 }
377 }
378 }
379} \ No newline at end of file
diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java
deleted file mode 100644
index 82d1611b..00000000
--- a/src/cuchaz/enigma/Deobfuscator.java
+++ /dev/null
@@ -1,551 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.io.FileOutputStream;
15import java.io.FileWriter;
16import java.io.IOException;
17import java.io.StringWriter;
18import java.util.List;
19import java.util.Map;
20import java.util.Set;
21import java.util.jar.JarEntry;
22import java.util.jar.JarFile;
23import java.util.jar.JarOutputStream;
24
25import javassist.CtClass;
26import javassist.bytecode.Descriptor;
27
28import com.google.common.collect.Maps;
29import com.google.common.collect.Sets;
30import com.strobel.assembler.metadata.MetadataSystem;
31import com.strobel.assembler.metadata.TypeDefinition;
32import com.strobel.assembler.metadata.TypeReference;
33import com.strobel.decompiler.DecompilerContext;
34import com.strobel.decompiler.DecompilerSettings;
35import com.strobel.decompiler.PlainTextOutput;
36import com.strobel.decompiler.languages.java.JavaOutputVisitor;
37import com.strobel.decompiler.languages.java.ast.AstBuilder;
38import com.strobel.decompiler.languages.java.ast.CompilationUnit;
39import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor;
40
41import cuchaz.enigma.analysis.EntryReference;
42import cuchaz.enigma.analysis.JarClassIterator;
43import cuchaz.enigma.analysis.JarIndex;
44import cuchaz.enigma.analysis.SourceIndex;
45import cuchaz.enigma.analysis.SourceIndexVisitor;
46import cuchaz.enigma.analysis.Token;
47import cuchaz.enigma.bytecode.ClassProtectifier;
48import cuchaz.enigma.bytecode.ClassPublifier;
49import cuchaz.enigma.mapping.ArgumentEntry;
50import cuchaz.enigma.mapping.BehaviorEntry;
51import cuchaz.enigma.mapping.ClassEntry;
52import cuchaz.enigma.mapping.ClassMapping;
53import cuchaz.enigma.mapping.ConstructorEntry;
54import cuchaz.enigma.mapping.Entry;
55import cuchaz.enigma.mapping.FieldEntry;
56import cuchaz.enigma.mapping.FieldMapping;
57import cuchaz.enigma.mapping.Mappings;
58import cuchaz.enigma.mapping.MappingsChecker;
59import cuchaz.enigma.mapping.MappingsRenamer;
60import cuchaz.enigma.mapping.MethodEntry;
61import cuchaz.enigma.mapping.MethodMapping;
62import cuchaz.enigma.mapping.TranslationDirection;
63import cuchaz.enigma.mapping.Translator;
64
65public class Deobfuscator {
66
67 public interface ProgressListener {
68 void init(int totalWork, String title);
69 void onProgress(int numDone, String message);
70 }
71
72 private JarFile m_jar;
73 private DecompilerSettings m_settings;
74 private JarIndex m_jarIndex;
75 private Mappings m_mappings;
76 private MappingsRenamer m_renamer;
77 private Map<TranslationDirection,Translator> m_translatorCache;
78
79 public Deobfuscator(JarFile jar) throws IOException {
80 m_jar = jar;
81
82 // build the jar index
83 m_jarIndex = new JarIndex();
84 m_jarIndex.indexJar(m_jar, true);
85
86 // config the decompiler
87 m_settings = DecompilerSettings.javaDefaults();
88 m_settings.setMergeVariables(true);
89 m_settings.setForceExplicitImports(true);
90 m_settings.setForceExplicitTypeArguments(true);
91 m_settings.setShowDebugLineNumbers(true);
92 // DEBUG
93 //m_settings.setShowSyntheticMembers(true);
94
95 // init defaults
96 m_translatorCache = Maps.newTreeMap();
97
98 // init mappings
99 setMappings(new Mappings());
100 }
101
102 public JarFile getJar() {
103 return m_jar;
104 }
105
106 public String getJarName() {
107 return m_jar.getName();
108 }
109
110 public JarIndex getJarIndex() {
111 return m_jarIndex;
112 }
113
114 public Mappings getMappings() {
115 return m_mappings;
116 }
117
118 public void setMappings(Mappings val) {
119 setMappings(val, true);
120 }
121
122 public void setMappings(Mappings val, boolean warnAboutDrops) {
123 if (val == null) {
124 val = new Mappings();
125 }
126
127 // drop mappings that don't match the jar
128 MappingsChecker checker = new MappingsChecker(m_jarIndex);
129 checker.dropBrokenMappings(val);
130 if (warnAboutDrops) {
131 for (java.util.Map.Entry<ClassEntry,ClassMapping> mapping : checker.getDroppedClassMappings().entrySet()) {
132 System.out.println("WARNING: Couldn't find class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
133 }
134 for (java.util.Map.Entry<ClassEntry,ClassMapping> mapping : checker.getDroppedInnerClassMappings().entrySet()) {
135 System.out.println("WARNING: Couldn't find inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
136 }
137 for (java.util.Map.Entry<FieldEntry,FieldMapping> mapping : checker.getDroppedFieldMappings().entrySet()) {
138 System.out.println("WARNING: Couldn't find field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
139 }
140 for (java.util.Map.Entry<BehaviorEntry,MethodMapping> mapping : checker.getDroppedMethodMappings().entrySet()) {
141 System.out.println("WARNING: Couldn't find behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
142 }
143 }
144
145 // check for related method inconsistencies
146 if (checker.getRelatedMethodChecker().hasProblems()) {
147 throw new Error("Related methods are inconsistent! Need to fix the mappings manually.\n" + checker.getRelatedMethodChecker().getReport());
148 }
149
150 m_mappings = val;
151 m_renamer = new MappingsRenamer(m_jarIndex, val);
152 m_translatorCache.clear();
153 }
154
155 public Translator getTranslator(TranslationDirection direction) {
156 Translator translator = m_translatorCache.get(direction);
157 if (translator == null) {
158 translator = m_mappings.getTranslator(direction, m_jarIndex.getTranslationIndex());
159 m_translatorCache.put(direction, translator);
160 }
161 return translator;
162 }
163
164 public void getSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) {
165 for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) {
166 // skip inner classes
167 if (obfClassEntry.isInnerClass()) {
168 continue;
169 }
170
171 // separate the classes
172 ClassEntry deobfClassEntry = deobfuscateEntry(obfClassEntry);
173 if (!deobfClassEntry.equals(obfClassEntry)) {
174 // if the class has a mapping, clearly it's deobfuscated
175 deobfClasses.add(deobfClassEntry);
176 } else if (!obfClassEntry.getPackageName().equals(Constants.NonePackage)) {
177 // also call it deobufscated if it's not in the none package
178 deobfClasses.add(obfClassEntry);
179 } else {
180 // otherwise, assume it's still obfuscated
181 obfClasses.add(obfClassEntry);
182 }
183 }
184 }
185
186 public CompilationUnit getSourceTree(String className) {
187
188 // we don't know if this class name is obfuscated or deobfuscated
189 // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out
190 // the decompiler only sees classes after deobfuscation, so we need to load it by the deobfuscated name if there is one
191
192 // first, assume class name is deobf
193 String deobfClassName = className;
194
195 // if it wasn't actually deobf, then we can find a mapping for it and get the deobf name
196 ClassMapping classMapping = m_mappings.getClassByObf(className);
197 if (classMapping != null && classMapping.getDeobfName() != null) {
198 deobfClassName = classMapping.getDeobfName();
199 }
200
201 // set the type loader
202 TranslatingTypeLoader loader = new TranslatingTypeLoader(
203 m_jar,
204 m_jarIndex,
205 getTranslator(TranslationDirection.Obfuscating),
206 getTranslator(TranslationDirection.Deobfuscating)
207 );
208 m_settings.setTypeLoader(loader);
209
210 // see if procyon can find the type
211 TypeReference type = new MetadataSystem(loader).lookupType(deobfClassName);
212 if (type == null) {
213 throw new Error(String.format("Unable to find type: %s (deobf: %s)\nTried class names: %s",
214 className, deobfClassName, loader.getClassNamesToTry(deobfClassName)
215 ));
216 }
217 TypeDefinition resolvedType = type.resolve();
218
219 // decompile it!
220 DecompilerContext context = new DecompilerContext();
221 context.setCurrentType(resolvedType);
222 context.setSettings(m_settings);
223 AstBuilder builder = new AstBuilder(context);
224 builder.addType(resolvedType);
225 builder.runTransformations(null);
226 return builder.getCompilationUnit();
227 }
228
229 public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source) {
230 return getSourceIndex(sourceTree, source, null);
231 }
232
233 public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source, Boolean ignoreBadTokens) {
234
235 // build the source index
236 SourceIndex index;
237 if (ignoreBadTokens != null) {
238 index = new SourceIndex(source, ignoreBadTokens);
239 } else {
240 index = new SourceIndex(source);
241 }
242 sourceTree.acceptVisitor(new SourceIndexVisitor(), index);
243
244 // DEBUG
245 // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null );
246
247 // resolve all the classes in the source references
248 for (Token token : index.referenceTokens()) {
249 EntryReference<Entry,Entry> deobfReference = index.getDeobfReference(token);
250
251 // get the obfuscated entry
252 Entry obfEntry = obfuscateEntry(deobfReference.entry);
253
254 // try to resolve the class
255 ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(obfEntry);
256 if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getClassEntry())) {
257 // change the class of the entry
258 obfEntry = obfEntry.cloneToNewClass(resolvedObfClassEntry);
259
260 // save the new deobfuscated reference
261 deobfReference.entry = deobfuscateEntry(obfEntry);
262 index.replaceDeobfReference(token, deobfReference);
263 }
264
265 // DEBUG
266 // System.out.println( token + " -> " + reference + " -> " + index.getReferenceToken( reference ) );
267 }
268
269 return index;
270 }
271
272 public String getSource(CompilationUnit sourceTree) {
273 // render the AST into source
274 StringWriter buf = new StringWriter();
275 sourceTree.acceptVisitor(new InsertParenthesesVisitor(), null);
276 sourceTree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(buf), m_settings), null);
277 return buf.toString();
278 }
279
280 public void writeSources(File dirOut, ProgressListener progress) throws IOException {
281 // get the classes to decompile
282 Set<ClassEntry> classEntries = Sets.newHashSet();
283 for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) {
284 // skip inner classes
285 if (obfClassEntry.isInnerClass()) {
286 continue;
287 }
288
289 classEntries.add(obfClassEntry);
290 }
291
292 if (progress != null) {
293 progress.init(classEntries.size(), "Decompiling classes...");
294 }
295
296 // DEOBFUSCATE ALL THE THINGS!! @_@
297 int i = 0;
298 for (ClassEntry obfClassEntry : classEntries) {
299 ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry));
300 if (progress != null) {
301 progress.onProgress(i++, deobfClassEntry.toString());
302 }
303
304 try {
305 // get the source
306 String source = getSource(getSourceTree(obfClassEntry.getName()));
307
308 // write the file
309 File file = new File(dirOut, deobfClassEntry.getName().replace('.', '/') + ".java");
310 file.getParentFile().mkdirs();
311 try (FileWriter out = new FileWriter(file)) {
312 out.write(source);
313 }
314 } catch (Throwable t) {
315 // don't crash the whole world here, just log the error and keep going
316 // TODO: set up logback via log4j
317 System.err.println("Unable to deobfuscate class " + deobfClassEntry.toString() + " (" + obfClassEntry.toString() + ")");
318 t.printStackTrace(System.err);
319 }
320 }
321 if (progress != null) {
322 progress.onProgress(i, "Done!");
323 }
324 }
325
326 public void writeJar(File out, ProgressListener progress) {
327 final TranslatingTypeLoader loader = new TranslatingTypeLoader(
328 m_jar,
329 m_jarIndex,
330 getTranslator(TranslationDirection.Obfuscating),
331 getTranslator(TranslationDirection.Deobfuscating)
332 );
333 transformJar(out, progress, new ClassTransformer() {
334
335 @Override
336 public CtClass transform(CtClass c) throws Exception {
337 return loader.transformClass(c);
338 }
339 });
340 }
341
342 public void protectifyJar(File out, ProgressListener progress) {
343 transformJar(out, progress, new ClassTransformer() {
344
345 @Override
346 public CtClass transform(CtClass c) throws Exception {
347 return ClassProtectifier.protectify(c);
348 }
349 });
350 }
351
352 public void publifyJar(File out, ProgressListener progress) {
353 transformJar(out, progress, new ClassTransformer() {
354
355 @Override
356 public CtClass transform(CtClass c) throws Exception {
357 return ClassPublifier.publify(c);
358 }
359 });
360 }
361
362 private interface ClassTransformer {
363 public CtClass transform(CtClass c) throws Exception;
364 }
365 private void transformJar(File out, ProgressListener progress, ClassTransformer transformer) {
366 try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) {
367 if (progress != null) {
368 progress.init(JarClassIterator.getClassEntries(m_jar).size(), "Transforming classes...");
369 }
370
371 int i = 0;
372 for (CtClass c : JarClassIterator.classes(m_jar)) {
373 if (progress != null) {
374 progress.onProgress(i++, c.getName());
375 }
376
377 try {
378 c = transformer.transform(c);
379 outJar.putNextEntry(new JarEntry(c.getName().replace('.', '/') + ".class"));
380 outJar.write(c.toBytecode());
381 outJar.closeEntry();
382 } catch (Throwable t) {
383 throw new Error("Unable to transform class " + c.getName(), t);
384 }
385 }
386 if (progress != null) {
387 progress.onProgress(i, "Done!");
388 }
389
390 outJar.close();
391 } catch (IOException ex) {
392 throw new Error("Unable to write to Jar file!");
393 }
394 }
395
396 public <T extends Entry> T obfuscateEntry(T deobfEntry) {
397 if (deobfEntry == null) {
398 return null;
399 }
400 return getTranslator(TranslationDirection.Obfuscating).translateEntry(deobfEntry);
401 }
402
403 public <T extends Entry> T deobfuscateEntry(T obfEntry) {
404 if (obfEntry == null) {
405 return null;
406 }
407 return getTranslator(TranslationDirection.Deobfuscating).translateEntry(obfEntry);
408 }
409
410 public <E extends Entry,C extends Entry> EntryReference<E,C> obfuscateReference(EntryReference<E,C> deobfReference) {
411 if (deobfReference == null) {
412 return null;
413 }
414 return new EntryReference<E,C>(
415 obfuscateEntry(deobfReference.entry),
416 obfuscateEntry(deobfReference.context),
417 deobfReference
418 );
419 }
420
421 public <E extends Entry,C extends Entry> EntryReference<E,C> deobfuscateReference(EntryReference<E,C> obfReference) {
422 if (obfReference == null) {
423 return null;
424 }
425 return new EntryReference<E,C>(
426 deobfuscateEntry(obfReference.entry),
427 deobfuscateEntry(obfReference.context),
428 obfReference
429 );
430 }
431
432 public boolean isObfuscatedIdentifier(Entry obfEntry) {
433
434 if (obfEntry instanceof MethodEntry) {
435
436 // HACKHACK: Object methods are not obfuscated identifiers
437 MethodEntry obfMethodEntry = (MethodEntry)obfEntry;
438 String name = obfMethodEntry.getName();
439 String sig = obfMethodEntry.getSignature().toString();
440 if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) {
441 return false;
442 } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) {
443 return false;
444 } else if (name.equals("finalize") && sig.equals("()V")) {
445 return false;
446 } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) {
447 return false;
448 } else if (name.equals("hashCode") && sig.equals("()I")) {
449 return false;
450 } else if (name.equals("notify") && sig.equals("()V")) {
451 return false;
452 } else if (name.equals("notifyAll") && sig.equals("()V")) {
453 return false;
454 } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) {
455 return false;
456 } else if (name.equals("wait") && sig.equals("()V")) {
457 return false;
458 } else if (name.equals("wait") && sig.equals("(J)V")) {
459 return false;
460 } else if (name.equals("wait") && sig.equals("(JI)V")) {
461 return false;
462 }
463 }
464
465 return m_jarIndex.containsObfEntry(obfEntry);
466 }
467
468 public boolean isRenameable(EntryReference<Entry,Entry> obfReference) {
469 return obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry());
470 }
471
472 // NOTE: these methods are a bit messy... oh well
473
474 public boolean hasDeobfuscatedName(Entry obfEntry) {
475 Translator translator = getTranslator(TranslationDirection.Deobfuscating);
476 if (obfEntry instanceof ClassEntry) {
477 ClassEntry obfClass = (ClassEntry)obfEntry;
478 List<ClassMapping> mappingChain = m_mappings.getClassMappingChain(obfClass);
479 ClassMapping classMapping = mappingChain.get(mappingChain.size() - 1);
480 return classMapping != null && classMapping.getDeobfName() != null;
481 } else if (obfEntry instanceof FieldEntry) {
482 return translator.translate((FieldEntry)obfEntry) != null;
483 } else if (obfEntry instanceof MethodEntry) {
484 return translator.translate((MethodEntry)obfEntry) != null;
485 } else if (obfEntry instanceof ConstructorEntry) {
486 // constructors have no names
487 return false;
488 } else if (obfEntry instanceof ArgumentEntry) {
489 return translator.translate((ArgumentEntry)obfEntry) != null;
490 } else {
491 throw new Error("Unknown entry type: " + obfEntry.getClass().getName());
492 }
493 }
494
495 public void rename(Entry obfEntry, String newName) {
496 if (obfEntry instanceof ClassEntry) {
497 m_renamer.setClassName((ClassEntry)obfEntry, Descriptor.toJvmName(newName));
498 } else if (obfEntry instanceof FieldEntry) {
499 m_renamer.setFieldName((FieldEntry)obfEntry, newName);
500 } else if (obfEntry instanceof MethodEntry) {
501 m_renamer.setMethodTreeName((MethodEntry)obfEntry, newName);
502 } else if (obfEntry instanceof ConstructorEntry) {
503 throw new IllegalArgumentException("Cannot rename constructors");
504 } else if (obfEntry instanceof ArgumentEntry) {
505 m_renamer.setArgumentName((ArgumentEntry)obfEntry, newName);
506 } else {
507 throw new Error("Unknown entry type: " + obfEntry.getClass().getName());
508 }
509
510 // clear caches
511 m_translatorCache.clear();
512 }
513
514 public void removeMapping(Entry obfEntry) {
515 if (obfEntry instanceof ClassEntry) {
516 m_renamer.removeClassMapping((ClassEntry)obfEntry);
517 } else if (obfEntry instanceof FieldEntry) {
518 m_renamer.removeFieldMapping((FieldEntry)obfEntry);
519 } else if (obfEntry instanceof MethodEntry) {
520 m_renamer.removeMethodTreeMapping((MethodEntry)obfEntry);
521 } else if (obfEntry instanceof ConstructorEntry) {
522 throw new IllegalArgumentException("Cannot rename constructors");
523 } else if (obfEntry instanceof ArgumentEntry) {
524 m_renamer.removeArgumentMapping((ArgumentEntry)obfEntry);
525 } else {
526 throw new Error("Unknown entry type: " + obfEntry);
527 }
528
529 // clear caches
530 m_translatorCache.clear();
531 }
532
533 public void markAsDeobfuscated(Entry obfEntry) {
534 if (obfEntry instanceof ClassEntry) {
535 m_renamer.markClassAsDeobfuscated((ClassEntry)obfEntry);
536 } else if (obfEntry instanceof FieldEntry) {
537 m_renamer.markFieldAsDeobfuscated((FieldEntry)obfEntry);
538 } else if (obfEntry instanceof MethodEntry) {
539 m_renamer.markMethodTreeAsDeobfuscated((MethodEntry)obfEntry);
540 } else if (obfEntry instanceof ConstructorEntry) {
541 throw new IllegalArgumentException("Cannot rename constructors");
542 } else if (obfEntry instanceof ArgumentEntry) {
543 m_renamer.markArgumentAsDeobfuscated((ArgumentEntry)obfEntry);
544 } else {
545 throw new Error("Unknown entry type: " + obfEntry);
546 }
547
548 // clear caches
549 m_translatorCache.clear();
550 }
551}
diff --git a/src/cuchaz/enigma/ExceptionIgnorer.java b/src/cuchaz/enigma/ExceptionIgnorer.java
deleted file mode 100644
index d8726d13..00000000
--- a/src/cuchaz/enigma/ExceptionIgnorer.java
+++ /dev/null
@@ -1,34 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13public class ExceptionIgnorer {
14
15 public static boolean shouldIgnore(Throwable t) {
16
17 // is this that pesky concurrent access bug in the highlight painter system?
18 // (ancient ui code is ancient)
19 if (t instanceof ArrayIndexOutOfBoundsException) {
20 StackTraceElement[] stackTrace = t.getStackTrace();
21 if (stackTrace.length > 1) {
22
23 // does this stack frame match javax.swing.text.DefaultHighlighter.paint*() ?
24 StackTraceElement frame = stackTrace[1];
25 if (frame.getClassName().equals("javax.swing.text.DefaultHighlighter") && frame.getMethodName().startsWith("paint")) {
26 return true;
27 }
28 }
29 }
30
31 return false;
32 }
33
34}
diff --git a/src/cuchaz/enigma/Main.java b/src/cuchaz/enigma/Main.java
deleted file mode 100644
index 4842a795..00000000
--- a/src/cuchaz/enigma/Main.java
+++ /dev/null
@@ -1,51 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.util.jar.JarFile;
15
16import cuchaz.enigma.gui.Gui;
17
18public class Main {
19
20 public static void main(String[] args) throws Exception {
21 Gui gui = new Gui();
22
23 // parse command-line args
24 if (args.length >= 1) {
25 gui.getController().openJar(new JarFile(getFile(args[0])));
26 }
27 if (args.length >= 2) {
28 gui.getController().openMappings(getFile(args[1]));
29 }
30
31 // DEBUG
32 //gui.getController().openDeclaration(new ClassEntry("none/bxq"));
33 }
34
35 private static File getFile(String path) {
36 // expand ~ to the home dir
37 if (path.startsWith("~")) {
38 // get the home dir
39 File dirHome = new File(System.getProperty("user.home"));
40
41 // is the path just ~/ or is it ~user/ ?
42 if (path.startsWith("~/")) {
43 return new File(dirHome, path.substring(2));
44 } else {
45 return new File(dirHome.getParentFile(), path.substring(1));
46 }
47 }
48
49 return new File(path);
50 }
51}
diff --git a/src/cuchaz/enigma/MainFormatConverter.java b/src/cuchaz/enigma/MainFormatConverter.java
deleted file mode 100644
index 73ee41f4..00000000
--- a/src/cuchaz/enigma/MainFormatConverter.java
+++ /dev/null
@@ -1,130 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.io.FileReader;
15import java.io.FileWriter;
16import java.lang.reflect.Field;
17import java.util.Map;
18import java.util.jar.JarFile;
19
20import javassist.CtClass;
21import javassist.CtField;
22
23import com.google.common.collect.Maps;
24
25import cuchaz.enigma.analysis.JarClassIterator;
26import cuchaz.enigma.mapping.ClassEntry;
27import cuchaz.enigma.mapping.ClassMapping;
28import cuchaz.enigma.mapping.ClassNameReplacer;
29import cuchaz.enigma.mapping.FieldEntry;
30import cuchaz.enigma.mapping.FieldMapping;
31import cuchaz.enigma.mapping.EntryFactory;
32import cuchaz.enigma.mapping.Mappings;
33import cuchaz.enigma.mapping.MappingsReader;
34import cuchaz.enigma.mapping.MappingsWriter;
35import cuchaz.enigma.mapping.Type;
36
37public class MainFormatConverter {
38
39 public static void main(String[] args)
40 throws Exception {
41
42 System.out.println("Getting field types from jar...");
43
44 JarFile jar = new JarFile(System.getProperty("user.home") + "/.minecraft/versions/1.8/1.8.jar");
45 Map<String,Type> fieldTypes = Maps.newHashMap();
46 for (CtClass c : JarClassIterator.classes(jar)) {
47 for (CtField field : c.getDeclaredFields()) {
48 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
49 fieldTypes.put(getFieldKey(fieldEntry), moveClasssesOutOfDefaultPackage(fieldEntry.getType()));
50 }
51 }
52
53 System.out.println("Reading mappings...");
54
55 File fileMappings = new File("../Enigma Mappings/1.8.mappings");
56 MappingsReader mappingsReader = new MappingsReader() {
57
58 @Override
59 protected FieldMapping readField(String[] parts) {
60 // assume the void type for now
61 return new FieldMapping(parts[1], new Type("V"), parts[2]);
62 }
63 };
64 Mappings mappings = mappingsReader.read(new FileReader(fileMappings));
65
66 System.out.println("Updating field types...");
67
68 for (ClassMapping classMapping : mappings.classes()) {
69 updateFieldsInClass(fieldTypes, classMapping);
70 }
71
72 System.out.println("Saving mappings...");
73
74 try (FileWriter writer = new FileWriter(fileMappings)) {
75 new MappingsWriter().write(writer, mappings);
76 }
77
78 System.out.println("Done!");
79 }
80
81 private static Type moveClasssesOutOfDefaultPackage(Type type) {
82 return new Type(type, new ClassNameReplacer() {
83 @Override
84 public String replace(String className) {
85 ClassEntry entry = new ClassEntry(className);
86 if (entry.isInDefaultPackage()) {
87 return Constants.NonePackage + "/" + className;
88 }
89 return null;
90 }
91 });
92 }
93
94 private static void updateFieldsInClass(Map<String,Type> fieldTypes, ClassMapping classMapping)
95 throws Exception {
96
97 // update the fields
98 for (FieldMapping fieldMapping : classMapping.fields()) {
99 setFieldType(fieldTypes, classMapping, fieldMapping);
100 }
101
102 // recurse
103 for (ClassMapping innerClassMapping : classMapping.innerClasses()) {
104 updateFieldsInClass(fieldTypes, innerClassMapping);
105 }
106 }
107
108 private static void setFieldType(Map<String,Type> fieldTypes, ClassMapping classMapping, FieldMapping fieldMapping)
109 throws Exception {
110
111 // get the new type
112 Type newType = fieldTypes.get(getFieldKey(classMapping, fieldMapping));
113 if (newType == null) {
114 throw new Error("Can't find type for field: " + getFieldKey(classMapping, fieldMapping));
115 }
116
117 // hack in the new field type
118 Field field = fieldMapping.getClass().getDeclaredField("m_obfType");
119 field.setAccessible(true);
120 field.set(fieldMapping, newType);
121 }
122
123 private static Object getFieldKey(ClassMapping classMapping, FieldMapping fieldMapping) {
124 return classMapping.getObfSimpleName() + "." + fieldMapping.getObfName();
125 }
126
127 private static String getFieldKey(FieldEntry obfFieldEntry) {
128 return obfFieldEntry.getClassEntry().getSimpleName() + "." + obfFieldEntry.getName();
129 }
130}
diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java
deleted file mode 100644
index a2185e5c..00000000
--- a/src/cuchaz/enigma/TranslatingTypeLoader.java
+++ /dev/null
@@ -1,249 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.ByteArrayOutputStream;
14import java.io.IOException;
15import java.io.InputStream;
16import java.util.List;
17import java.util.Map;
18import java.util.jar.JarEntry;
19import java.util.jar.JarFile;
20
21import javassist.ByteArrayClassPath;
22import javassist.CannotCompileException;
23import javassist.ClassPool;
24import javassist.CtClass;
25import javassist.NotFoundException;
26import javassist.bytecode.Descriptor;
27
28import com.google.common.collect.Lists;
29import com.google.common.collect.Maps;
30import com.strobel.assembler.metadata.Buffer;
31import com.strobel.assembler.metadata.ClasspathTypeLoader;
32import com.strobel.assembler.metadata.ITypeLoader;
33
34import cuchaz.enigma.analysis.BridgeMarker;
35import cuchaz.enigma.analysis.JarIndex;
36import cuchaz.enigma.bytecode.ClassRenamer;
37import cuchaz.enigma.bytecode.ClassTranslator;
38import cuchaz.enigma.bytecode.InnerClassWriter;
39import cuchaz.enigma.bytecode.LocalVariableRenamer;
40import cuchaz.enigma.bytecode.MethodParameterWriter;
41import cuchaz.enigma.mapping.ClassEntry;
42import cuchaz.enigma.mapping.Translator;
43
44public class TranslatingTypeLoader implements ITypeLoader {
45
46 private JarFile m_jar;
47 private JarIndex m_jarIndex;
48 private Translator m_obfuscatingTranslator;
49 private Translator m_deobfuscatingTranslator;
50 private Map<String,byte[]> m_cache;
51 private ClasspathTypeLoader m_defaultTypeLoader;
52
53 public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex) {
54 this(jar, jarIndex, new Translator(), new Translator());
55 }
56
57 public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator) {
58 m_jar = jar;
59 m_jarIndex = jarIndex;
60 m_obfuscatingTranslator = obfuscatingTranslator;
61 m_deobfuscatingTranslator = deobfuscatingTranslator;
62 m_cache = Maps.newHashMap();
63 m_defaultTypeLoader = new ClasspathTypeLoader();
64 }
65
66 public void clearCache() {
67 m_cache.clear();
68 }
69
70 @Override
71 public boolean tryLoadType(String className, Buffer out) {
72
73 // check the cache
74 byte[] data;
75 if (m_cache.containsKey(className)) {
76 data = m_cache.get(className);
77 } else {
78 data = loadType(className);
79 m_cache.put(className, data);
80 }
81
82 if (data == null) {
83 // chain to default type loader
84 return m_defaultTypeLoader.tryLoadType(className, out);
85 }
86
87 // send the class to the decompiler
88 out.reset(data.length);
89 System.arraycopy(data, 0, out.array(), out.position(), data.length);
90 out.position(0);
91 return true;
92 }
93
94 public CtClass loadClass(String deobfClassName) {
95
96 byte[] data = loadType(deobfClassName);
97 if (data == null) {
98 return null;
99 }
100
101 // return a javassist handle for the class
102 String javaClassFileName = Descriptor.toJavaName(deobfClassName);
103 ClassPool classPool = new ClassPool();
104 classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, data));
105 try {
106 return classPool.get(javaClassFileName);
107 } catch (NotFoundException ex) {
108 throw new Error(ex);
109 }
110 }
111
112 private byte[] loadType(String className) {
113
114 // NOTE: don't know if class name is obf or deobf
115 ClassEntry classEntry = new ClassEntry(className);
116 ClassEntry obfClassEntry = m_obfuscatingTranslator.translateEntry(classEntry);
117
118 // is this an inner class referenced directly? (ie trying to load b instead of a$b)
119 if (!obfClassEntry.isInnerClass()) {
120 List<ClassEntry> classChain = m_jarIndex.getObfClassChain(obfClassEntry);
121 if (classChain.size() > 1) {
122 System.err.println(String.format("WARNING: no class %s after inner class reconstruction. Try %s",
123 className, obfClassEntry.buildClassEntry(classChain)
124 ));
125 return null;
126 }
127 }
128
129 // is this a class we should even know about?
130 if (!m_jarIndex.containsObfClass(obfClassEntry)) {
131 return null;
132 }
133
134 // DEBUG
135 //System.out.println(String.format("Looking for %s (obf: %s)", classEntry.getName(), obfClassEntry.getName()));
136
137 // find the class in the jar
138 String classInJarName = findClassInJar(obfClassEntry);
139 if (classInJarName == null) {
140 // couldn't find it
141 return null;
142 }
143
144 try {
145 // read the class file into a buffer
146 ByteArrayOutputStream data = new ByteArrayOutputStream();
147 byte[] buf = new byte[1024 * 1024]; // 1 KiB
148 InputStream in = m_jar.getInputStream(m_jar.getJarEntry(classInJarName + ".class"));
149 while (true) {
150 int bytesRead = in.read(buf);
151 if (bytesRead <= 0) {
152 break;
153 }
154 data.write(buf, 0, bytesRead);
155 }
156 data.close();
157 in.close();
158 buf = data.toByteArray();
159
160 // load the javassist handle to the raw class
161 ClassPool classPool = new ClassPool();
162 String classInJarJavaName = Descriptor.toJavaName(classInJarName);
163 classPool.insertClassPath(new ByteArrayClassPath(classInJarJavaName, buf));
164 CtClass c = classPool.get(classInJarJavaName);
165
166 c = transformClass(c);
167
168 // sanity checking
169 assertClassName(c, classEntry);
170
171 // DEBUG
172 //Util.writeClass( c );
173
174 // we have a transformed class!
175 return c.toBytecode();
176 } catch (IOException | NotFoundException | CannotCompileException ex) {
177 throw new Error(ex);
178 }
179 }
180
181 private String findClassInJar(ClassEntry obfClassEntry) {
182
183 // try to find the class in the jar
184 for (String className : getClassNamesToTry(obfClassEntry)) {
185 JarEntry jarEntry = m_jar.getJarEntry(className + ".class");
186 if (jarEntry != null) {
187 return className;
188 }
189 }
190
191 // didn't find it ;_;
192 return null;
193 }
194
195 public List<String> getClassNamesToTry(String className) {
196 return getClassNamesToTry(m_obfuscatingTranslator.translateEntry(new ClassEntry(className)));
197 }
198
199 public List<String> getClassNamesToTry(ClassEntry obfClassEntry) {
200 List<String> classNamesToTry = Lists.newArrayList();
201 classNamesToTry.add(obfClassEntry.getName());
202 if (obfClassEntry.getPackageName().equals(Constants.NonePackage)) {
203 // taking off the none package, if any
204 classNamesToTry.add(obfClassEntry.getSimpleName());
205 }
206 if (obfClassEntry.isInnerClass()) {
207 // try just the inner class name
208 classNamesToTry.add(obfClassEntry.getInnermostClassName());
209 }
210 return classNamesToTry;
211 }
212
213 public CtClass transformClass(CtClass c)
214 throws IOException, NotFoundException, CannotCompileException {
215
216 // we moved a lot of classes out of the default package into the none package
217 // make sure all the class references are consistent
218 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
219
220 // reconstruct inner classes
221 new InnerClassWriter(m_jarIndex).write(c);
222
223 // re-get the javassist handle since we changed class names
224 ClassEntry obfClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
225 String javaClassReconstructedName = Descriptor.toJavaName(obfClassEntry.getName());
226 ClassPool classPool = new ClassPool();
227 classPool.insertClassPath(new ByteArrayClassPath(javaClassReconstructedName, c.toBytecode()));
228 c = classPool.get(javaClassReconstructedName);
229
230 // check that the file is correct after inner class reconstruction (ie cause Javassist to fail fast if something is wrong)
231 assertClassName(c, obfClassEntry);
232
233 // do all kinds of deobfuscating transformations on the class
234 new BridgeMarker(m_jarIndex).markBridges(c);
235 new MethodParameterWriter(m_deobfuscatingTranslator).writeMethodArguments(c);
236 new LocalVariableRenamer(m_deobfuscatingTranslator).rename(c);
237 new ClassTranslator(m_deobfuscatingTranslator).translate(c);
238
239 return c;
240 }
241
242 private void assertClassName(CtClass c, ClassEntry obfClassEntry) {
243 String name1 = Descriptor.toJvmName(c.getName());
244 assert (name1.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name1);
245
246 String name2 = Descriptor.toJvmName(c.getClassFile().getName());
247 assert (name2.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name2);
248 }
249}
diff --git a/src/cuchaz/enigma/Util.java b/src/cuchaz/enigma/Util.java
deleted file mode 100644
index c7e509fa..00000000
--- a/src/cuchaz/enigma/Util.java
+++ /dev/null
@@ -1,104 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.awt.Desktop;
14import java.io.Closeable;
15import java.io.File;
16import java.io.FileOutputStream;
17import java.io.IOException;
18import java.io.InputStream;
19import java.io.InputStreamReader;
20import java.net.URI;
21import java.net.URISyntaxException;
22import java.util.Arrays;
23import java.util.jar.JarFile;
24
25import javassist.CannotCompileException;
26import javassist.CtClass;
27import javassist.bytecode.Descriptor;
28
29import com.google.common.io.CharStreams;
30
31public class Util {
32
33 public static int combineHashesOrdered(Object... objs) {
34 return combineHashesOrdered(Arrays.asList(objs));
35 }
36
37 public static int combineHashesOrdered(Iterable<Object> objs) {
38 final int prime = 67;
39 int result = 1;
40 for (Object obj : objs) {
41 result *= prime;
42 if (obj != null) {
43 result += obj.hashCode();
44 }
45 }
46 return result;
47 }
48
49 public static void closeQuietly(Closeable closeable) {
50 if (closeable != null) {
51 try {
52 closeable.close();
53 } catch (IOException ex) {
54 // just ignore any further exceptions
55 }
56 }
57 }
58
59 public static void closeQuietly(JarFile jarFile) {
60 // silly library should implement Closeable...
61 if (jarFile != null) {
62 try {
63 jarFile.close();
64 } catch (IOException ex) {
65 // just ignore any further exceptions
66 }
67 }
68 }
69
70 public static String readStreamToString(InputStream in) throws IOException {
71 return CharStreams.toString(new InputStreamReader(in, "UTF-8"));
72 }
73
74 public static String readResourceToString(String path) throws IOException {
75 InputStream in = Util.class.getResourceAsStream(path);
76 if (in == null) {
77 throw new IllegalArgumentException("Resource not found! " + path);
78 }
79 return readStreamToString(in);
80 }
81
82 public static void openUrl(String url) {
83 if (Desktop.isDesktopSupported()) {
84 Desktop desktop = Desktop.getDesktop();
85 try {
86 desktop.browse(new URI(url));
87 } catch (IOException ex) {
88 throw new Error(ex);
89 } catch (URISyntaxException ex) {
90 throw new IllegalArgumentException(ex);
91 }
92 }
93 }
94
95 public static void writeClass(CtClass c) {
96 String name = Descriptor.toJavaName(c.getName());
97 File file = new File(name + ".class");
98 try (FileOutputStream out = new FileOutputStream(file)) {
99 out.write(c.toBytecode());
100 } catch (IOException | CannotCompileException ex) {
101 throw new Error(ex);
102 }
103 }
104}
diff --git a/src/cuchaz/enigma/analysis/Access.java b/src/cuchaz/enigma/analysis/Access.java
deleted file mode 100644
index 1c8cfc48..00000000
--- a/src/cuchaz/enigma/analysis/Access.java
+++ /dev/null
@@ -1,43 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.lang.reflect.Modifier;
14
15import javassist.CtBehavior;
16import javassist.CtField;
17
18public enum Access {
19
20 Public,
21 Protected,
22 Private;
23
24 public static Access get(CtBehavior behavior) {
25 return get(behavior.getModifiers());
26 }
27
28 public static Access get(CtField field) {
29 return get(field.getModifiers());
30 }
31
32 public static Access get(int modifiers) {
33 if (Modifier.isPublic(modifiers)) {
34 return Public;
35 } else if (Modifier.isProtected(modifiers)) {
36 return Protected;
37 } else if (Modifier.isPrivate(modifiers)) {
38 return Private;
39 }
40 // assume public by default
41 return Public;
42 }
43}
diff --git a/src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java b/src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java
deleted file mode 100644
index 353a4bf0..00000000
--- a/src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java
+++ /dev/null
@@ -1,93 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.Set;
14
15import javax.swing.tree.DefaultMutableTreeNode;
16import javax.swing.tree.TreeNode;
17
18import com.google.common.collect.Sets;
19
20import cuchaz.enigma.mapping.BehaviorEntry;
21import cuchaz.enigma.mapping.Entry;
22import cuchaz.enigma.mapping.Translator;
23
24public class BehaviorReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<BehaviorEntry,BehaviorEntry> {
25
26 private static final long serialVersionUID = -3658163700783307520L;
27
28 private Translator m_deobfuscatingTranslator;
29 private BehaviorEntry m_entry;
30 private EntryReference<BehaviorEntry,BehaviorEntry> m_reference;
31 private Access m_access;
32
33 public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, BehaviorEntry entry) {
34 m_deobfuscatingTranslator = deobfuscatingTranslator;
35 m_entry = entry;
36 m_reference = null;
37 }
38
39 public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<BehaviorEntry,BehaviorEntry> reference, Access access) {
40 m_deobfuscatingTranslator = deobfuscatingTranslator;
41 m_entry = reference.entry;
42 m_reference = reference;
43 m_access = access;
44 }
45
46 @Override
47 public BehaviorEntry getEntry() {
48 return m_entry;
49 }
50
51 @Override
52 public EntryReference<BehaviorEntry,BehaviorEntry> getReference() {
53 return m_reference;
54 }
55
56 @Override
57 public String toString() {
58 if (m_reference != null) {
59 return String.format("%s (%s)", m_deobfuscatingTranslator.translateEntry(m_reference.context), m_access);
60 }
61 return m_deobfuscatingTranslator.translateEntry(m_entry).toString();
62 }
63
64 public void load(JarIndex index, boolean recurse) {
65 // get all the child nodes
66 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(m_entry)) {
67 add(new BehaviorReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_entry)));
68 }
69
70 if (recurse && children != null) {
71 for (Object child : children) {
72 if (child instanceof BehaviorReferenceTreeNode) {
73 BehaviorReferenceTreeNode node = (BehaviorReferenceTreeNode)child;
74
75 // don't recurse into ancestor
76 Set<Entry> ancestors = Sets.newHashSet();
77 TreeNode n = (TreeNode)node;
78 while (n.getParent() != null) {
79 n = n.getParent();
80 if (n instanceof BehaviorReferenceTreeNode) {
81 ancestors.add( ((BehaviorReferenceTreeNode)n).getEntry());
82 }
83 }
84 if (ancestors.contains(node.getEntry())) {
85 continue;
86 }
87
88 node.load(index, true);
89 }
90 }
91 }
92 }
93}
diff --git a/src/cuchaz/enigma/analysis/BridgeMarker.java b/src/cuchaz/enigma/analysis/BridgeMarker.java
deleted file mode 100644
index 650b3a78..00000000
--- a/src/cuchaz/enigma/analysis/BridgeMarker.java
+++ /dev/null
@@ -1,43 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import javassist.CtClass;
14import javassist.CtMethod;
15import javassist.bytecode.AccessFlag;
16import cuchaz.enigma.mapping.EntryFactory;
17import cuchaz.enigma.mapping.MethodEntry;
18
19public class BridgeMarker {
20
21 private JarIndex m_jarIndex;
22
23 public BridgeMarker(JarIndex jarIndex) {
24 m_jarIndex = jarIndex;
25 }
26
27 public void markBridges(CtClass c) {
28
29 for (CtMethod method : c.getDeclaredMethods()) {
30 MethodEntry methodEntry = EntryFactory.getMethodEntry(method);
31
32 // is this a bridge method?
33 MethodEntry bridgedMethodEntry = m_jarIndex.getBridgedMethod(methodEntry);
34 if (bridgedMethodEntry != null) {
35
36 // it's a bridge method! add the bridge flag
37 int flags = method.getMethodInfo().getAccessFlags();
38 flags |= AccessFlag.BRIDGE;
39 method.getMethodInfo().setAccessFlags(flags);
40 }
41 }
42 }
43}
diff --git a/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
deleted file mode 100644
index cc70f51e..00000000
--- a/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
+++ /dev/null
@@ -1,80 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.List;
14
15import javax.swing.tree.DefaultMutableTreeNode;
16
17import com.google.common.collect.Lists;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.MethodEntry;
21import cuchaz.enigma.mapping.Translator;
22
23public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
24
25 private static final long serialVersionUID = 3112703459157851912L;
26
27 private Translator m_deobfuscatingTranslator;
28 private ClassEntry m_entry;
29
30 public ClassImplementationsTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) {
31 m_deobfuscatingTranslator = deobfuscatingTranslator;
32 m_entry = entry;
33 }
34
35 public ClassEntry getClassEntry() {
36 return m_entry;
37 }
38
39 public String getDeobfClassName() {
40 return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
41 }
42
43 @Override
44 public String toString() {
45 String className = getDeobfClassName();
46 if (className == null) {
47 className = m_entry.getClassName();
48 }
49 return className;
50 }
51
52 public void load(JarIndex index) {
53 // get all method implementations
54 List<ClassImplementationsTreeNode> nodes = Lists.newArrayList();
55 for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) {
56 nodes.add(new ClassImplementationsTreeNode(m_deobfuscatingTranslator, new ClassEntry(implementingClassName)));
57 }
58
59 // add them to this node
60 for (ClassImplementationsTreeNode node : nodes) {
61 this.add(node);
62 }
63 }
64
65 public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) {
66 // is this the node?
67 if (node.m_entry.equals(entry)) {
68 return node;
69 }
70
71 // recurse
72 for (int i = 0; i < node.getChildCount(); i++) {
73 ClassImplementationsTreeNode foundNode = findNode((ClassImplementationsTreeNode)node.getChildAt(i), entry);
74 if (foundNode != null) {
75 return foundNode;
76 }
77 }
78 return null;
79 }
80}
diff --git a/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java b/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java
deleted file mode 100644
index 7542bd9d..00000000
--- a/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java
+++ /dev/null
@@ -1,85 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.List;
14
15import javax.swing.tree.DefaultMutableTreeNode;
16
17import com.google.common.collect.Lists;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.Translator;
21
22public class ClassInheritanceTreeNode extends DefaultMutableTreeNode {
23
24 private static final long serialVersionUID = 4432367405826178490L;
25
26 private Translator m_deobfuscatingTranslator;
27 private String m_obfClassName;
28
29 public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) {
30 m_deobfuscatingTranslator = deobfuscatingTranslator;
31 m_obfClassName = obfClassName;
32 }
33
34 public String getObfClassName() {
35 return m_obfClassName;
36 }
37
38 public String getDeobfClassName() {
39 return m_deobfuscatingTranslator.translateClass(m_obfClassName);
40 }
41
42 @Override
43 public String toString() {
44 String deobfClassName = getDeobfClassName();
45 if (deobfClassName != null) {
46 return deobfClassName;
47 }
48 return m_obfClassName;
49 }
50
51 public void load(TranslationIndex ancestries, boolean recurse) {
52 // get all the child nodes
53 List<ClassInheritanceTreeNode> nodes = Lists.newArrayList();
54 for (ClassEntry subclassEntry : ancestries.getSubclass(new ClassEntry(m_obfClassName))) {
55 nodes.add(new ClassInheritanceTreeNode(m_deobfuscatingTranslator, subclassEntry.getName()));
56 }
57
58 // add them to this node
59 for (ClassInheritanceTreeNode node : nodes) {
60 this.add(node);
61 }
62
63 if (recurse) {
64 for (ClassInheritanceTreeNode node : nodes) {
65 node.load(ancestries, true);
66 }
67 }
68 }
69
70 public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) {
71 // is this the node?
72 if (node.getObfClassName().equals(entry.getName())) {
73 return node;
74 }
75
76 // recurse
77 for (int i = 0; i < node.getChildCount(); i++) {
78 ClassInheritanceTreeNode foundNode = findNode((ClassInheritanceTreeNode)node.getChildAt(i), entry);
79 if (foundNode != null) {
80 return foundNode;
81 }
82 }
83 return null;
84 }
85}
diff --git a/src/cuchaz/enigma/analysis/EntryReference.java b/src/cuchaz/enigma/analysis/EntryReference.java
deleted file mode 100644
index 85127239..00000000
--- a/src/cuchaz/enigma/analysis/EntryReference.java
+++ /dev/null
@@ -1,126 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.Arrays;
14import java.util.List;
15
16import cuchaz.enigma.Util;
17import cuchaz.enigma.mapping.ClassEntry;
18import cuchaz.enigma.mapping.ConstructorEntry;
19import cuchaz.enigma.mapping.Entry;
20
21public class EntryReference<E extends Entry,C extends Entry> {
22
23 private static final List<String> ConstructorNonNames = Arrays.asList("this", "super", "static");
24 public E entry;
25 public C context;
26
27 private boolean m_isNamed;
28
29 public EntryReference(E entry, String sourceName) {
30 this(entry, sourceName, null);
31 }
32
33 public EntryReference(E entry, String sourceName, C context) {
34 if (entry == null) {
35 throw new IllegalArgumentException("Entry cannot be null!");
36 }
37
38 this.entry = entry;
39 this.context = context;
40
41 m_isNamed = sourceName != null && sourceName.length() > 0;
42 if (entry instanceof ConstructorEntry && ConstructorNonNames.contains(sourceName)) {
43 m_isNamed = false;
44 }
45 }
46
47 public EntryReference(E entry, C context, EntryReference<E,C> other) {
48 this.entry = entry;
49 this.context = context;
50 m_isNamed = other.m_isNamed;
51 }
52
53 public ClassEntry getLocationClassEntry() {
54 if (context != null) {
55 return context.getClassEntry();
56 }
57 return entry.getClassEntry();
58 }
59
60 public boolean isNamed() {
61 return m_isNamed;
62 }
63
64 public Entry getNameableEntry() {
65 if (entry instanceof ConstructorEntry) {
66 // renaming a constructor really means renaming the class
67 return entry.getClassEntry();
68 }
69 return entry;
70 }
71
72 public String getNamableName() {
73 if (getNameableEntry() instanceof ClassEntry) {
74 ClassEntry classEntry = (ClassEntry)getNameableEntry();
75 if (classEntry.isInnerClass()) {
76 // make sure we only rename the inner class name
77 return classEntry.getInnermostClassName();
78 }
79 }
80
81 return getNameableEntry().getName();
82 }
83
84 @Override
85 public int hashCode() {
86 if (context != null) {
87 return Util.combineHashesOrdered(entry.hashCode(), context.hashCode());
88 }
89 return entry.hashCode();
90 }
91
92 @Override
93 public boolean equals(Object other) {
94 if (other instanceof EntryReference) {
95 return equals((EntryReference<?,?>)other);
96 }
97 return false;
98 }
99
100 public boolean equals(EntryReference<?,?> other) {
101 // check entry first
102 boolean isEntrySame = entry.equals(other.entry);
103 if (!isEntrySame) {
104 return false;
105 }
106
107 // check caller
108 if (context == null && other.context == null) {
109 return true;
110 } else if (context != null && other.context != null) {
111 return context.equals(other.context);
112 }
113 return false;
114 }
115
116 @Override
117 public String toString() {
118 StringBuilder buf = new StringBuilder();
119 buf.append(entry);
120 if (context != null) {
121 buf.append(" called from ");
122 buf.append(context);
123 }
124 return buf.toString();
125 }
126}
diff --git a/src/cuchaz/enigma/analysis/EntryRenamer.java b/src/cuchaz/enigma/analysis/EntryRenamer.java
deleted file mode 100644
index f748274f..00000000
--- a/src/cuchaz/enigma/analysis/EntryRenamer.java
+++ /dev/null
@@ -1,192 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.AbstractMap;
14import java.util.List;
15import java.util.Map;
16import java.util.Set;
17
18import com.google.common.collect.Lists;
19import com.google.common.collect.Multimap;
20import com.google.common.collect.Sets;
21
22import cuchaz.enigma.mapping.ArgumentEntry;
23import cuchaz.enigma.mapping.ClassEntry;
24import cuchaz.enigma.mapping.ClassNameReplacer;
25import cuchaz.enigma.mapping.ConstructorEntry;
26import cuchaz.enigma.mapping.Entry;
27import cuchaz.enigma.mapping.FieldEntry;
28import cuchaz.enigma.mapping.MethodEntry;
29import cuchaz.enigma.mapping.Signature;
30import cuchaz.enigma.mapping.Type;
31
32public class EntryRenamer {
33
34 public static <T> void renameClassesInSet(Map<String,String> renames, Set<T> set) {
35 List<T> entries = Lists.newArrayList();
36 for (T val : set) {
37 entries.add(renameClassesInThing(renames, val));
38 }
39 set.clear();
40 set.addAll(entries);
41 }
42
43 public static <Key,Val> void renameClassesInMap(Map<String,String> renames, Map<Key,Val> map) {
44 // for each key/value pair...
45 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
46 for (Map.Entry<Key,Val> entry : map.entrySet()) {
47 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
48 renameClassesInThing(renames, entry.getKey()),
49 renameClassesInThing(renames, entry.getValue())
50 ));
51 }
52 map.clear();
53 for (Map.Entry<Key,Val> entry : entriesToAdd) {
54 map.put(entry.getKey(), entry.getValue());
55 }
56 }
57
58 public static <Key,Val> void renameClassesInMultimap(Map<String,String> renames, Multimap<Key,Val> map) {
59 // for each key/value pair...
60 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
61 for (Map.Entry<Key,Val> entry : map.entries()) {
62 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
63 renameClassesInThing(renames, entry.getKey()),
64 renameClassesInThing(renames, entry.getValue())
65 ));
66 }
67 map.clear();
68 for (Map.Entry<Key,Val> entry : entriesToAdd) {
69 map.put(entry.getKey(), entry.getValue());
70 }
71 }
72
73 public static <Key,Val> void renameMethodsInMultimap(Map<MethodEntry,MethodEntry> renames, Multimap<Key,Val> map) {
74 // for each key/value pair...
75 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
76 for (Map.Entry<Key,Val> entry : map.entries()) {
77 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
78 renameMethodsInThing(renames, entry.getKey()),
79 renameMethodsInThing(renames, entry.getValue())
80 ));
81 }
82 map.clear();
83 for (Map.Entry<Key,Val> entry : entriesToAdd) {
84 map.put(entry.getKey(), entry.getValue());
85 }
86 }
87
88 public static <Key,Val> void renameMethodsInMap(Map<MethodEntry,MethodEntry> renames, Map<Key,Val> map) {
89 // for each key/value pair...
90 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
91 for (Map.Entry<Key,Val> entry : map.entrySet()) {
92 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
93 renameMethodsInThing(renames, entry.getKey()),
94 renameMethodsInThing(renames, entry.getValue())
95 ));
96 }
97 map.clear();
98 for (Map.Entry<Key,Val> entry : entriesToAdd) {
99 map.put(entry.getKey(), entry.getValue());
100 }
101 }
102
103 @SuppressWarnings("unchecked")
104 public static <T> T renameMethodsInThing(Map<MethodEntry,MethodEntry> renames, T thing) {
105 if (thing instanceof MethodEntry) {
106 MethodEntry methodEntry = (MethodEntry)thing;
107 MethodEntry newMethodEntry = renames.get(methodEntry);
108 if (newMethodEntry != null) {
109 return (T)new MethodEntry(
110 methodEntry.getClassEntry(),
111 newMethodEntry.getName(),
112 methodEntry.getSignature()
113 );
114 }
115 return thing;
116 } else if (thing instanceof ArgumentEntry) {
117 ArgumentEntry argumentEntry = (ArgumentEntry)thing;
118 return (T)new ArgumentEntry(
119 renameMethodsInThing(renames, argumentEntry.getBehaviorEntry()),
120 argumentEntry.getIndex(),
121 argumentEntry.getName()
122 );
123 } else if (thing instanceof EntryReference) {
124 EntryReference<Entry,Entry> reference = (EntryReference<Entry,Entry>)thing;
125 reference.entry = renameMethodsInThing(renames, reference.entry);
126 reference.context = renameMethodsInThing(renames, reference.context);
127 return thing;
128 }
129 return thing;
130 }
131
132 @SuppressWarnings("unchecked")
133 public static <T> T renameClassesInThing(final Map<String,String> renames, T thing) {
134 if (thing instanceof String) {
135 String stringEntry = (String)thing;
136 if (renames.containsKey(stringEntry)) {
137 return (T)renames.get(stringEntry);
138 }
139 } else if (thing instanceof ClassEntry) {
140 ClassEntry classEntry = (ClassEntry)thing;
141 return (T)new ClassEntry(renameClassesInThing(renames, classEntry.getClassName()));
142 } else if (thing instanceof FieldEntry) {
143 FieldEntry fieldEntry = (FieldEntry)thing;
144 return (T)new FieldEntry(
145 renameClassesInThing(renames, fieldEntry.getClassEntry()),
146 fieldEntry.getName(),
147 renameClassesInThing(renames, fieldEntry.getType())
148 );
149 } else if (thing instanceof ConstructorEntry) {
150 ConstructorEntry constructorEntry = (ConstructorEntry)thing;
151 return (T)new ConstructorEntry(
152 renameClassesInThing(renames, constructorEntry.getClassEntry()),
153 renameClassesInThing(renames, constructorEntry.getSignature())
154 );
155 } else if (thing instanceof MethodEntry) {
156 MethodEntry methodEntry = (MethodEntry)thing;
157 return (T)new MethodEntry(
158 renameClassesInThing(renames, methodEntry.getClassEntry()),
159 methodEntry.getName(),
160 renameClassesInThing(renames, methodEntry.getSignature())
161 );
162 } else if (thing instanceof ArgumentEntry) {
163 ArgumentEntry argumentEntry = (ArgumentEntry)thing;
164 return (T)new ArgumentEntry(
165 renameClassesInThing(renames, argumentEntry.getBehaviorEntry()),
166 argumentEntry.getIndex(),
167 argumentEntry.getName()
168 );
169 } else if (thing instanceof EntryReference) {
170 EntryReference<Entry,Entry> reference = (EntryReference<Entry,Entry>)thing;
171 reference.entry = renameClassesInThing(renames, reference.entry);
172 reference.context = renameClassesInThing(renames, reference.context);
173 return thing;
174 } else if (thing instanceof Signature) {
175 return (T)new Signature((Signature)thing, new ClassNameReplacer() {
176 @Override
177 public String replace(String className) {
178 return renameClassesInThing(renames, className);
179 }
180 });
181 } else if (thing instanceof Type) {
182 return (T)new Type((Type)thing, new ClassNameReplacer() {
183 @Override
184 public String replace(String className) {
185 return renameClassesInThing(renames, className);
186 }
187 });
188 }
189
190 return thing;
191 }
192}
diff --git a/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
deleted file mode 100644
index 4ed8fee2..00000000
--- a/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
+++ /dev/null
@@ -1,81 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import javax.swing.tree.DefaultMutableTreeNode;
14
15import cuchaz.enigma.mapping.BehaviorEntry;
16import cuchaz.enigma.mapping.FieldEntry;
17import cuchaz.enigma.mapping.Translator;
18
19public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<FieldEntry,BehaviorEntry> {
20
21 private static final long serialVersionUID = -7934108091928699835L;
22
23 private Translator m_deobfuscatingTranslator;
24 private FieldEntry m_entry;
25 private EntryReference<FieldEntry,BehaviorEntry> m_reference;
26 private Access m_access;
27
28 public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) {
29 m_deobfuscatingTranslator = deobfuscatingTranslator;
30 m_entry = entry;
31 m_reference = null;
32 }
33
34 private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<FieldEntry,BehaviorEntry> reference, Access access) {
35 m_deobfuscatingTranslator = deobfuscatingTranslator;
36 m_entry = reference.entry;
37 m_reference = reference;
38 m_access = access;
39 }
40
41 @Override
42 public FieldEntry getEntry() {
43 return m_entry;
44 }
45
46 @Override
47 public EntryReference<FieldEntry,BehaviorEntry> getReference() {
48 return m_reference;
49 }
50
51 @Override
52 public String toString() {
53 if (m_reference != null) {
54 return String.format("%s (%s)", m_deobfuscatingTranslator.translateEntry(m_reference.context), m_access);
55 }
56 return m_deobfuscatingTranslator.translateEntry(m_entry).toString();
57 }
58
59 public void load(JarIndex index, boolean recurse) {
60 // get all the child nodes
61 if (m_reference == null) {
62 for (EntryReference<FieldEntry,BehaviorEntry> reference : index.getFieldReferences(m_entry)) {
63 add(new FieldReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_entry)));
64 }
65 } else {
66 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(m_reference.context)) {
67 add(new BehaviorReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_reference.context)));
68 }
69 }
70
71 if (recurse && children != null) {
72 for (Object node : children) {
73 if (node instanceof BehaviorReferenceTreeNode) {
74 ((BehaviorReferenceTreeNode)node).load(index, true);
75 } else if (node instanceof FieldReferenceTreeNode) {
76 ((FieldReferenceTreeNode)node).load(index, true);
77 }
78 }
79 }
80 }
81}
diff --git a/src/cuchaz/enigma/analysis/JarClassIterator.java b/src/cuchaz/enigma/analysis/JarClassIterator.java
deleted file mode 100644
index aa58e9ec..00000000
--- a/src/cuchaz/enigma/analysis/JarClassIterator.java
+++ /dev/null
@@ -1,137 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.io.ByteArrayOutputStream;
14import java.io.IOException;
15import java.io.InputStream;
16import java.util.Enumeration;
17import java.util.Iterator;
18import java.util.List;
19import java.util.jar.JarEntry;
20import java.util.jar.JarFile;
21
22import javassist.ByteArrayClassPath;
23import javassist.ClassPool;
24import javassist.CtClass;
25import javassist.NotFoundException;
26import javassist.bytecode.Descriptor;
27
28import com.google.common.collect.Lists;
29
30import cuchaz.enigma.Constants;
31import cuchaz.enigma.mapping.ClassEntry;
32
33public class JarClassIterator implements Iterator<CtClass> {
34
35 private JarFile m_jar;
36 private Iterator<JarEntry> m_iter;
37
38 public JarClassIterator(JarFile jar) {
39 m_jar = jar;
40
41 // get the jar entries that correspond to classes
42 List<JarEntry> classEntries = Lists.newArrayList();
43 Enumeration<JarEntry> entries = m_jar.entries();
44 while (entries.hasMoreElements()) {
45 JarEntry entry = entries.nextElement();
46
47 // is this a class file?
48 if (entry.getName().endsWith(".class")) {
49 classEntries.add(entry);
50 }
51 }
52 m_iter = classEntries.iterator();
53 }
54
55 @Override
56 public boolean hasNext() {
57 return m_iter.hasNext();
58 }
59
60 @Override
61 public CtClass next() {
62 JarEntry entry = m_iter.next();
63 try {
64 return getClass(m_jar, entry);
65 } catch (IOException | NotFoundException ex) {
66 throw new Error("Unable to load class: " + entry.getName());
67 }
68 }
69
70 @Override
71 public void remove() {
72 throw new UnsupportedOperationException();
73 }
74
75 public static List<ClassEntry> getClassEntries(JarFile jar) {
76 List<ClassEntry> classEntries = Lists.newArrayList();
77 Enumeration<JarEntry> entries = jar.entries();
78 while (entries.hasMoreElements()) {
79 JarEntry entry = entries.nextElement();
80
81 // is this a class file?
82 if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
83 classEntries.add(getClassEntry(entry));
84 }
85 }
86 return classEntries;
87 }
88
89 public static Iterable<CtClass> classes(final JarFile jar) {
90 return new Iterable<CtClass>() {
91 @Override
92 public Iterator<CtClass> iterator() {
93 return new JarClassIterator(jar);
94 }
95 };
96 }
97
98 public static CtClass getClass(JarFile jar, ClassEntry classEntry) {
99 try {
100 return getClass(jar, new JarEntry(classEntry.getName() + ".class"));
101 } catch (IOException | NotFoundException ex) {
102 throw new Error("Unable to load class: " + classEntry.getName());
103 }
104 }
105
106 private static CtClass getClass(JarFile jar, JarEntry entry) throws IOException, NotFoundException {
107 // read the class into a buffer
108 ByteArrayOutputStream bos = new ByteArrayOutputStream();
109 byte[] buf = new byte[Constants.KiB];
110 int totalNumBytesRead = 0;
111 InputStream in = jar.getInputStream(entry);
112 while (in.available() > 0) {
113 int numBytesRead = in.read(buf);
114 if (numBytesRead < 0) {
115 break;
116 }
117 bos.write(buf, 0, numBytesRead);
118
119 // sanity checking
120 totalNumBytesRead += numBytesRead;
121 if (totalNumBytesRead > Constants.MiB) {
122 throw new Error("Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!");
123 }
124 }
125
126 // get a javassist handle for the class
127 String className = Descriptor.toJavaName(getClassEntry(entry).getName());
128 ClassPool classPool = new ClassPool();
129 classPool.appendSystemPath();
130 classPool.insertClassPath(new ByteArrayClassPath(className, bos.toByteArray()));
131 return classPool.get(className);
132 }
133
134 private static ClassEntry getClassEntry(JarEntry entry) {
135 return new ClassEntry(entry.getName().substring(0, entry.getName().length() - ".class".length()));
136 }
137}
diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java
deleted file mode 100644
index 7e3c1b59..00000000
--- a/src/cuchaz/enigma/analysis/JarIndex.java
+++ /dev/null
@@ -1,839 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.lang.reflect.Modifier;
14import java.util.Collection;
15import java.util.Collections;
16import java.util.HashSet;
17import java.util.List;
18import java.util.Map;
19import java.util.Set;
20import java.util.jar.JarFile;
21
22import javassist.CannotCompileException;
23import javassist.CtBehavior;
24import javassist.CtClass;
25import javassist.CtConstructor;
26import javassist.CtField;
27import javassist.CtMethod;
28import javassist.NotFoundException;
29import javassist.bytecode.AccessFlag;
30import javassist.bytecode.Descriptor;
31import javassist.bytecode.EnclosingMethodAttribute;
32import javassist.bytecode.FieldInfo;
33import javassist.bytecode.InnerClassesAttribute;
34import javassist.expr.ConstructorCall;
35import javassist.expr.ExprEditor;
36import javassist.expr.FieldAccess;
37import javassist.expr.MethodCall;
38import javassist.expr.NewExpr;
39
40import com.google.common.collect.HashMultimap;
41import com.google.common.collect.Lists;
42import com.google.common.collect.Maps;
43import com.google.common.collect.Multimap;
44import com.google.common.collect.Sets;
45
46import cuchaz.enigma.Constants;
47import cuchaz.enigma.bytecode.ClassRenamer;
48import cuchaz.enigma.mapping.ArgumentEntry;
49import cuchaz.enigma.mapping.BehaviorEntry;
50import cuchaz.enigma.mapping.ClassEntry;
51import cuchaz.enigma.mapping.ConstructorEntry;
52import cuchaz.enigma.mapping.Entry;
53import cuchaz.enigma.mapping.EntryFactory;
54import cuchaz.enigma.mapping.FieldEntry;
55import cuchaz.enigma.mapping.MethodEntry;
56import cuchaz.enigma.mapping.Translator;
57
58public class JarIndex {
59
60 private Set<ClassEntry> m_obfClassEntries;
61 private TranslationIndex m_translationIndex;
62 private Map<Entry,Access> m_access;
63 private Multimap<ClassEntry,FieldEntry> m_fields;
64 private Multimap<ClassEntry,BehaviorEntry> m_behaviors;
65 private Multimap<String,MethodEntry> m_methodImplementations;
66 private Multimap<BehaviorEntry,EntryReference<BehaviorEntry,BehaviorEntry>> m_behaviorReferences;
67 private Multimap<FieldEntry,EntryReference<FieldEntry,BehaviorEntry>> m_fieldReferences;
68 private Multimap<ClassEntry,ClassEntry> m_innerClassesByOuter;
69 private Map<ClassEntry,ClassEntry> m_outerClassesByInner;
70 private Map<ClassEntry,BehaviorEntry> m_anonymousClasses;
71 private Map<MethodEntry,MethodEntry> m_bridgedMethods;
72
73 public JarIndex() {
74 m_obfClassEntries = Sets.newHashSet();
75 m_translationIndex = new TranslationIndex();
76 m_access = Maps.newHashMap();
77 m_fields = HashMultimap.create();
78 m_behaviors = HashMultimap.create();
79 m_methodImplementations = HashMultimap.create();
80 m_behaviorReferences = HashMultimap.create();
81 m_fieldReferences = HashMultimap.create();
82 m_innerClassesByOuter = HashMultimap.create();
83 m_outerClassesByInner = Maps.newHashMap();
84 m_anonymousClasses = Maps.newHashMap();
85 m_bridgedMethods = Maps.newHashMap();
86 }
87
88 public void indexJar(JarFile jar, boolean buildInnerClasses) {
89
90 // step 1: read the class names
91 for (ClassEntry classEntry : JarClassIterator.getClassEntries(jar)) {
92 if (classEntry.isInDefaultPackage()) {
93 // move out of default package
94 classEntry = new ClassEntry(Constants.NonePackage + "/" + classEntry.getName());
95 }
96 m_obfClassEntries.add(classEntry);
97 }
98
99 // step 2: index field/method/constructor access
100 for (CtClass c : JarClassIterator.classes(jar)) {
101 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
102 for (CtField field : c.getDeclaredFields()) {
103 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
104 m_access.put(fieldEntry, Access.get(field));
105 m_fields.put(fieldEntry.getClassEntry(), fieldEntry);
106 }
107 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
108 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
109 m_access.put(behaviorEntry, Access.get(behavior));
110 m_behaviors.put(behaviorEntry.getClassEntry(), behaviorEntry);
111 }
112 }
113
114 // step 3: index extends, implements, fields, and methods
115 for (CtClass c : JarClassIterator.classes(jar)) {
116 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
117 m_translationIndex.indexClass(c);
118 String className = Descriptor.toJvmName(c.getName());
119 for (String interfaceName : c.getClassFile().getInterfaces()) {
120 className = Descriptor.toJvmName(className);
121 interfaceName = Descriptor.toJvmName(interfaceName);
122 if (className.equals(interfaceName)) {
123 throw new IllegalArgumentException("Class cannot be its own interface! " + className);
124 }
125 }
126 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
127 indexBehavior(behavior);
128 }
129 }
130
131 // step 4: index field, method, constructor references
132 for (CtClass c : JarClassIterator.classes(jar)) {
133 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
134 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
135 indexBehaviorReferences(behavior);
136 }
137 }
138
139 if (buildInnerClasses) {
140
141 // step 5: index inner classes and anonymous classes
142 for (CtClass c : JarClassIterator.classes(jar)) {
143 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
144 ClassEntry innerClassEntry = EntryFactory.getClassEntry(c);
145 ClassEntry outerClassEntry = findOuterClass(c);
146 if (outerClassEntry != null) {
147 m_innerClassesByOuter.put(outerClassEntry, innerClassEntry);
148 boolean innerWasAdded = m_outerClassesByInner.put(innerClassEntry, outerClassEntry) == null;
149 assert (innerWasAdded);
150
151 BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassEntry);
152 if (enclosingBehavior != null) {
153 m_anonymousClasses.put(innerClassEntry, enclosingBehavior);
154
155 // DEBUG
156 //System.out.println("ANONYMOUS: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
157 } else {
158 // DEBUG
159 //System.out.println("INNER: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
160 }
161 }
162 }
163
164 // step 6: update other indices with inner class info
165 Map<String,String> renames = Maps.newHashMap();
166 for (ClassEntry innerClassEntry : m_innerClassesByOuter.values()) {
167 String newName = innerClassEntry.buildClassEntry(getObfClassChain(innerClassEntry)).getName();
168 if (!innerClassEntry.getName().equals(newName)) {
169 // DEBUG
170 //System.out.println("REPLACE: " + innerClassEntry.getName() + " WITH " + newName);
171 renames.put(innerClassEntry.getName(), newName);
172 }
173 }
174 EntryRenamer.renameClassesInSet(renames, m_obfClassEntries);
175 m_translationIndex.renameClasses(renames);
176 EntryRenamer.renameClassesInMultimap(renames, m_methodImplementations);
177 EntryRenamer.renameClassesInMultimap(renames, m_behaviorReferences);
178 EntryRenamer.renameClassesInMultimap(renames, m_fieldReferences);
179 EntryRenamer.renameClassesInMap(renames, m_access);
180 }
181 }
182
183 private void indexBehavior(CtBehavior behavior) {
184 // get the behavior entry
185 final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
186 if (behaviorEntry instanceof MethodEntry) {
187 MethodEntry methodEntry = (MethodEntry)behaviorEntry;
188
189 // index implementation
190 m_methodImplementations.put(behaviorEntry.getClassName(), methodEntry);
191
192 // look for bridge and bridged methods
193 CtMethod bridgedMethod = getBridgedMethod((CtMethod)behavior);
194 if (bridgedMethod != null) {
195 m_bridgedMethods.put(methodEntry, EntryFactory.getMethodEntry(bridgedMethod));
196 }
197 }
198 // looks like we don't care about constructors here
199 }
200
201 private void indexBehaviorReferences(CtBehavior behavior) {
202 // index method calls
203 final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
204 try {
205 behavior.instrument(new ExprEditor() {
206 @Override
207 public void edit(MethodCall call) {
208 MethodEntry calledMethodEntry = EntryFactory.getMethodEntry(call);
209 ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledMethodEntry);
210 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) {
211 calledMethodEntry = new MethodEntry(
212 resolvedClassEntry,
213 calledMethodEntry.getName(),
214 calledMethodEntry.getSignature()
215 );
216 }
217 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
218 calledMethodEntry,
219 call.getMethodName(),
220 behaviorEntry
221 );
222 m_behaviorReferences.put(calledMethodEntry, reference);
223 }
224
225 @Override
226 public void edit(FieldAccess call) {
227 FieldEntry calledFieldEntry = EntryFactory.getFieldEntry(call);
228 ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledFieldEntry);
229 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) {
230 calledFieldEntry = new FieldEntry(calledFieldEntry, resolvedClassEntry);
231 }
232 EntryReference<FieldEntry,BehaviorEntry> reference = new EntryReference<FieldEntry,BehaviorEntry>(
233 calledFieldEntry,
234 call.getFieldName(),
235 behaviorEntry
236 );
237 m_fieldReferences.put(calledFieldEntry, reference);
238 }
239
240 @Override
241 public void edit(ConstructorCall call) {
242 ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
243 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
244 calledConstructorEntry,
245 call.getMethodName(),
246 behaviorEntry
247 );
248 m_behaviorReferences.put(calledConstructorEntry, reference);
249 }
250
251 @Override
252 public void edit(NewExpr call) {
253 ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
254 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
255 calledConstructorEntry,
256 call.getClassName(),
257 behaviorEntry
258 );
259 m_behaviorReferences.put(calledConstructorEntry, reference);
260 }
261 });
262 } catch (CannotCompileException ex) {
263 throw new Error(ex);
264 }
265 }
266
267 private CtMethod getBridgedMethod(CtMethod method) {
268
269 // bridge methods just call another method, cast it to the return type, and return the result
270 // let's see if we can detect this scenario
271
272 // skip non-synthetic methods
273 if ((method.getModifiers() & AccessFlag.SYNTHETIC) == 0) {
274 return null;
275 }
276
277 // get all the called methods
278 final List<MethodCall> methodCalls = Lists.newArrayList();
279 try {
280 method.instrument(new ExprEditor() {
281 @Override
282 public void edit(MethodCall call) {
283 methodCalls.add(call);
284 }
285 });
286 } catch (CannotCompileException ex) {
287 // this is stupid... we're not even compiling anything
288 throw new Error(ex);
289 }
290
291 // is there just one?
292 if (methodCalls.size() != 1) {
293 return null;
294 }
295 MethodCall call = methodCalls.get(0);
296
297 try {
298 // we have a bridge method!
299 return call.getMethod();
300 } catch (NotFoundException ex) {
301 // can't find the type? not a bridge method
302 return null;
303 }
304 }
305
306 private ClassEntry findOuterClass(CtClass c) {
307
308 ClassEntry classEntry = EntryFactory.getClassEntry(c);
309
310 // does this class already have an outer class?
311 if (classEntry.isInnerClass()) {
312 return classEntry.getOuterClassEntry();
313 }
314
315 // inner classes:
316 // have constructors that can (illegally) set synthetic fields
317 // the outer class is the only class that calls constructors
318
319 // use the synthetic fields to find the synthetic constructors
320 for (CtConstructor constructor : c.getDeclaredConstructors()) {
321 Set<String> syntheticFieldTypes = Sets.newHashSet();
322 if (!isIllegalConstructor(syntheticFieldTypes, constructor)) {
323 continue;
324 }
325
326 ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
327
328 // gather the classes from the illegally-set synthetic fields
329 Set<ClassEntry> illegallySetClasses = Sets.newHashSet();
330 for (String type : syntheticFieldTypes) {
331 if (type.startsWith("L")) {
332 ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1));
333 if (isSaneOuterClass(outerClassEntry, classEntry)) {
334 illegallySetClasses.add(outerClassEntry);
335 }
336 }
337 }
338
339 // who calls this constructor?
340 Set<ClassEntry> callerClasses = Sets.newHashSet();
341 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : getBehaviorReferences(constructorEntry)) {
342
343 // make sure it's not a call to super
344 if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) {
345
346 // is the entry a superclass of the context?
347 ClassEntry calledClassEntry = reference.entry.getClassEntry();
348 ClassEntry superclassEntry = m_translationIndex.getSuperclass(reference.context.getClassEntry());
349 if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) {
350 // it's a super call, skip
351 continue;
352 }
353 }
354
355 if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) {
356 callerClasses.add(reference.context.getClassEntry());
357 }
358 }
359
360 // do we have an answer yet?
361 if (callerClasses.isEmpty()) {
362 if (illegallySetClasses.size() == 1) {
363 return illegallySetClasses.iterator().next();
364 } else {
365 System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry));
366 }
367 } else {
368 if (callerClasses.size() == 1) {
369 return callerClasses.iterator().next();
370 } else {
371 // multiple callers, do the illegally set classes narrow it down?
372 Set<ClassEntry> intersection = Sets.newHashSet(callerClasses);
373 intersection.retainAll(illegallySetClasses);
374 if (intersection.size() == 1) {
375 return intersection.iterator().next();
376 } else {
377 System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses));
378 }
379 }
380 }
381 }
382
383 return null;
384 }
385
386 private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) {
387
388 // clearly this would be silly
389 if (outerClassEntry.equals(innerClassEntry)) {
390 return false;
391 }
392
393 // is the outer class in the jar?
394 if (!m_obfClassEntries.contains(outerClassEntry)) {
395 return false;
396 }
397
398 return true;
399 }
400
401 @SuppressWarnings("unchecked")
402 private boolean isIllegalConstructor(Set<String> syntheticFieldTypes, CtConstructor constructor) {
403
404 // illegal constructors only set synthetic member fields, then call super()
405 String className = constructor.getDeclaringClass().getName();
406
407 // collect all the field accesses, constructor calls, and method calls
408 final List<FieldAccess> illegalFieldWrites = Lists.newArrayList();
409 final List<ConstructorCall> constructorCalls = Lists.newArrayList();
410 try {
411 constructor.instrument(new ExprEditor() {
412 @Override
413 public void edit(FieldAccess fieldAccess) {
414 if (fieldAccess.isWriter() && constructorCalls.isEmpty()) {
415 illegalFieldWrites.add(fieldAccess);
416 }
417 }
418
419 @Override
420 public void edit(ConstructorCall constructorCall) {
421 constructorCalls.add(constructorCall);
422 }
423 });
424 } catch (CannotCompileException ex) {
425 // we're not compiling anything... this is stupid
426 throw new Error(ex);
427 }
428
429 // are there any illegal field writes?
430 if (illegalFieldWrites.isEmpty()) {
431 return false;
432 }
433
434 // are all the writes to synthetic fields?
435 for (FieldAccess fieldWrite : illegalFieldWrites) {
436
437 // all illegal writes have to be to the local class
438 if (!fieldWrite.getClassName().equals(className)) {
439 System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName()));
440 return false;
441 }
442
443 // find the field
444 FieldInfo fieldInfo = null;
445 for (FieldInfo info : (List<FieldInfo>)constructor.getDeclaringClass().getClassFile().getFields()) {
446 if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) {
447 fieldInfo = info;
448 break;
449 }
450 }
451 if (fieldInfo == null) {
452 // field is in a superclass or something, can't be a local synthetic member
453 return false;
454 }
455
456 // is this field synthetic?
457 boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0;
458 if (isSynthetic) {
459 syntheticFieldTypes.add(fieldInfo.getDescriptor());
460 } else {
461 System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName()));
462 return false;
463 }
464 }
465
466 // we passed all the tests!
467 return true;
468 }
469
470 private BehaviorEntry isAnonymousClass(CtClass c, ClassEntry outerClassEntry) {
471
472 // is this class already marked anonymous?
473 EnclosingMethodAttribute enclosingMethodAttribute = (EnclosingMethodAttribute)c.getClassFile().getAttribute(EnclosingMethodAttribute.tag);
474 if (enclosingMethodAttribute != null) {
475 if (enclosingMethodAttribute.methodIndex() > 0) {
476 return EntryFactory.getBehaviorEntry(
477 Descriptor.toJvmName(enclosingMethodAttribute.className()),
478 enclosingMethodAttribute.methodName(),
479 enclosingMethodAttribute.methodDescriptor()
480 );
481 } else {
482 // an attribute but no method? assume not anonymous
483 return null;
484 }
485 }
486
487 // if there's an inner class attribute, but not an enclosing method attribute, then it's not anonymous
488 InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
489 if (innerClassesAttribute != null) {
490 return null;
491 }
492
493 ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
494
495 // anonymous classes:
496 // can't be abstract
497 // have only one constructor
498 // it's called exactly once by the outer class
499 // the type the instance is assigned to can't be this type
500
501 // is abstract?
502 if (Modifier.isAbstract(c.getModifiers())) {
503 return null;
504 }
505
506 // is there exactly one constructor?
507 if (c.getDeclaredConstructors().length != 1) {
508 return null;
509 }
510 CtConstructor constructor = c.getDeclaredConstructors()[0];
511
512 // is this constructor called exactly once?
513 ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
514 Collection<EntryReference<BehaviorEntry,BehaviorEntry>> references = getBehaviorReferences(constructorEntry);
515 if (references.size() != 1) {
516 return null;
517 }
518
519 // does the caller use this type?
520 BehaviorEntry caller = references.iterator().next().context;
521 for (FieldEntry fieldEntry : getReferencedFields(caller)) {
522 if (fieldEntry.getType().hasClass() && fieldEntry.getType().getClassEntry().equals(innerClassEntry)) {
523 // caller references this type, so it can't be anonymous
524 return null;
525 }
526 }
527 for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) {
528 if (behaviorEntry.getSignature().hasClass(innerClassEntry)) {
529 return null;
530 }
531 }
532
533 return caller;
534 }
535
536 public Set<ClassEntry> getObfClassEntries() {
537 return m_obfClassEntries;
538 }
539
540 public Collection<FieldEntry> getObfFieldEntries() {
541 return m_fields.values();
542 }
543
544 public Collection<FieldEntry> getObfFieldEntries(ClassEntry classEntry) {
545 return m_fields.get(classEntry);
546 }
547
548 public Collection<BehaviorEntry> getObfBehaviorEntries() {
549 return m_behaviors.values();
550 }
551
552 public Collection<BehaviorEntry> getObfBehaviorEntries(ClassEntry classEntry) {
553 return m_behaviors.get(classEntry);
554 }
555
556 public TranslationIndex getTranslationIndex() {
557 return m_translationIndex;
558 }
559
560 public Access getAccess(Entry entry) {
561 return m_access.get(entry);
562 }
563
564 public ClassInheritanceTreeNode getClassInheritance(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
565
566 // get the root node
567 List<String> ancestry = Lists.newArrayList();
568 ancestry.add(obfClassEntry.getName());
569 for (ClassEntry classEntry : m_translationIndex.getAncestry(obfClassEntry)) {
570 if (containsObfClass(classEntry)) {
571 ancestry.add(classEntry.getName());
572 }
573 }
574 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(
575 deobfuscatingTranslator,
576 ancestry.get(ancestry.size() - 1)
577 );
578
579 // expand all children recursively
580 rootNode.load(m_translationIndex, true);
581
582 return rootNode;
583 }
584
585 public ClassImplementationsTreeNode getClassImplementations(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
586
587 // is this even an interface?
588 if (isInterface(obfClassEntry.getClassName())) {
589 ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(deobfuscatingTranslator, obfClassEntry);
590 node.load(this);
591 return node;
592 }
593 return null;
594 }
595
596 public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
597
598 // travel to the ancestor implementation
599 ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry();
600 for (ClassEntry ancestorClassEntry : m_translationIndex.getAncestry(obfMethodEntry.getClassEntry())) {
601 MethodEntry ancestorMethodEntry = new MethodEntry(
602 new ClassEntry(ancestorClassEntry),
603 obfMethodEntry.getName(),
604 obfMethodEntry.getSignature()
605 );
606 if (containsObfBehavior(ancestorMethodEntry)) {
607 baseImplementationClassEntry = ancestorClassEntry;
608 }
609 }
610
611 // make a root node at the base
612 MethodEntry methodEntry = new MethodEntry(
613 baseImplementationClassEntry,
614 obfMethodEntry.getName(),
615 obfMethodEntry.getSignature()
616 );
617 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode(
618 deobfuscatingTranslator,
619 methodEntry,
620 containsObfBehavior(methodEntry)
621 );
622
623 // expand the full tree
624 rootNode.load(this, true);
625
626 return rootNode;
627 }
628
629 public List<MethodImplementationsTreeNode> getMethodImplementations(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
630
631 List<MethodEntry> interfaceMethodEntries = Lists.newArrayList();
632
633 // is this method on an interface?
634 if (isInterface(obfMethodEntry.getClassName())) {
635 interfaceMethodEntries.add(obfMethodEntry);
636 } else {
637 // get the interface class
638 for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) {
639
640 // is this method defined in this interface?
641 MethodEntry methodInterface = new MethodEntry(
642 interfaceEntry,
643 obfMethodEntry.getName(),
644 obfMethodEntry.getSignature()
645 );
646 if (containsObfBehavior(methodInterface)) {
647 interfaceMethodEntries.add(methodInterface);
648 }
649 }
650 }
651
652 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
653 if (!interfaceMethodEntries.isEmpty()) {
654 for (MethodEntry interfaceMethodEntry : interfaceMethodEntries) {
655 MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(deobfuscatingTranslator, interfaceMethodEntry);
656 node.load(this);
657 nodes.add(node);
658 }
659 }
660 return nodes;
661 }
662
663 public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) {
664 Set<MethodEntry> methodEntries = Sets.newHashSet();
665 getRelatedMethodImplementations(methodEntries, getMethodInheritance(null, obfMethodEntry));
666 return methodEntries;
667 }
668
669 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) {
670 MethodEntry methodEntry = node.getMethodEntry();
671 if (containsObfBehavior(methodEntry)) {
672 // collect the entry
673 methodEntries.add(methodEntry);
674 }
675
676 // look at interface methods too
677 for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(null, methodEntry)) {
678 getRelatedMethodImplementations(methodEntries, implementationsNode);
679 }
680
681 // recurse
682 for (int i = 0; i < node.getChildCount(); i++) {
683 getRelatedMethodImplementations(methodEntries, (MethodInheritanceTreeNode)node.getChildAt(i));
684 }
685 }
686
687 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) {
688 MethodEntry methodEntry = node.getMethodEntry();
689 if (containsObfBehavior(methodEntry)) {
690 // collect the entry
691 methodEntries.add(methodEntry);
692 }
693
694 // recurse
695 for (int i = 0; i < node.getChildCount(); i++) {
696 getRelatedMethodImplementations(methodEntries, (MethodImplementationsTreeNode)node.getChildAt(i));
697 }
698 }
699
700 public Collection<EntryReference<FieldEntry,BehaviorEntry>> getFieldReferences(FieldEntry fieldEntry) {
701 return m_fieldReferences.get(fieldEntry);
702 }
703
704 public Collection<FieldEntry> getReferencedFields(BehaviorEntry behaviorEntry) {
705 // linear search is fast enough for now
706 Set<FieldEntry> fieldEntries = Sets.newHashSet();
707 for (EntryReference<FieldEntry,BehaviorEntry> reference : m_fieldReferences.values()) {
708 if (reference.context == behaviorEntry) {
709 fieldEntries.add(reference.entry);
710 }
711 }
712 return fieldEntries;
713 }
714
715 public Collection<EntryReference<BehaviorEntry,BehaviorEntry>> getBehaviorReferences(BehaviorEntry behaviorEntry) {
716 return m_behaviorReferences.get(behaviorEntry);
717 }
718
719 public Collection<BehaviorEntry> getReferencedBehaviors(BehaviorEntry behaviorEntry) {
720 // linear search is fast enough for now
721 Set<BehaviorEntry> behaviorEntries = Sets.newHashSet();
722 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : m_behaviorReferences.values()) {
723 if (reference.context == behaviorEntry) {
724 behaviorEntries.add(reference.entry);
725 }
726 }
727 return behaviorEntries;
728 }
729
730 public Collection<ClassEntry> getInnerClasses(ClassEntry obfOuterClassEntry) {
731 return m_innerClassesByOuter.get(obfOuterClassEntry);
732 }
733
734 public ClassEntry getOuterClass(ClassEntry obfInnerClassEntry) {
735 return m_outerClassesByInner.get(obfInnerClassEntry);
736 }
737
738 public boolean isAnonymousClass(ClassEntry obfInnerClassEntry) {
739 return m_anonymousClasses.containsKey(obfInnerClassEntry);
740 }
741
742 public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName) {
743 return m_anonymousClasses.get(obfInnerClassName);
744 }
745
746 public Set<ClassEntry> getInterfaces(String className) {
747 ClassEntry classEntry = new ClassEntry(className);
748 Set<ClassEntry> interfaces = new HashSet<ClassEntry>();
749 interfaces.addAll(m_translationIndex.getInterfaces(classEntry));
750 for (ClassEntry ancestor : m_translationIndex.getAncestry(classEntry)) {
751 interfaces.addAll(m_translationIndex.getInterfaces(ancestor));
752 }
753 return interfaces;
754 }
755
756 public Set<String> getImplementingClasses(String targetInterfaceName) {
757
758 // linear search is fast enough for now
759 Set<String> classNames = Sets.newHashSet();
760 for (Map.Entry<ClassEntry,ClassEntry> entry : m_translationIndex.getClassInterfaces()) {
761 ClassEntry classEntry = entry.getKey();
762 ClassEntry interfaceEntry = entry.getValue();
763 if (interfaceEntry.getName().equals(targetInterfaceName)) {
764 classNames.add(classEntry.getClassName());
765 m_translationIndex.getSubclassNamesRecursively(classNames, classEntry);
766 }
767 }
768 return classNames;
769 }
770
771 public boolean isInterface(String className) {
772 return m_translationIndex.isInterface(new ClassEntry(className));
773 }
774
775 public boolean containsObfClass(ClassEntry obfClassEntry) {
776 return m_obfClassEntries.contains(obfClassEntry);
777 }
778
779 public boolean containsObfField(FieldEntry obfFieldEntry) {
780 return m_access.containsKey(obfFieldEntry);
781 }
782
783 public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) {
784 return m_access.containsKey(obfBehaviorEntry);
785 }
786
787 public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) {
788 // check the behavior
789 if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) {
790 return false;
791 }
792
793 // check the argument
794 if (obfArgumentEntry.getIndex() >= obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size()) {
795 return false;
796 }
797
798 return true;
799 }
800
801 public boolean containsObfEntry(Entry obfEntry) {
802 if (obfEntry instanceof ClassEntry) {
803 return containsObfClass((ClassEntry)obfEntry);
804 } else if (obfEntry instanceof FieldEntry) {
805 return containsObfField((FieldEntry)obfEntry);
806 } else if (obfEntry instanceof BehaviorEntry) {
807 return containsObfBehavior((BehaviorEntry)obfEntry);
808 } else if (obfEntry instanceof ArgumentEntry) {
809 return containsObfArgument((ArgumentEntry)obfEntry);
810 } else {
811 throw new Error("Entry type not supported: " + obfEntry.getClass().getName());
812 }
813 }
814
815 public MethodEntry getBridgedMethod(MethodEntry bridgeMethodEntry) {
816 return m_bridgedMethods.get(bridgeMethodEntry);
817 }
818
819 public List<ClassEntry> getObfClassChain(ClassEntry obfClassEntry) {
820
821 // build class chain in inner-to-outer order
822 List<ClassEntry> obfClassChain = Lists.newArrayList(obfClassEntry);
823 ClassEntry checkClassEntry = obfClassEntry;
824 while (true) {
825 ClassEntry obfOuterClassEntry = getOuterClass(checkClassEntry);
826 if (obfOuterClassEntry != null) {
827 obfClassChain.add(obfOuterClassEntry);
828 checkClassEntry = obfOuterClassEntry;
829 } else {
830 break;
831 }
832 }
833
834 // switch to outer-to-inner order
835 Collections.reverse(obfClassChain);
836
837 return obfClassChain;
838 }
839}
diff --git a/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java
deleted file mode 100644
index aa0aeca6..00000000
--- a/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java
+++ /dev/null
@@ -1,101 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.List;
14
15import javax.swing.tree.DefaultMutableTreeNode;
16
17import com.google.common.collect.Lists;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.MethodEntry;
21import cuchaz.enigma.mapping.Translator;
22
23public class MethodImplementationsTreeNode extends DefaultMutableTreeNode {
24
25 private static final long serialVersionUID = 3781080657461899915L;
26
27 private Translator m_deobfuscatingTranslator;
28 private MethodEntry m_entry;
29
30 public MethodImplementationsTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) {
31 if (entry == null) {
32 throw new IllegalArgumentException("entry cannot be null!");
33 }
34
35 m_deobfuscatingTranslator = deobfuscatingTranslator;
36 m_entry = entry;
37 }
38
39 public MethodEntry getMethodEntry() {
40 return m_entry;
41 }
42
43 public String getDeobfClassName() {
44 return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
45 }
46
47 public String getDeobfMethodName() {
48 return m_deobfuscatingTranslator.translate(m_entry);
49 }
50
51 @Override
52 public String toString() {
53 String className = getDeobfClassName();
54 if (className == null) {
55 className = m_entry.getClassName();
56 }
57
58 String methodName = getDeobfMethodName();
59 if (methodName == null) {
60 methodName = m_entry.getName();
61 }
62 return className + "." + methodName + "()";
63 }
64
65 public void load(JarIndex index) {
66
67 // get all method implementations
68 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
69 for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) {
70 MethodEntry methodEntry = new MethodEntry(
71 new ClassEntry(implementingClassName),
72 m_entry.getName(),
73 m_entry.getSignature()
74 );
75 if (index.containsObfBehavior(methodEntry)) {
76 nodes.add(new MethodImplementationsTreeNode(m_deobfuscatingTranslator, methodEntry));
77 }
78 }
79
80 // add them to this node
81 for (MethodImplementationsTreeNode node : nodes) {
82 this.add(node);
83 }
84 }
85
86 public static MethodImplementationsTreeNode findNode(MethodImplementationsTreeNode node, MethodEntry entry) {
87 // is this the node?
88 if (node.getMethodEntry().equals(entry)) {
89 return node;
90 }
91
92 // recurse
93 for (int i = 0; i < node.getChildCount(); i++) {
94 MethodImplementationsTreeNode foundNode = findNode((MethodImplementationsTreeNode)node.getChildAt(i), entry);
95 if (foundNode != null) {
96 return foundNode;
97 }
98 }
99 return null;
100 }
101}
diff --git a/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java
deleted file mode 100644
index 0da3c8c9..00000000
--- a/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java
+++ /dev/null
@@ -1,114 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.List;
14
15import javax.swing.tree.DefaultMutableTreeNode;
16
17import com.google.common.collect.Lists;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.MethodEntry;
21import cuchaz.enigma.mapping.Translator;
22
23public class MethodInheritanceTreeNode extends DefaultMutableTreeNode {
24
25 private static final long serialVersionUID = 1096677030991810007L;
26
27 private Translator m_deobfuscatingTranslator;
28 private MethodEntry m_entry;
29 private boolean m_isImplemented;
30
31 public MethodInheritanceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry, boolean isImplemented) {
32 m_deobfuscatingTranslator = deobfuscatingTranslator;
33 m_entry = entry;
34 m_isImplemented = isImplemented;
35 }
36
37 public MethodEntry getMethodEntry() {
38 return m_entry;
39 }
40
41 public String getDeobfClassName() {
42 return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
43 }
44
45 public String getDeobfMethodName() {
46 return m_deobfuscatingTranslator.translate(m_entry);
47 }
48
49 public boolean isImplemented() {
50 return m_isImplemented;
51 }
52
53 @Override
54 public String toString() {
55 String className = getDeobfClassName();
56 if (className == null) {
57 className = m_entry.getClassName();
58 }
59
60 if (!m_isImplemented) {
61 return className;
62 } else {
63 String methodName = getDeobfMethodName();
64 if (methodName == null) {
65 methodName = m_entry.getName();
66 }
67 return className + "." + methodName + "()";
68 }
69 }
70
71 public void load(JarIndex index, boolean recurse) {
72 // get all the child nodes
73 List<MethodInheritanceTreeNode> nodes = Lists.newArrayList();
74 for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(m_entry.getClassEntry())) {
75 MethodEntry methodEntry = new MethodEntry(
76 subclassEntry,
77 m_entry.getName(),
78 m_entry.getSignature()
79 );
80 nodes.add(new MethodInheritanceTreeNode(
81 m_deobfuscatingTranslator,
82 methodEntry,
83 index.containsObfBehavior(methodEntry)
84 ));
85 }
86
87 // add them to this node
88 for (MethodInheritanceTreeNode node : nodes) {
89 this.add(node);
90 }
91
92 if (recurse) {
93 for (MethodInheritanceTreeNode node : nodes) {
94 node.load(index, true);
95 }
96 }
97 }
98
99 public static MethodInheritanceTreeNode findNode(MethodInheritanceTreeNode node, MethodEntry entry) {
100 // is this the node?
101 if (node.getMethodEntry().equals(entry)) {
102 return node;
103 }
104
105 // recurse
106 for (int i = 0; i < node.getChildCount(); i++) {
107 MethodInheritanceTreeNode foundNode = findNode((MethodInheritanceTreeNode)node.getChildAt(i), entry);
108 if (foundNode != null) {
109 return foundNode;
110 }
111 }
112 return null;
113 }
114}
diff --git a/src/cuchaz/enigma/analysis/RelatedMethodChecker.java b/src/cuchaz/enigma/analysis/RelatedMethodChecker.java
deleted file mode 100644
index e592a1c3..00000000
--- a/src/cuchaz/enigma/analysis/RelatedMethodChecker.java
+++ /dev/null
@@ -1,106 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.Map;
14import java.util.Set;
15
16import com.google.common.collect.Maps;
17import com.google.common.collect.Sets;
18
19import cuchaz.enigma.mapping.BehaviorEntry;
20import cuchaz.enigma.mapping.ClassEntry;
21import cuchaz.enigma.mapping.EntryFactory;
22import cuchaz.enigma.mapping.MethodEntry;
23import cuchaz.enigma.mapping.MethodMapping;
24
25public class RelatedMethodChecker {
26
27 private JarIndex m_jarIndex;
28 private Map<Set<MethodEntry>,String> m_deobfNamesByGroup;
29 private Map<MethodEntry,String> m_deobfNamesByObfMethod;
30 private Map<MethodEntry,Set<MethodEntry>> m_groupsByObfMethod;
31 private Set<Set<MethodEntry>> m_inconsistentGroups;
32
33 public RelatedMethodChecker(JarIndex jarIndex) {
34 m_jarIndex = jarIndex;
35 m_deobfNamesByGroup = Maps.newHashMap();
36 m_deobfNamesByObfMethod = Maps.newHashMap();
37 m_groupsByObfMethod = Maps.newHashMap();
38 m_inconsistentGroups = Sets.newHashSet();
39 }
40
41 public void checkMethod(ClassEntry classEntry, MethodMapping methodMapping) {
42
43 // TEMP: disable the expensive check for now, maybe we can optimize it later, or just use it for debugging
44 if (true) return;
45
46 BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping);
47 if (!(obfBehaviorEntry instanceof MethodEntry)) {
48 // only methods have related implementations
49 return;
50 }
51 MethodEntry obfMethodEntry = (MethodEntry)obfBehaviorEntry;
52 String deobfName = methodMapping.getDeobfName();
53 m_deobfNamesByObfMethod.put(obfMethodEntry, deobfName);
54
55 // have we seen this method's group before?
56 Set<MethodEntry> group = m_groupsByObfMethod.get(obfMethodEntry);
57 if (group == null) {
58
59 // no, compute the group and save the name
60 group = m_jarIndex.getRelatedMethodImplementations(obfMethodEntry);
61 m_deobfNamesByGroup.put(group, deobfName);
62
63 assert(group.contains(obfMethodEntry));
64 for (MethodEntry relatedMethodEntry : group) {
65 m_groupsByObfMethod.put(relatedMethodEntry, group);
66 }
67 }
68
69 // check the name
70 if (!sameName(m_deobfNamesByGroup.get(group), deobfName)) {
71 m_inconsistentGroups.add(group);
72 }
73 }
74
75 private boolean sameName(String a, String b) {
76 if (a == null && b == null) {
77 return true;
78 } else if (a != null && b != null) {
79 return a.equals(b);
80 }
81 return false;
82 }
83
84 public boolean hasProblems() {
85 return m_inconsistentGroups.size() > 0;
86 }
87
88 public String getReport() {
89 StringBuilder buf = new StringBuilder();
90 buf.append(m_inconsistentGroups.size());
91 buf.append(" groups of methods related by inheritance and/or interfaces have different deobf names!\n");
92 for (Set<MethodEntry> group : m_inconsistentGroups) {
93 buf.append("\tGroup with ");
94 buf.append(group.size());
95 buf.append(" methods:\n");
96 for (MethodEntry methodEntry : group) {
97 buf.append("\t\t");
98 buf.append(methodEntry.toString());
99 buf.append(" => ");
100 buf.append(m_deobfNamesByObfMethod.get(methodEntry));
101 buf.append("\n");
102 }
103 }
104 return buf.toString();
105 }
106}
diff --git a/src/cuchaz/enigma/analysis/SourceIndex.java b/src/cuchaz/enigma/analysis/SourceIndex.java
deleted file mode 100644
index 3c4ac464..00000000
--- a/src/cuchaz/enigma/analysis/SourceIndex.java
+++ /dev/null
@@ -1,184 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.Collection;
14import java.util.List;
15import java.util.Map;
16import java.util.TreeMap;
17
18import com.google.common.collect.HashMultimap;
19import com.google.common.collect.Lists;
20import com.google.common.collect.Maps;
21import com.google.common.collect.Multimap;
22import com.strobel.decompiler.languages.Region;
23import com.strobel.decompiler.languages.java.ast.AstNode;
24import com.strobel.decompiler.languages.java.ast.Identifier;
25
26import cuchaz.enigma.mapping.Entry;
27
28public class SourceIndex {
29
30 private String m_source;
31 private TreeMap<Token,EntryReference<Entry,Entry>> m_tokenToReference;
32 private Multimap<EntryReference<Entry,Entry>,Token> m_referenceToTokens;
33 private Map<Entry,Token> m_declarationToToken;
34 private List<Integer> m_lineOffsets;
35 private boolean m_ignoreBadTokens;
36
37 public SourceIndex(String source) {
38 this(source, true);
39 }
40
41 public SourceIndex(String source, boolean ignoreBadTokens) {
42 m_source = source;
43 m_ignoreBadTokens = ignoreBadTokens;
44 m_tokenToReference = Maps.newTreeMap();
45 m_referenceToTokens = HashMultimap.create();
46 m_declarationToToken = Maps.newHashMap();
47 m_lineOffsets = Lists.newArrayList();
48
49 // count the lines
50 m_lineOffsets.add(0);
51 for (int i = 0; i < source.length(); i++) {
52 if (source.charAt(i) == '\n') {
53 m_lineOffsets.add(i + 1);
54 }
55 }
56 }
57
58 public String getSource() {
59 return m_source;
60 }
61
62 public Token getToken(AstNode node) {
63
64 // get the text of the node
65 String name = "";
66 if (node instanceof Identifier) {
67 name = ((Identifier)node).getName();
68 }
69
70 // get a token for this node's region
71 Region region = node.getRegion();
72 if (region.getBeginLine() == 0 || region.getEndLine() == 0) {
73 // DEBUG
74 System.err.println(String.format("WARNING: %s \"%s\" has invalid region: %s", node.getNodeType(), name, region));
75 return null;
76 }
77 Token token = new Token(
78 toPos(region.getBeginLine(), region.getBeginColumn()),
79 toPos(region.getEndLine(), region.getEndColumn()),
80 m_source
81 );
82 if (token.start == 0) {
83 // DEBUG
84 System.err.println(String.format("WARNING: %s \"%s\" has invalid start: %s", node.getNodeType(), name, region));
85 return null;
86 }
87
88 // DEBUG
89 // System.out.println( String.format( "%s \"%s\" region: %s", node.getNodeType(), name, region ) );
90
91 // if the token has a $ in it, something's wrong. Ignore this token
92 if (name.lastIndexOf('$') >= 0 && m_ignoreBadTokens) {
93 // DEBUG
94 System.err.println(String.format("WARNING: %s \"%s\" is probably a bad token. It was ignored", node.getNodeType(), name));
95 return null;
96 }
97
98 return token;
99 }
100
101 public void addReference(AstNode node, Entry deobfEntry, Entry deobfContext) {
102 Token token = getToken(node);
103 if (token != null) {
104 EntryReference<Entry,Entry> deobfReference = new EntryReference<Entry,Entry>(deobfEntry, token.text, deobfContext);
105 m_tokenToReference.put(token, deobfReference);
106 m_referenceToTokens.put(deobfReference, token);
107 }
108 }
109
110 public void addDeclaration(AstNode node, Entry deobfEntry) {
111 Token token = getToken(node);
112 if (token != null) {
113 EntryReference<Entry,Entry> reference = new EntryReference<Entry,Entry>(deobfEntry, token.text);
114 m_tokenToReference.put(token, reference);
115 m_referenceToTokens.put(reference, token);
116 m_declarationToToken.put(deobfEntry, token);
117 }
118 }
119
120 public Token getReferenceToken(int pos) {
121 Token token = m_tokenToReference.floorKey(new Token(pos, pos, null));
122 if (token != null && token.contains(pos)) {
123 return token;
124 }
125 return null;
126 }
127
128 public Collection<Token> getReferenceTokens(EntryReference<Entry,Entry> deobfReference) {
129 return m_referenceToTokens.get(deobfReference);
130 }
131
132 public EntryReference<Entry,Entry> getDeobfReference(Token token) {
133 if (token == null) {
134 return null;
135 }
136 return m_tokenToReference.get(token);
137 }
138
139 public void replaceDeobfReference(Token token, EntryReference<Entry,Entry> newDeobfReference) {
140 EntryReference<Entry,Entry> oldDeobfReference = m_tokenToReference.get(token);
141 m_tokenToReference.put(token, newDeobfReference);
142 Collection<Token> tokens = m_referenceToTokens.get(oldDeobfReference);
143 m_referenceToTokens.removeAll(oldDeobfReference);
144 m_referenceToTokens.putAll(newDeobfReference, tokens);
145 }
146
147 public Iterable<Token> referenceTokens() {
148 return m_tokenToReference.keySet();
149 }
150
151 public Iterable<Token> declarationTokens() {
152 return m_declarationToToken.values();
153 }
154
155 public Iterable<Entry> declarations() {
156 return m_declarationToToken.keySet();
157 }
158
159 public Token getDeclarationToken(Entry deobfEntry) {
160 return m_declarationToToken.get(deobfEntry);
161 }
162
163 public int getLineNumber(int pos) {
164 // line number is 1-based
165 int line = 0;
166 for (Integer offset : m_lineOffsets) {
167 if (offset > pos) {
168 break;
169 }
170 line++;
171 }
172 return line;
173 }
174
175 public int getColumnNumber(int pos) {
176 // column number is 1-based
177 return pos - m_lineOffsets.get(getLineNumber(pos) - 1) + 1;
178 }
179
180 private int toPos(int line, int col) {
181 // line and col are 1-based
182 return m_lineOffsets.get(line - 1) + col - 1;
183 }
184}
diff --git a/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java
deleted file mode 100644
index a660a376..00000000
--- a/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java
+++ /dev/null
@@ -1,150 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.assembler.metadata.MemberReference;
14import com.strobel.assembler.metadata.MethodDefinition;
15import com.strobel.assembler.metadata.MethodReference;
16import com.strobel.assembler.metadata.ParameterDefinition;
17import com.strobel.assembler.metadata.TypeReference;
18import com.strobel.decompiler.languages.TextLocation;
19import com.strobel.decompiler.languages.java.ast.AstNode;
20import com.strobel.decompiler.languages.java.ast.IdentifierExpression;
21import com.strobel.decompiler.languages.java.ast.InvocationExpression;
22import com.strobel.decompiler.languages.java.ast.Keys;
23import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
24import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression;
25import com.strobel.decompiler.languages.java.ast.ParameterDeclaration;
26import com.strobel.decompiler.languages.java.ast.SimpleType;
27import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression;
28import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression;
29
30import cuchaz.enigma.mapping.ArgumentEntry;
31import cuchaz.enigma.mapping.BehaviorEntry;
32import cuchaz.enigma.mapping.ClassEntry;
33import cuchaz.enigma.mapping.ConstructorEntry;
34import cuchaz.enigma.mapping.FieldEntry;
35import cuchaz.enigma.mapping.MethodEntry;
36import cuchaz.enigma.mapping.ProcyonEntryFactory;
37import cuchaz.enigma.mapping.Signature;
38import cuchaz.enigma.mapping.Type;
39
40public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
41
42 private BehaviorEntry m_behaviorEntry;
43
44 public SourceIndexBehaviorVisitor(BehaviorEntry behaviorEntry) {
45 m_behaviorEntry = behaviorEntry;
46 }
47
48 @Override
49 public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) {
50 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
51
52 // get the behavior entry
53 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
54 BehaviorEntry behaviorEntry = null;
55 if (ref instanceof MethodReference) {
56 MethodReference methodRef = (MethodReference)ref;
57 if (methodRef.isConstructor()) {
58 behaviorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature()));
59 } else if (methodRef.isTypeInitializer()) {
60 behaviorEntry = new ConstructorEntry(classEntry);
61 } else {
62 behaviorEntry = new MethodEntry(classEntry, ref.getName(), new Signature(ref.getErasedSignature()));
63 }
64 }
65 if (behaviorEntry != null) {
66 // get the node for the token
67 AstNode tokenNode = null;
68 if (node.getTarget() instanceof MemberReferenceExpression) {
69 tokenNode = ((MemberReferenceExpression)node.getTarget()).getMemberNameToken();
70 } else if (node.getTarget() instanceof SuperReferenceExpression) {
71 tokenNode = node.getTarget();
72 } else if (node.getTarget() instanceof ThisReferenceExpression) {
73 tokenNode = node.getTarget();
74 }
75 if (tokenNode != null) {
76 index.addReference(tokenNode, behaviorEntry, m_behaviorEntry);
77 }
78 }
79
80 return recurse(node, index);
81 }
82
83 @Override
84 public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) {
85 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
86 if (ref != null) {
87 // make sure this is actually a field
88 if (ref.getErasedSignature().indexOf('(') >= 0) {
89 throw new Error("Expected a field here! got " + ref);
90 }
91
92 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
93 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature()));
94 index.addReference(node.getMemberNameToken(), fieldEntry, m_behaviorEntry);
95 }
96
97 return recurse(node, index);
98 }
99
100 @Override
101 public Void visitSimpleType(SimpleType node, SourceIndex index) {
102 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
103 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
104 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
105 index.addReference(node.getIdentifierToken(), classEntry, m_behaviorEntry);
106 }
107
108 return recurse(node, index);
109 }
110
111 @Override
112 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
113 ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION);
114 if (def.getMethod() instanceof MethodDefinition) {
115 MethodDefinition methodDef = (MethodDefinition)def.getMethod();
116 BehaviorEntry behaviorEntry = ProcyonEntryFactory.getBehaviorEntry(methodDef);
117 ArgumentEntry argumentEntry = new ArgumentEntry(behaviorEntry, def.getPosition(), node.getName());
118 index.addDeclaration(node.getNameToken(), argumentEntry);
119 }
120
121 return recurse(node, index);
122 }
123
124 @Override
125 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
126 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
127 if (ref != null) {
128 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
129 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature()));
130 index.addReference(node.getIdentifierToken(), fieldEntry, m_behaviorEntry);
131 }
132
133 return recurse(node, index);
134 }
135
136 @Override
137 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
138 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
139 if (ref != null) {
140 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
141 ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature()));
142 if (node.getType() instanceof SimpleType) {
143 SimpleType simpleTypeNode = (SimpleType)node.getType();
144 index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, m_behaviorEntry);
145 }
146 }
147
148 return recurse(node, index);
149 }
150}
diff --git a/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
deleted file mode 100644
index db0bc0b7..00000000
--- a/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
+++ /dev/null
@@ -1,112 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.assembler.metadata.FieldDefinition;
14import com.strobel.assembler.metadata.MethodDefinition;
15import com.strobel.assembler.metadata.TypeDefinition;
16import com.strobel.assembler.metadata.TypeReference;
17import com.strobel.decompiler.languages.TextLocation;
18import com.strobel.decompiler.languages.java.ast.AstNode;
19import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration;
20import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration;
21import com.strobel.decompiler.languages.java.ast.FieldDeclaration;
22import com.strobel.decompiler.languages.java.ast.Keys;
23import com.strobel.decompiler.languages.java.ast.MethodDeclaration;
24import com.strobel.decompiler.languages.java.ast.SimpleType;
25import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
26import com.strobel.decompiler.languages.java.ast.VariableInitializer;
27
28import cuchaz.enigma.mapping.BehaviorEntry;
29import cuchaz.enigma.mapping.ClassEntry;
30import cuchaz.enigma.mapping.ConstructorEntry;
31import cuchaz.enigma.mapping.FieldEntry;
32import cuchaz.enigma.mapping.ProcyonEntryFactory;
33
34public class SourceIndexClassVisitor extends SourceIndexVisitor {
35
36 private ClassEntry m_classEntry;
37
38 public SourceIndexClassVisitor(ClassEntry classEntry) {
39 m_classEntry = classEntry;
40 }
41
42 @Override
43 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
44 // is this this class, or a subtype?
45 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
46 ClassEntry classEntry = new ClassEntry(def.getInternalName());
47 if (!classEntry.equals(m_classEntry)) {
48 // it's a sub-type, recurse
49 index.addDeclaration(node.getNameToken(), classEntry);
50 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
51 }
52
53 return recurse(node, index);
54 }
55
56 @Override
57 public Void visitSimpleType(SimpleType node, SourceIndex index) {
58 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
59 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
60 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
61 index.addReference(node.getIdentifierToken(), classEntry, m_classEntry);
62 }
63
64 return recurse(node, index);
65 }
66
67 @Override
68 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
69 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
70 BehaviorEntry behaviorEntry = ProcyonEntryFactory.getBehaviorEntry(def);
71 AstNode tokenNode = node.getNameToken();
72
73 if (behaviorEntry instanceof ConstructorEntry) {
74 ConstructorEntry constructorEntry = (ConstructorEntry)behaviorEntry;
75 if (constructorEntry.isStatic()) {
76 // for static initializers, check elsewhere for the token node
77 tokenNode = node.getModifiers().firstOrNullObject();
78 }
79 }
80 index.addDeclaration(tokenNode, behaviorEntry);
81 return node.acceptVisitor(new SourceIndexBehaviorVisitor(behaviorEntry), index);
82 }
83
84 @Override
85 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
86 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
87 ConstructorEntry constructorEntry = ProcyonEntryFactory.getConstructorEntry(def);
88 index.addDeclaration(node.getNameToken(), constructorEntry);
89 return node.acceptVisitor(new SourceIndexBehaviorVisitor(constructorEntry), index);
90 }
91
92 @Override
93 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
94 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
95 FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def);
96 assert (node.getVariables().size() == 1);
97 VariableInitializer variable = node.getVariables().firstOrNullObject();
98 index.addDeclaration(variable.getNameToken(), fieldEntry);
99
100 return recurse(node, index);
101 }
102
103 @Override
104 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
105 // treat enum declarations as field declarations
106 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
107 FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def);
108 index.addDeclaration(node.getNameToken(), fieldEntry);
109
110 return recurse(node, index);
111 }
112}
diff --git a/src/cuchaz/enigma/analysis/SourceIndexVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexVisitor.java
deleted file mode 100644
index 08698267..00000000
--- a/src/cuchaz/enigma/analysis/SourceIndexVisitor.java
+++ /dev/null
@@ -1,452 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.assembler.metadata.TypeDefinition;
14import com.strobel.decompiler.languages.java.ast.Annotation;
15import com.strobel.decompiler.languages.java.ast.AnonymousObjectCreationExpression;
16import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression;
17import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression;
18import com.strobel.decompiler.languages.java.ast.ArraySpecifier;
19import com.strobel.decompiler.languages.java.ast.AssertStatement;
20import com.strobel.decompiler.languages.java.ast.AssignmentExpression;
21import com.strobel.decompiler.languages.java.ast.AstNode;
22import com.strobel.decompiler.languages.java.ast.BinaryOperatorExpression;
23import com.strobel.decompiler.languages.java.ast.BlockStatement;
24import com.strobel.decompiler.languages.java.ast.BreakStatement;
25import com.strobel.decompiler.languages.java.ast.CaseLabel;
26import com.strobel.decompiler.languages.java.ast.CastExpression;
27import com.strobel.decompiler.languages.java.ast.CatchClause;
28import com.strobel.decompiler.languages.java.ast.ClassOfExpression;
29import com.strobel.decompiler.languages.java.ast.Comment;
30import com.strobel.decompiler.languages.java.ast.CompilationUnit;
31import com.strobel.decompiler.languages.java.ast.ComposedType;
32import com.strobel.decompiler.languages.java.ast.ConditionalExpression;
33import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration;
34import com.strobel.decompiler.languages.java.ast.ContinueStatement;
35import com.strobel.decompiler.languages.java.ast.DoWhileStatement;
36import com.strobel.decompiler.languages.java.ast.EmptyStatement;
37import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration;
38import com.strobel.decompiler.languages.java.ast.ExpressionStatement;
39import com.strobel.decompiler.languages.java.ast.FieldDeclaration;
40import com.strobel.decompiler.languages.java.ast.ForEachStatement;
41import com.strobel.decompiler.languages.java.ast.ForStatement;
42import com.strobel.decompiler.languages.java.ast.GotoStatement;
43import com.strobel.decompiler.languages.java.ast.IAstVisitor;
44import com.strobel.decompiler.languages.java.ast.Identifier;
45import com.strobel.decompiler.languages.java.ast.IdentifierExpression;
46import com.strobel.decompiler.languages.java.ast.IfElseStatement;
47import com.strobel.decompiler.languages.java.ast.ImportDeclaration;
48import com.strobel.decompiler.languages.java.ast.IndexerExpression;
49import com.strobel.decompiler.languages.java.ast.InstanceInitializer;
50import com.strobel.decompiler.languages.java.ast.InstanceOfExpression;
51import com.strobel.decompiler.languages.java.ast.InvocationExpression;
52import com.strobel.decompiler.languages.java.ast.JavaTokenNode;
53import com.strobel.decompiler.languages.java.ast.Keys;
54import com.strobel.decompiler.languages.java.ast.LabelStatement;
55import com.strobel.decompiler.languages.java.ast.LabeledStatement;
56import com.strobel.decompiler.languages.java.ast.LambdaExpression;
57import com.strobel.decompiler.languages.java.ast.LocalTypeDeclarationStatement;
58import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
59import com.strobel.decompiler.languages.java.ast.MethodDeclaration;
60import com.strobel.decompiler.languages.java.ast.MethodGroupExpression;
61import com.strobel.decompiler.languages.java.ast.NewLineNode;
62import com.strobel.decompiler.languages.java.ast.NullReferenceExpression;
63import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression;
64import com.strobel.decompiler.languages.java.ast.PackageDeclaration;
65import com.strobel.decompiler.languages.java.ast.ParameterDeclaration;
66import com.strobel.decompiler.languages.java.ast.ParenthesizedExpression;
67import com.strobel.decompiler.languages.java.ast.PrimitiveExpression;
68import com.strobel.decompiler.languages.java.ast.ReturnStatement;
69import com.strobel.decompiler.languages.java.ast.SimpleType;
70import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression;
71import com.strobel.decompiler.languages.java.ast.SwitchSection;
72import com.strobel.decompiler.languages.java.ast.SwitchStatement;
73import com.strobel.decompiler.languages.java.ast.SynchronizedStatement;
74import com.strobel.decompiler.languages.java.ast.TextNode;
75import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression;
76import com.strobel.decompiler.languages.java.ast.ThrowStatement;
77import com.strobel.decompiler.languages.java.ast.TryCatchStatement;
78import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
79import com.strobel.decompiler.languages.java.ast.TypeParameterDeclaration;
80import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression;
81import com.strobel.decompiler.languages.java.ast.UnaryOperatorExpression;
82import com.strobel.decompiler.languages.java.ast.VariableDeclarationStatement;
83import com.strobel.decompiler.languages.java.ast.VariableInitializer;
84import com.strobel.decompiler.languages.java.ast.WhileStatement;
85import com.strobel.decompiler.languages.java.ast.WildcardType;
86import com.strobel.decompiler.patterns.Pattern;
87
88import cuchaz.enigma.mapping.ClassEntry;
89
90public class SourceIndexVisitor implements IAstVisitor<SourceIndex,Void> {
91
92 @Override
93 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
94 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
95 ClassEntry classEntry = new ClassEntry(def.getInternalName());
96 index.addDeclaration(node.getNameToken(), classEntry);
97
98 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
99 }
100
101 protected Void recurse(AstNode node, SourceIndex index) {
102 for (final AstNode child : node.getChildren()) {
103 child.acceptVisitor(this, index);
104 }
105 return null;
106 }
107
108 @Override
109 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
110 return recurse(node, index);
111 }
112
113 @Override
114 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
115 return recurse(node, index);
116 }
117
118 @Override
119 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
120 return recurse(node, index);
121 }
122
123 @Override
124 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
125 return recurse(node, index);
126 }
127
128 @Override
129 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
130 return recurse(node, index);
131 }
132
133 @Override
134 public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) {
135 return recurse(node, index);
136 }
137
138 @Override
139 public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) {
140 return recurse(node, index);
141 }
142
143 @Override
144 public Void visitSimpleType(SimpleType node, SourceIndex index) {
145 return recurse(node, index);
146 }
147
148 @Override
149 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
150 return recurse(node, index);
151 }
152
153 @Override
154 public Void visitComment(Comment node, SourceIndex index) {
155 return recurse(node, index);
156 }
157
158 @Override
159 public Void visitPatternPlaceholder(AstNode node, Pattern pattern, SourceIndex index) {
160 return recurse(node, index);
161 }
162
163 @Override
164 public Void visitTypeReference(TypeReferenceExpression node, SourceIndex index) {
165 return recurse(node, index);
166 }
167
168 @Override
169 public Void visitJavaTokenNode(JavaTokenNode node, SourceIndex index) {
170 return recurse(node, index);
171 }
172
173 @Override
174 public Void visitIdentifier(Identifier node, SourceIndex index) {
175 return recurse(node, index);
176 }
177
178 @Override
179 public Void visitNullReferenceExpression(NullReferenceExpression node, SourceIndex index) {
180 return recurse(node, index);
181 }
182
183 @Override
184 public Void visitThisReferenceExpression(ThisReferenceExpression node, SourceIndex index) {
185 return recurse(node, index);
186 }
187
188 @Override
189 public Void visitSuperReferenceExpression(SuperReferenceExpression node, SourceIndex index) {
190 return recurse(node, index);
191 }
192
193 @Override
194 public Void visitClassOfExpression(ClassOfExpression node, SourceIndex index) {
195 return recurse(node, index);
196 }
197
198 @Override
199 public Void visitBlockStatement(BlockStatement node, SourceIndex index) {
200 return recurse(node, index);
201 }
202
203 @Override
204 public Void visitExpressionStatement(ExpressionStatement node, SourceIndex index) {
205 return recurse(node, index);
206 }
207
208 @Override
209 public Void visitBreakStatement(BreakStatement node, SourceIndex index) {
210 return recurse(node, index);
211 }
212
213 @Override
214 public Void visitContinueStatement(ContinueStatement node, SourceIndex index) {
215 return recurse(node, index);
216 }
217
218 @Override
219 public Void visitDoWhileStatement(DoWhileStatement node, SourceIndex index) {
220 return recurse(node, index);
221 }
222
223 @Override
224 public Void visitEmptyStatement(EmptyStatement node, SourceIndex index) {
225 return recurse(node, index);
226 }
227
228 @Override
229 public Void visitIfElseStatement(IfElseStatement node, SourceIndex index) {
230 return recurse(node, index);
231 }
232
233 @Override
234 public Void visitLabelStatement(LabelStatement node, SourceIndex index) {
235 return recurse(node, index);
236 }
237
238 @Override
239 public Void visitLabeledStatement(LabeledStatement node, SourceIndex index) {
240 return recurse(node, index);
241 }
242
243 @Override
244 public Void visitReturnStatement(ReturnStatement node, SourceIndex index) {
245 return recurse(node, index);
246 }
247
248 @Override
249 public Void visitSwitchStatement(SwitchStatement node, SourceIndex index) {
250 return recurse(node, index);
251 }
252
253 @Override
254 public Void visitSwitchSection(SwitchSection node, SourceIndex index) {
255 return recurse(node, index);
256 }
257
258 @Override
259 public Void visitCaseLabel(CaseLabel node, SourceIndex index) {
260 return recurse(node, index);
261 }
262
263 @Override
264 public Void visitThrowStatement(ThrowStatement node, SourceIndex index) {
265 return recurse(node, index);
266 }
267
268 @Override
269 public Void visitCatchClause(CatchClause node, SourceIndex index) {
270 return recurse(node, index);
271 }
272
273 @Override
274 public Void visitAnnotation(Annotation node, SourceIndex index) {
275 return recurse(node, index);
276 }
277
278 @Override
279 public Void visitNewLine(NewLineNode node, SourceIndex index) {
280 return recurse(node, index);
281 }
282
283 @Override
284 public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) {
285 return recurse(node, index);
286 }
287
288 @Override
289 public Void visitVariableInitializer(VariableInitializer node, SourceIndex index) {
290 return recurse(node, index);
291 }
292
293 @Override
294 public Void visitText(TextNode node, SourceIndex index) {
295 return recurse(node, index);
296 }
297
298 @Override
299 public Void visitImportDeclaration(ImportDeclaration node, SourceIndex index) {
300 return recurse(node, index);
301 }
302
303 @Override
304 public Void visitInitializerBlock(InstanceInitializer node, SourceIndex index) {
305 return recurse(node, index);
306 }
307
308 @Override
309 public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, SourceIndex index) {
310 return recurse(node, index);
311 }
312
313 @Override
314 public Void visitCompilationUnit(CompilationUnit node, SourceIndex index) {
315 return recurse(node, index);
316 }
317
318 @Override
319 public Void visitPackageDeclaration(PackageDeclaration node, SourceIndex index) {
320 return recurse(node, index);
321 }
322
323 @Override
324 public Void visitArraySpecifier(ArraySpecifier node, SourceIndex index) {
325 return recurse(node, index);
326 }
327
328 @Override
329 public Void visitComposedType(ComposedType node, SourceIndex index) {
330 return recurse(node, index);
331 }
332
333 @Override
334 public Void visitWhileStatement(WhileStatement node, SourceIndex index) {
335 return recurse(node, index);
336 }
337
338 @Override
339 public Void visitPrimitiveExpression(PrimitiveExpression node, SourceIndex index) {
340 return recurse(node, index);
341 }
342
343 @Override
344 public Void visitCastExpression(CastExpression node, SourceIndex index) {
345 return recurse(node, index);
346 }
347
348 @Override
349 public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, SourceIndex index) {
350 return recurse(node, index);
351 }
352
353 @Override
354 public Void visitInstanceOfExpression(InstanceOfExpression node, SourceIndex index) {
355 return recurse(node, index);
356 }
357
358 @Override
359 public Void visitIndexerExpression(IndexerExpression node, SourceIndex index) {
360 return recurse(node, index);
361 }
362
363 @Override
364 public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, SourceIndex index) {
365 return recurse(node, index);
366 }
367
368 @Override
369 public Void visitConditionalExpression(ConditionalExpression node, SourceIndex index) {
370 return recurse(node, index);
371 }
372
373 @Override
374 public Void visitArrayInitializerExpression(ArrayInitializerExpression node, SourceIndex index) {
375 return recurse(node, index);
376 }
377
378 @Override
379 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
380 return recurse(node, index);
381 }
382
383 @Override
384 public Void visitArrayCreationExpression(ArrayCreationExpression node, SourceIndex index) {
385 return recurse(node, index);
386 }
387
388 @Override
389 public Void visitAssignmentExpression(AssignmentExpression node, SourceIndex index) {
390 return recurse(node, index);
391 }
392
393 @Override
394 public Void visitForStatement(ForStatement node, SourceIndex index) {
395 return recurse(node, index);
396 }
397
398 @Override
399 public Void visitForEachStatement(ForEachStatement node, SourceIndex index) {
400 return recurse(node, index);
401 }
402
403 @Override
404 public Void visitTryCatchStatement(TryCatchStatement node, SourceIndex index) {
405 return recurse(node, index);
406 }
407
408 @Override
409 public Void visitGotoStatement(GotoStatement node, SourceIndex index) {
410 return recurse(node, index);
411 }
412
413 @Override
414 public Void visitParenthesizedExpression(ParenthesizedExpression node, SourceIndex index) {
415 return recurse(node, index);
416 }
417
418 @Override
419 public Void visitSynchronizedStatement(SynchronizedStatement node, SourceIndex index) {
420 return recurse(node, index);
421 }
422
423 @Override
424 public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, SourceIndex index) {
425 return recurse(node, index);
426 }
427
428 @Override
429 public Void visitWildcardType(WildcardType node, SourceIndex index) {
430 return recurse(node, index);
431 }
432
433 @Override
434 public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) {
435 return recurse(node, index);
436 }
437
438 @Override
439 public Void visitAssertStatement(AssertStatement node, SourceIndex index) {
440 return recurse(node, index);
441 }
442
443 @Override
444 public Void visitLambdaExpression(LambdaExpression node, SourceIndex index) {
445 return recurse(node, index);
446 }
447
448 @Override
449 public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, SourceIndex index) {
450 return recurse(node, index);
451 }
452}
diff --git a/src/cuchaz/enigma/analysis/Token.java b/src/cuchaz/enigma/analysis/Token.java
deleted file mode 100644
index 76d63276..00000000
--- a/src/cuchaz/enigma/analysis/Token.java
+++ /dev/null
@@ -1,56 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13public class Token implements Comparable<Token> {
14
15 public int start;
16 public int end;
17 public String text;
18
19 public Token(int start, int end) {
20 this(start, end, null);
21 }
22
23 public Token(int start, int end, String source) {
24 this.start = start;
25 this.end = end;
26 if (source != null) {
27 this.text = source.substring(start, end);
28 }
29 }
30
31 public boolean contains(int pos) {
32 return pos >= start && pos <= end;
33 }
34
35 @Override
36 public int compareTo(Token other) {
37 return start - other.start;
38 }
39
40 @Override
41 public boolean equals(Object other) {
42 if (other instanceof Token) {
43 return equals((Token)other);
44 }
45 return false;
46 }
47
48 public boolean equals(Token other) {
49 return start == other.start && end == other.end;
50 }
51
52 @Override
53 public String toString() {
54 return String.format("[%d,%d]", start, end);
55 }
56}
diff --git a/src/cuchaz/enigma/analysis/TranslationIndex.java b/src/cuchaz/enigma/analysis/TranslationIndex.java
deleted file mode 100644
index a491cfce..00000000
--- a/src/cuchaz/enigma/analysis/TranslationIndex.java
+++ /dev/null
@@ -1,298 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.io.IOException;
14import java.io.InputStream;
15import java.io.ObjectInputStream;
16import java.io.ObjectOutputStream;
17import java.io.OutputStream;
18import java.io.Serializable;
19import java.util.Collection;
20import java.util.HashMap;
21import java.util.List;
22import java.util.Map;
23import java.util.Set;
24import java.util.zip.GZIPInputStream;
25import java.util.zip.GZIPOutputStream;
26
27import javassist.CtBehavior;
28import javassist.CtClass;
29import javassist.CtField;
30import javassist.bytecode.Descriptor;
31
32import com.google.common.collect.HashMultimap;
33import com.google.common.collect.Lists;
34import com.google.common.collect.Maps;
35import com.google.common.collect.Multimap;
36
37import cuchaz.enigma.mapping.ArgumentEntry;
38import cuchaz.enigma.mapping.BehaviorEntry;
39import cuchaz.enigma.mapping.ClassEntry;
40import cuchaz.enigma.mapping.Entry;
41import cuchaz.enigma.mapping.EntryFactory;
42import cuchaz.enigma.mapping.FieldEntry;
43import cuchaz.enigma.mapping.Translator;
44
45public class TranslationIndex implements Serializable {
46
47 private static final long serialVersionUID = 738687982126844179L;
48
49 private Map<ClassEntry,ClassEntry> m_superclasses;
50 private Multimap<ClassEntry,FieldEntry> m_fieldEntries;
51 private Multimap<ClassEntry,BehaviorEntry> m_behaviorEntries;
52 private Multimap<ClassEntry,ClassEntry> m_interfaces;
53
54 public TranslationIndex() {
55 m_superclasses = Maps.newHashMap();
56 m_fieldEntries = HashMultimap.create();
57 m_behaviorEntries = HashMultimap.create();
58 m_interfaces = HashMultimap.create();
59 }
60
61 public TranslationIndex(TranslationIndex other, Translator translator) {
62
63 // translate the superclasses
64 m_superclasses = Maps.newHashMap();
65 for (Map.Entry<ClassEntry,ClassEntry> mapEntry : other.m_superclasses.entrySet()) {
66 m_superclasses.put(
67 translator.translateEntry(mapEntry.getKey()),
68 translator.translateEntry(mapEntry.getValue())
69 );
70 }
71
72 // translate the interfaces
73 m_interfaces = HashMultimap.create();
74 for (Map.Entry<ClassEntry,ClassEntry> mapEntry : other.m_interfaces.entries()) {
75 m_interfaces.put(
76 translator.translateEntry(mapEntry.getKey()),
77 translator.translateEntry(mapEntry.getValue())
78 );
79 }
80
81 // translate the fields
82 m_fieldEntries = HashMultimap.create();
83 for (Map.Entry<ClassEntry,FieldEntry> mapEntry : other.m_fieldEntries.entries()) {
84 m_fieldEntries.put(
85 translator.translateEntry(mapEntry.getKey()),
86 translator.translateEntry(mapEntry.getValue())
87 );
88 }
89
90 m_behaviorEntries = HashMultimap.create();
91 for (Map.Entry<ClassEntry,BehaviorEntry> mapEntry : other.m_behaviorEntries.entries()) {
92 m_behaviorEntries.put(
93 translator.translateEntry(mapEntry.getKey()),
94 translator.translateEntry(mapEntry.getValue())
95 );
96 }
97 }
98
99 public void indexClass(CtClass c) {
100 indexClass(c, true);
101 }
102
103 public void indexClass(CtClass c, boolean indexMembers) {
104
105 ClassEntry classEntry = EntryFactory.getClassEntry(c);
106 if (isJre(classEntry)) {
107 return;
108 }
109
110 // add the superclass
111 ClassEntry superclassEntry = EntryFactory.getSuperclassEntry(c);
112 if (superclassEntry != null) {
113 m_superclasses.put(classEntry, superclassEntry);
114 }
115
116 // add the interfaces
117 for (String interfaceClassName : c.getClassFile().getInterfaces()) {
118 ClassEntry interfaceClassEntry = new ClassEntry(Descriptor.toJvmName(interfaceClassName));
119 if (!isJre(interfaceClassEntry)) {
120 m_interfaces.put(classEntry, interfaceClassEntry);
121 }
122 }
123
124 if (indexMembers) {
125 // add fields
126 for (CtField field : c.getDeclaredFields()) {
127 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
128 m_fieldEntries.put(fieldEntry.getClassEntry(), fieldEntry);
129 }
130
131 // add behaviors
132 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
133 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
134 m_behaviorEntries.put(behaviorEntry.getClassEntry(), behaviorEntry);
135 }
136 }
137 }
138
139 public void renameClasses(Map<String,String> renames) {
140 EntryRenamer.renameClassesInMap(renames, m_superclasses);
141 EntryRenamer.renameClassesInMultimap(renames, m_fieldEntries);
142 EntryRenamer.renameClassesInMultimap(renames, m_behaviorEntries);
143 }
144
145 public ClassEntry getSuperclass(ClassEntry classEntry) {
146 return m_superclasses.get(classEntry);
147 }
148
149 public List<ClassEntry> getAncestry(ClassEntry classEntry) {
150 List<ClassEntry> ancestors = Lists.newArrayList();
151 while (classEntry != null) {
152 classEntry = getSuperclass(classEntry);
153 if (classEntry != null) {
154 ancestors.add(classEntry);
155 }
156 }
157 return ancestors;
158 }
159
160 public List<ClassEntry> getSubclass(ClassEntry classEntry) {
161
162 // linear search is fast enough for now
163 List<ClassEntry> subclasses = Lists.newArrayList();
164 for (Map.Entry<ClassEntry,ClassEntry> entry : m_superclasses.entrySet()) {
165 ClassEntry subclass = entry.getKey();
166 ClassEntry superclass = entry.getValue();
167 if (classEntry.equals(superclass)) {
168 subclasses.add(subclass);
169 }
170 }
171 return subclasses;
172 }
173
174 public void getSubclassesRecursively(Set<ClassEntry> out, ClassEntry classEntry) {
175 for (ClassEntry subclassEntry : getSubclass(classEntry)) {
176 out.add(subclassEntry);
177 getSubclassesRecursively(out, subclassEntry);
178 }
179 }
180
181 public void getSubclassNamesRecursively(Set<String> out, ClassEntry classEntry) {
182 for (ClassEntry subclassEntry : getSubclass(classEntry)) {
183 out.add(subclassEntry.getName());
184 getSubclassNamesRecursively(out, subclassEntry);
185 }
186 }
187
188 public Collection<Map.Entry<ClassEntry,ClassEntry>> getClassInterfaces() {
189 return m_interfaces.entries();
190 }
191
192 public Collection<ClassEntry> getInterfaces(ClassEntry classEntry) {
193 return m_interfaces.get(classEntry);
194 }
195
196 public boolean isInterface(ClassEntry classEntry) {
197 return m_interfaces.containsValue(classEntry);
198 }
199
200 public boolean entryExists(Entry entry) {
201 if (entry instanceof FieldEntry) {
202 return fieldExists((FieldEntry)entry);
203 } else if (entry instanceof BehaviorEntry) {
204 return behaviorExists((BehaviorEntry)entry);
205 } else if (entry instanceof ArgumentEntry) {
206 return behaviorExists(((ArgumentEntry)entry).getBehaviorEntry());
207 }
208 throw new IllegalArgumentException("Cannot check existence for " + entry.getClass());
209 }
210
211 public boolean fieldExists(FieldEntry fieldEntry) {
212 return m_fieldEntries.containsEntry(fieldEntry.getClassEntry(), fieldEntry);
213 }
214
215 public boolean behaviorExists(BehaviorEntry behaviorEntry) {
216 return m_behaviorEntries.containsEntry(behaviorEntry.getClassEntry(), behaviorEntry);
217 }
218
219 public ClassEntry resolveEntryClass(Entry entry) {
220
221 if (entry instanceof ClassEntry) {
222 return (ClassEntry)entry;
223 }
224
225 ClassEntry superclassEntry = resolveSuperclass(entry);
226 if (superclassEntry != null) {
227 return superclassEntry;
228 }
229
230 ClassEntry interfaceEntry = resolveInterface(entry);
231 if (interfaceEntry != null) {
232 return interfaceEntry;
233 }
234
235 return null;
236 }
237
238 public ClassEntry resolveSuperclass(Entry entry) {
239
240 // this entry could refer to a method on a class where the method is not actually implemented
241 // travel up the inheritance tree to find the closest implementation
242 while (!entryExists(entry)) {
243
244 // is there a parent class?
245 ClassEntry superclassEntry = getSuperclass(entry.getClassEntry());
246 if (superclassEntry == null) {
247 // this is probably a method from a class in a library
248 // we can't trace the implementation up any higher unless we index the library
249 return null;
250 }
251
252 // move up to the parent class
253 entry = entry.cloneToNewClass(superclassEntry);
254 }
255 return entry.getClassEntry();
256 }
257
258 public ClassEntry resolveInterface(Entry entry) {
259
260 // the interfaces for any class is a forest
261 // so let's look at all the trees
262 for (ClassEntry interfaceEntry : m_interfaces.get(entry.getClassEntry())) {
263 ClassEntry resolvedClassEntry = resolveSuperclass(entry.cloneToNewClass(interfaceEntry));
264 if (resolvedClassEntry != null) {
265 return resolvedClassEntry;
266 }
267 }
268 return null;
269 }
270
271 private boolean isJre(ClassEntry classEntry) {
272 String packageName = classEntry.getPackageName();
273 return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax"));
274 }
275
276 public void write(OutputStream out)
277 throws IOException {
278 GZIPOutputStream gzipout = new GZIPOutputStream(out);
279 ObjectOutputStream oout = new ObjectOutputStream(gzipout);
280 oout.writeObject(m_superclasses);
281 oout.writeObject(m_fieldEntries);
282 oout.writeObject(m_behaviorEntries);
283 gzipout.finish();
284 }
285
286 @SuppressWarnings("unchecked")
287 public void read(InputStream in)
288 throws IOException {
289 try {
290 ObjectInputStream oin = new ObjectInputStream(new GZIPInputStream(in));
291 m_superclasses = (HashMap<ClassEntry,ClassEntry>)oin.readObject();
292 m_fieldEntries = (HashMultimap<ClassEntry,FieldEntry>)oin.readObject();
293 m_behaviorEntries = (HashMultimap<ClassEntry,BehaviorEntry>)oin.readObject();
294 } catch (ClassNotFoundException ex) {
295 throw new Error(ex);
296 }
297 }
298}
diff --git a/src/cuchaz/enigma/analysis/TreeDumpVisitor.java b/src/cuchaz/enigma/analysis/TreeDumpVisitor.java
deleted file mode 100644
index 0a90bacc..00000000
--- a/src/cuchaz/enigma/analysis/TreeDumpVisitor.java
+++ /dev/null
@@ -1,512 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.io.File;
14import java.io.FileWriter;
15import java.io.IOException;
16import java.io.Writer;
17
18import com.strobel.componentmodel.Key;
19import com.strobel.decompiler.languages.java.ast.Annotation;
20import com.strobel.decompiler.languages.java.ast.AnonymousObjectCreationExpression;
21import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression;
22import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression;
23import com.strobel.decompiler.languages.java.ast.ArraySpecifier;
24import com.strobel.decompiler.languages.java.ast.AssertStatement;
25import com.strobel.decompiler.languages.java.ast.AssignmentExpression;
26import com.strobel.decompiler.languages.java.ast.AstNode;
27import com.strobel.decompiler.languages.java.ast.BinaryOperatorExpression;
28import com.strobel.decompiler.languages.java.ast.BlockStatement;
29import com.strobel.decompiler.languages.java.ast.BreakStatement;
30import com.strobel.decompiler.languages.java.ast.CaseLabel;
31import com.strobel.decompiler.languages.java.ast.CastExpression;
32import com.strobel.decompiler.languages.java.ast.CatchClause;
33import com.strobel.decompiler.languages.java.ast.ClassOfExpression;
34import com.strobel.decompiler.languages.java.ast.Comment;
35import com.strobel.decompiler.languages.java.ast.CompilationUnit;
36import com.strobel.decompiler.languages.java.ast.ComposedType;
37import com.strobel.decompiler.languages.java.ast.ConditionalExpression;
38import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration;
39import com.strobel.decompiler.languages.java.ast.ContinueStatement;
40import com.strobel.decompiler.languages.java.ast.DoWhileStatement;
41import com.strobel.decompiler.languages.java.ast.EmptyStatement;
42import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration;
43import com.strobel.decompiler.languages.java.ast.ExpressionStatement;
44import com.strobel.decompiler.languages.java.ast.FieldDeclaration;
45import com.strobel.decompiler.languages.java.ast.ForEachStatement;
46import com.strobel.decompiler.languages.java.ast.ForStatement;
47import com.strobel.decompiler.languages.java.ast.GotoStatement;
48import com.strobel.decompiler.languages.java.ast.IAstVisitor;
49import com.strobel.decompiler.languages.java.ast.Identifier;
50import com.strobel.decompiler.languages.java.ast.IdentifierExpression;
51import com.strobel.decompiler.languages.java.ast.IfElseStatement;
52import com.strobel.decompiler.languages.java.ast.ImportDeclaration;
53import com.strobel.decompiler.languages.java.ast.IndexerExpression;
54import com.strobel.decompiler.languages.java.ast.InstanceInitializer;
55import com.strobel.decompiler.languages.java.ast.InstanceOfExpression;
56import com.strobel.decompiler.languages.java.ast.InvocationExpression;
57import com.strobel.decompiler.languages.java.ast.JavaTokenNode;
58import com.strobel.decompiler.languages.java.ast.Keys;
59import com.strobel.decompiler.languages.java.ast.LabelStatement;
60import com.strobel.decompiler.languages.java.ast.LabeledStatement;
61import com.strobel.decompiler.languages.java.ast.LambdaExpression;
62import com.strobel.decompiler.languages.java.ast.LocalTypeDeclarationStatement;
63import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
64import com.strobel.decompiler.languages.java.ast.MethodDeclaration;
65import com.strobel.decompiler.languages.java.ast.MethodGroupExpression;
66import com.strobel.decompiler.languages.java.ast.NewLineNode;
67import com.strobel.decompiler.languages.java.ast.NullReferenceExpression;
68import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression;
69import com.strobel.decompiler.languages.java.ast.PackageDeclaration;
70import com.strobel.decompiler.languages.java.ast.ParameterDeclaration;
71import com.strobel.decompiler.languages.java.ast.ParenthesizedExpression;
72import com.strobel.decompiler.languages.java.ast.PrimitiveExpression;
73import com.strobel.decompiler.languages.java.ast.ReturnStatement;
74import com.strobel.decompiler.languages.java.ast.SimpleType;
75import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression;
76import com.strobel.decompiler.languages.java.ast.SwitchSection;
77import com.strobel.decompiler.languages.java.ast.SwitchStatement;
78import com.strobel.decompiler.languages.java.ast.SynchronizedStatement;
79import com.strobel.decompiler.languages.java.ast.TextNode;
80import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression;
81import com.strobel.decompiler.languages.java.ast.ThrowStatement;
82import com.strobel.decompiler.languages.java.ast.TryCatchStatement;
83import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
84import com.strobel.decompiler.languages.java.ast.TypeParameterDeclaration;
85import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression;
86import com.strobel.decompiler.languages.java.ast.UnaryOperatorExpression;
87import com.strobel.decompiler.languages.java.ast.VariableDeclarationStatement;
88import com.strobel.decompiler.languages.java.ast.VariableInitializer;
89import com.strobel.decompiler.languages.java.ast.WhileStatement;
90import com.strobel.decompiler.languages.java.ast.WildcardType;
91import com.strobel.decompiler.patterns.Pattern;
92
93public class TreeDumpVisitor implements IAstVisitor<Void,Void> {
94
95 private File m_file;
96 private Writer m_out;
97
98 public TreeDumpVisitor(File file) {
99 m_file = file;
100 m_out = null;
101 }
102
103 @Override
104 public Void visitCompilationUnit(CompilationUnit node, Void ignored) {
105 try {
106 m_out = new FileWriter(m_file);
107 recurse(node, ignored);
108 m_out.close();
109 return null;
110 } catch (IOException ex) {
111 throw new Error(ex);
112 }
113 }
114
115 private Void recurse(AstNode node, Void ignored) {
116 // show the tree
117 try {
118 m_out.write(getIndent(node) + node.getClass().getSimpleName() + " " + getText(node) + " " + dumpUserData(node) + " " + node.getRegion() + "\n");
119 } catch (IOException ex) {
120 throw new Error(ex);
121 }
122
123 // recurse
124 for (final AstNode child : node.getChildren()) {
125 child.acceptVisitor(this, ignored);
126 }
127 return null;
128 }
129
130 private String getText(AstNode node) {
131 if (node instanceof Identifier) {
132 return "\"" + ((Identifier)node).getName() + "\"";
133 }
134 return "";
135 }
136
137 private String dumpUserData(AstNode node) {
138 StringBuilder buf = new StringBuilder();
139 for (Key<?> key : Keys.ALL_KEYS) {
140 Object val = node.getUserData(key);
141 if (val != null) {
142 buf.append(String.format(" [%s=%s]", key, val));
143 }
144 }
145 return buf.toString();
146 }
147
148 private String getIndent(AstNode node) {
149 StringBuilder buf = new StringBuilder();
150 int depth = getDepth(node);
151 for (int i = 0; i < depth; i++) {
152 buf.append("\t");
153 }
154 return buf.toString();
155 }
156
157 private int getDepth(AstNode node) {
158 int depth = -1;
159 while (node != null) {
160 depth++;
161 node = node.getParent();
162 }
163 return depth;
164 }
165
166 // OVERRIDES WE DON'T CARE ABOUT
167
168 @Override
169 public Void visitInvocationExpression(InvocationExpression node, Void ignored) {
170 return recurse(node, ignored);
171 }
172
173 @Override
174 public Void visitMemberReferenceExpression(MemberReferenceExpression node, Void ignored) {
175 return recurse(node, ignored);
176 }
177
178 @Override
179 public Void visitSimpleType(SimpleType node, Void ignored) {
180 return recurse(node, ignored);
181 }
182
183 @Override
184 public Void visitMethodDeclaration(MethodDeclaration node, Void ignored) {
185 return recurse(node, ignored);
186 }
187
188 @Override
189 public Void visitConstructorDeclaration(ConstructorDeclaration node, Void ignored) {
190 return recurse(node, ignored);
191 }
192
193 @Override
194 public Void visitParameterDeclaration(ParameterDeclaration node, Void ignored) {
195 return recurse(node, ignored);
196 }
197
198 @Override
199 public Void visitFieldDeclaration(FieldDeclaration node, Void ignored) {
200 return recurse(node, ignored);
201 }
202
203 @Override
204 public Void visitTypeDeclaration(TypeDeclaration node, Void ignored) {
205 return recurse(node, ignored);
206 }
207
208 @Override
209 public Void visitComment(Comment node, Void ignored) {
210 return recurse(node, ignored);
211 }
212
213 @Override
214 public Void visitPatternPlaceholder(AstNode node, Pattern pattern, Void ignored) {
215 return recurse(node, ignored);
216 }
217
218 @Override
219 public Void visitTypeReference(TypeReferenceExpression node, Void ignored) {
220 return recurse(node, ignored);
221 }
222
223 @Override
224 public Void visitJavaTokenNode(JavaTokenNode node, Void ignored) {
225 return recurse(node, ignored);
226 }
227
228 @Override
229 public Void visitIdentifier(Identifier node, Void ignored) {
230 return recurse(node, ignored);
231 }
232
233 @Override
234 public Void visitNullReferenceExpression(NullReferenceExpression node, Void ignored) {
235 return recurse(node, ignored);
236 }
237
238 @Override
239 public Void visitThisReferenceExpression(ThisReferenceExpression node, Void ignored) {
240 return recurse(node, ignored);
241 }
242
243 @Override
244 public Void visitSuperReferenceExpression(SuperReferenceExpression node, Void ignored) {
245 return recurse(node, ignored);
246 }
247
248 @Override
249 public Void visitClassOfExpression(ClassOfExpression node, Void ignored) {
250 return recurse(node, ignored);
251 }
252
253 @Override
254 public Void visitBlockStatement(BlockStatement node, Void ignored) {
255 return recurse(node, ignored);
256 }
257
258 @Override
259 public Void visitExpressionStatement(ExpressionStatement node, Void ignored) {
260 return recurse(node, ignored);
261 }
262
263 @Override
264 public Void visitBreakStatement(BreakStatement node, Void ignored) {
265 return recurse(node, ignored);
266 }
267
268 @Override
269 public Void visitContinueStatement(ContinueStatement node, Void ignored) {
270 return recurse(node, ignored);
271 }
272
273 @Override
274 public Void visitDoWhileStatement(DoWhileStatement node, Void ignored) {
275 return recurse(node, ignored);
276 }
277
278 @Override
279 public Void visitEmptyStatement(EmptyStatement node, Void ignored) {
280 return recurse(node, ignored);
281 }
282
283 @Override
284 public Void visitIfElseStatement(IfElseStatement node, Void ignored) {
285 return recurse(node, ignored);
286 }
287
288 @Override
289 public Void visitLabelStatement(LabelStatement node, Void ignored) {
290 return recurse(node, ignored);
291 }
292
293 @Override
294 public Void visitLabeledStatement(LabeledStatement node, Void ignored) {
295 return recurse(node, ignored);
296 }
297
298 @Override
299 public Void visitReturnStatement(ReturnStatement node, Void ignored) {
300 return recurse(node, ignored);
301 }
302
303 @Override
304 public Void visitSwitchStatement(SwitchStatement node, Void ignored) {
305 return recurse(node, ignored);
306 }
307
308 @Override
309 public Void visitSwitchSection(SwitchSection node, Void ignored) {
310 return recurse(node, ignored);
311 }
312
313 @Override
314 public Void visitCaseLabel(CaseLabel node, Void ignored) {
315 return recurse(node, ignored);
316 }
317
318 @Override
319 public Void visitThrowStatement(ThrowStatement node, Void ignored) {
320 return recurse(node, ignored);
321 }
322
323 @Override
324 public Void visitCatchClause(CatchClause node, Void ignored) {
325 return recurse(node, ignored);
326 }
327
328 @Override
329 public Void visitAnnotation(Annotation node, Void ignored) {
330 return recurse(node, ignored);
331 }
332
333 @Override
334 public Void visitNewLine(NewLineNode node, Void ignored) {
335 return recurse(node, ignored);
336 }
337
338 @Override
339 public Void visitVariableDeclaration(VariableDeclarationStatement node, Void ignored) {
340 return recurse(node, ignored);
341 }
342
343 @Override
344 public Void visitVariableInitializer(VariableInitializer node, Void ignored) {
345 return recurse(node, ignored);
346 }
347
348 @Override
349 public Void visitText(TextNode node, Void ignored) {
350 return recurse(node, ignored);
351 }
352
353 @Override
354 public Void visitImportDeclaration(ImportDeclaration node, Void ignored) {
355 return recurse(node, ignored);
356 }
357
358 @Override
359 public Void visitInitializerBlock(InstanceInitializer node, Void ignored) {
360 return recurse(node, ignored);
361 }
362
363 @Override
364 public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, Void ignored) {
365 return recurse(node, ignored);
366 }
367
368 @Override
369 public Void visitPackageDeclaration(PackageDeclaration node, Void ignored) {
370 return recurse(node, ignored);
371 }
372
373 @Override
374 public Void visitArraySpecifier(ArraySpecifier node, Void ignored) {
375 return recurse(node, ignored);
376 }
377
378 @Override
379 public Void visitComposedType(ComposedType node, Void ignored) {
380 return recurse(node, ignored);
381 }
382
383 @Override
384 public Void visitWhileStatement(WhileStatement node, Void ignored) {
385 return recurse(node, ignored);
386 }
387
388 @Override
389 public Void visitPrimitiveExpression(PrimitiveExpression node, Void ignored) {
390 return recurse(node, ignored);
391 }
392
393 @Override
394 public Void visitCastExpression(CastExpression node, Void ignored) {
395 return recurse(node, ignored);
396 }
397
398 @Override
399 public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, Void ignored) {
400 return recurse(node, ignored);
401 }
402
403 @Override
404 public Void visitInstanceOfExpression(InstanceOfExpression node, Void ignored) {
405 return recurse(node, ignored);
406 }
407
408 @Override
409 public Void visitIndexerExpression(IndexerExpression node, Void ignored) {
410 return recurse(node, ignored);
411 }
412
413 @Override
414 public Void visitIdentifierExpression(IdentifierExpression node, Void ignored) {
415 return recurse(node, ignored);
416 }
417
418 @Override
419 public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, Void ignored) {
420 return recurse(node, ignored);
421 }
422
423 @Override
424 public Void visitConditionalExpression(ConditionalExpression node, Void ignored) {
425 return recurse(node, ignored);
426 }
427
428 @Override
429 public Void visitArrayInitializerExpression(ArrayInitializerExpression node, Void ignored) {
430 return recurse(node, ignored);
431 }
432
433 @Override
434 public Void visitObjectCreationExpression(ObjectCreationExpression node, Void ignored) {
435 return recurse(node, ignored);
436 }
437
438 @Override
439 public Void visitArrayCreationExpression(ArrayCreationExpression node, Void ignored) {
440 return recurse(node, ignored);
441 }
442
443 @Override
444 public Void visitAssignmentExpression(AssignmentExpression node, Void ignored) {
445 return recurse(node, ignored);
446 }
447
448 @Override
449 public Void visitForStatement(ForStatement node, Void ignored) {
450 return recurse(node, ignored);
451 }
452
453 @Override
454 public Void visitForEachStatement(ForEachStatement node, Void ignored) {
455 return recurse(node, ignored);
456 }
457
458 @Override
459 public Void visitTryCatchStatement(TryCatchStatement node, Void ignored) {
460 return recurse(node, ignored);
461 }
462
463 @Override
464 public Void visitGotoStatement(GotoStatement node, Void ignored) {
465 return recurse(node, ignored);
466 }
467
468 @Override
469 public Void visitParenthesizedExpression(ParenthesizedExpression node, Void ignored) {
470 return recurse(node, ignored);
471 }
472
473 @Override
474 public Void visitSynchronizedStatement(SynchronizedStatement node, Void ignored) {
475 return recurse(node, ignored);
476 }
477
478 @Override
479 public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, Void ignored) {
480 return recurse(node, ignored);
481 }
482
483 @Override
484 public Void visitWildcardType(WildcardType node, Void ignored) {
485 return recurse(node, ignored);
486 }
487
488 @Override
489 public Void visitMethodGroupExpression(MethodGroupExpression node, Void ignored) {
490 return recurse(node, ignored);
491 }
492
493 @Override
494 public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void ignored) {
495 return recurse(node, ignored);
496 }
497
498 @Override
499 public Void visitAssertStatement(AssertStatement node, Void ignored) {
500 return recurse(node, ignored);
501 }
502
503 @Override
504 public Void visitLambdaExpression(LambdaExpression node, Void ignored) {
505 return recurse(node, ignored);
506 }
507
508 @Override
509 public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, Void ignored) {
510 return recurse(node, ignored);
511 }
512}
diff --git a/src/cuchaz/enigma/bytecode/CheckCastIterator.java b/src/cuchaz/enigma/bytecode/CheckCastIterator.java
deleted file mode 100644
index 517b9d62..00000000
--- a/src/cuchaz/enigma/bytecode/CheckCastIterator.java
+++ /dev/null
@@ -1,127 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.Iterator;
14
15import javassist.bytecode.BadBytecode;
16import javassist.bytecode.CodeAttribute;
17import javassist.bytecode.CodeIterator;
18import javassist.bytecode.ConstPool;
19import javassist.bytecode.Descriptor;
20import javassist.bytecode.Opcode;
21import cuchaz.enigma.bytecode.CheckCastIterator.CheckCast;
22import cuchaz.enigma.mapping.ClassEntry;
23import cuchaz.enigma.mapping.MethodEntry;
24import cuchaz.enigma.mapping.Signature;
25
26public class CheckCastIterator implements Iterator<CheckCast> {
27
28 public static class CheckCast {
29
30 public String className;
31 public MethodEntry prevMethodEntry;
32
33 public CheckCast(String className, MethodEntry prevMethodEntry) {
34 this.className = className;
35 this.prevMethodEntry = prevMethodEntry;
36 }
37 }
38
39 private ConstPool m_constants;
40 private CodeAttribute m_attribute;
41 private CodeIterator m_iter;
42 private CheckCast m_next;
43
44 public CheckCastIterator(CodeAttribute codeAttribute) throws BadBytecode {
45 m_constants = codeAttribute.getConstPool();
46 m_attribute = codeAttribute;
47 m_iter = m_attribute.iterator();
48
49 m_next = getNext();
50 }
51
52 @Override
53 public boolean hasNext() {
54 return m_next != null;
55 }
56
57 @Override
58 public CheckCast next() {
59 CheckCast out = m_next;
60 try {
61 m_next = getNext();
62 } catch (BadBytecode ex) {
63 throw new Error(ex);
64 }
65 return out;
66 }
67
68 @Override
69 public void remove() {
70 throw new UnsupportedOperationException();
71 }
72
73 private CheckCast getNext() throws BadBytecode {
74 int prevPos = 0;
75 while (m_iter.hasNext()) {
76 int pos = m_iter.next();
77 int opcode = m_iter.byteAt(pos);
78 switch (opcode) {
79 case Opcode.CHECKCAST:
80
81 // get the type of this op code (next two bytes are a classinfo index)
82 MethodEntry prevMethodEntry = getMethodEntry(prevPos);
83 if (prevMethodEntry != null) {
84 return new CheckCast(m_constants.getClassInfo(m_iter.s16bitAt(pos + 1)), prevMethodEntry);
85 }
86 break;
87 }
88 prevPos = pos;
89 }
90 return null;
91 }
92
93 private MethodEntry getMethodEntry(int pos) {
94 switch (m_iter.byteAt(pos)) {
95 case Opcode.INVOKEVIRTUAL:
96 case Opcode.INVOKESTATIC:
97 case Opcode.INVOKEDYNAMIC:
98 case Opcode.INVOKESPECIAL: {
99 int index = m_iter.s16bitAt(pos + 1);
100 return new MethodEntry(
101 new ClassEntry(Descriptor.toJvmName(m_constants.getMethodrefClassName(index))),
102 m_constants.getMethodrefName(index),
103 new Signature(m_constants.getMethodrefType(index))
104 );
105 }
106
107 case Opcode.INVOKEINTERFACE: {
108 int index = m_iter.s16bitAt(pos + 1);
109 return new MethodEntry(
110 new ClassEntry(Descriptor.toJvmName(m_constants.getInterfaceMethodrefClassName(index))),
111 m_constants.getInterfaceMethodrefName(index),
112 new Signature(m_constants.getInterfaceMethodrefType(index))
113 );
114 }
115 }
116 return null;
117 }
118
119 public Iterable<CheckCast> casts() {
120 return new Iterable<CheckCast>() {
121 @Override
122 public Iterator<CheckCast> iterator() {
123 return CheckCastIterator.this;
124 }
125 };
126 }
127}
diff --git a/src/cuchaz/enigma/bytecode/ClassProtectifier.java b/src/cuchaz/enigma/bytecode/ClassProtectifier.java
deleted file mode 100644
index f1ee4e77..00000000
--- a/src/cuchaz/enigma/bytecode/ClassProtectifier.java
+++ /dev/null
@@ -1,51 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.CtField;
16import javassist.bytecode.AccessFlag;
17import javassist.bytecode.InnerClassesAttribute;
18
19
20public class ClassProtectifier {
21
22 public static CtClass protectify(CtClass c) {
23
24 // protectify all the fields
25 for (CtField field : c.getDeclaredFields()) {
26 field.setModifiers(protectify(field.getModifiers()));
27 }
28
29 // protectify all the methods and constructors
30 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
31 behavior.setModifiers(protectify(behavior.getModifiers()));
32 }
33
34 // protectify all the inner classes
35 InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
36 if (attr != null) {
37 for (int i=0; i<attr.tableLength(); i++) {
38 attr.setAccessFlags(i, protectify(attr.accessFlags(i)));
39 }
40 }
41
42 return c;
43 }
44
45 private static int protectify(int flags) {
46 if (AccessFlag.isPrivate(flags)) {
47 flags = AccessFlag.setProtected(flags);
48 }
49 return flags;
50 }
51}
diff --git a/src/cuchaz/enigma/bytecode/ClassPublifier.java b/src/cuchaz/enigma/bytecode/ClassPublifier.java
deleted file mode 100644
index dbefd426..00000000
--- a/src/cuchaz/enigma/bytecode/ClassPublifier.java
+++ /dev/null
@@ -1,51 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.CtField;
16import javassist.bytecode.AccessFlag;
17import javassist.bytecode.InnerClassesAttribute;
18
19
20public class ClassPublifier {
21
22 public static CtClass publify(CtClass c) {
23
24 // publify all the fields
25 for (CtField field : c.getDeclaredFields()) {
26 field.setModifiers(publify(field.getModifiers()));
27 }
28
29 // publify all the methods and constructors
30 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
31 behavior.setModifiers(publify(behavior.getModifiers()));
32 }
33
34 // publify all the inner classes
35 InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
36 if (attr != null) {
37 for (int i=0; i<attr.tableLength(); i++) {
38 attr.setAccessFlags(i, publify(attr.accessFlags(i)));
39 }
40 }
41
42 return c;
43 }
44
45 private static int publify(int flags) {
46 if (AccessFlag.isPrivate(flags) || AccessFlag.isProtected(flags)) {
47 flags = AccessFlag.setPublic(flags);
48 }
49 return flags;
50 }
51}
diff --git a/src/cuchaz/enigma/bytecode/ClassRenamer.java b/src/cuchaz/enigma/bytecode/ClassRenamer.java
deleted file mode 100644
index 509877a4..00000000
--- a/src/cuchaz/enigma/bytecode/ClassRenamer.java
+++ /dev/null
@@ -1,544 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.lang.reflect.InvocationTargetException;
14import java.lang.reflect.Method;
15import java.util.Arrays;
16import java.util.HashMap;
17import java.util.List;
18import java.util.Map;
19
20import javassist.CtClass;
21import javassist.bytecode.AttributeInfo;
22import javassist.bytecode.BadBytecode;
23import javassist.bytecode.ByteArray;
24import javassist.bytecode.ClassFile;
25import javassist.bytecode.CodeAttribute;
26import javassist.bytecode.ConstPool;
27import javassist.bytecode.Descriptor;
28import javassist.bytecode.FieldInfo;
29import javassist.bytecode.InnerClassesAttribute;
30import javassist.bytecode.LocalVariableTypeAttribute;
31import javassist.bytecode.MethodInfo;
32import javassist.bytecode.SignatureAttribute;
33import javassist.bytecode.SignatureAttribute.ArrayType;
34import javassist.bytecode.SignatureAttribute.BaseType;
35import javassist.bytecode.SignatureAttribute.ClassSignature;
36import javassist.bytecode.SignatureAttribute.ClassType;
37import javassist.bytecode.SignatureAttribute.MethodSignature;
38import javassist.bytecode.SignatureAttribute.NestedClassType;
39import javassist.bytecode.SignatureAttribute.ObjectType;
40import javassist.bytecode.SignatureAttribute.Type;
41import javassist.bytecode.SignatureAttribute.TypeArgument;
42import javassist.bytecode.SignatureAttribute.TypeParameter;
43import javassist.bytecode.SignatureAttribute.TypeVariable;
44import cuchaz.enigma.mapping.ClassEntry;
45import cuchaz.enigma.mapping.ClassNameReplacer;
46import cuchaz.enigma.mapping.Translator;
47
48public class ClassRenamer {
49
50 private static enum SignatureType {
51 Class {
52
53 @Override
54 public String rename(String signature, ReplacerClassMap map) {
55 return renameClassSignature(signature, map);
56 }
57 },
58 Field {
59
60 @Override
61 public String rename(String signature, ReplacerClassMap map) {
62 return renameFieldSignature(signature, map);
63 }
64 },
65 Method {
66
67 @Override
68 public String rename(String signature, ReplacerClassMap map) {
69 return renameMethodSignature(signature, map);
70 }
71 };
72
73 public abstract String rename(String signature, ReplacerClassMap map);
74 }
75
76 private static class ReplacerClassMap extends HashMap<String,String> {
77
78 private static final long serialVersionUID = 317915213205066168L;
79
80 private ClassNameReplacer m_replacer;
81
82 public ReplacerClassMap(ClassNameReplacer replacer) {
83 m_replacer = replacer;
84 }
85
86 @Override
87 public String get(Object obj) {
88 if (obj instanceof String) {
89 return get((String)obj);
90 }
91 return null;
92 }
93
94 public String get(String className) {
95 return m_replacer.replace(className);
96 }
97 }
98
99 public static void renameClasses(CtClass c, final Translator translator) {
100 renameClasses(c, new ClassNameReplacer() {
101 @Override
102 public String replace(String className) {
103 ClassEntry entry = translator.translateEntry(new ClassEntry(className));
104 if (entry != null) {
105 return entry.getName();
106 }
107 return null;
108 }
109 });
110 }
111
112 public static void moveAllClassesOutOfDefaultPackage(CtClass c, final String newPackageName) {
113 renameClasses(c, new ClassNameReplacer() {
114 @Override
115 public String replace(String className) {
116 ClassEntry entry = new ClassEntry(className);
117 if (entry.isInDefaultPackage()) {
118 return newPackageName + "/" + entry.getName();
119 }
120 return null;
121 }
122 });
123 }
124
125 public static void moveAllClassesIntoDefaultPackage(CtClass c, final String oldPackageName) {
126 renameClasses(c, new ClassNameReplacer() {
127 @Override
128 public String replace(String className) {
129 ClassEntry entry = new ClassEntry(className);
130 if (entry.getPackageName().equals(oldPackageName)) {
131 return entry.getSimpleName();
132 }
133 return null;
134 }
135 });
136 }
137
138 @SuppressWarnings("unchecked")
139 public static void renameClasses(CtClass c, ClassNameReplacer replacer) {
140
141 // sadly, we can't use CtClass.renameClass() because SignatureAttribute.renameClass() is extremely buggy =(
142
143 ReplacerClassMap map = new ReplacerClassMap(replacer);
144 ClassFile classFile = c.getClassFile();
145
146 // rename the constant pool (covers ClassInfo, MethodTypeInfo, and NameAndTypeInfo)
147 ConstPool constPool = c.getClassFile().getConstPool();
148 constPool.renameClass(map);
149
150 // rename class attributes
151 renameAttributes(classFile.getAttributes(), map, SignatureType.Class);
152
153 // rename methods
154 for (MethodInfo methodInfo : (List<MethodInfo>)classFile.getMethods()) {
155 methodInfo.setDescriptor(Descriptor.rename(methodInfo.getDescriptor(), map));
156 renameAttributes(methodInfo.getAttributes(), map, SignatureType.Method);
157 }
158
159 // rename fields
160 for (FieldInfo fieldInfo : (List<FieldInfo>)classFile.getFields()) {
161 fieldInfo.setDescriptor(Descriptor.rename(fieldInfo.getDescriptor(), map));
162 renameAttributes(fieldInfo.getAttributes(), map, SignatureType.Field);
163 }
164
165 // rename the class name itself last
166 // NOTE: don't use the map here, because setName() calls the buggy SignatureAttribute.renameClass()
167 // we only want to replace exactly this class name
168 String newName = renameClassName(c.getName(), map);
169 if (newName != null) {
170 c.setName(newName);
171 }
172
173 // replace simple names in the InnerClasses attribute too
174 InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
175 if (attr != null) {
176 for (int i = 0; i < attr.tableLength(); i++) {
177
178 // get the inner class full name (which has already been translated)
179 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(attr.innerClass(i)));
180
181 if (attr.innerNameIndex(i) != 0) {
182 // update the inner name
183 attr.setInnerNameIndex(i, constPool.addUtf8Info(classEntry.getInnermostClassName()));
184 }
185
186 /* DEBUG
187 System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i)));
188 */
189 }
190 }
191 }
192
193 @SuppressWarnings("unchecked")
194 private static void renameAttributes(List<AttributeInfo> attributes, ReplacerClassMap map, SignatureType type) {
195 try {
196
197 // make the rename class method accessible
198 Method renameClassMethod = AttributeInfo.class.getDeclaredMethod("renameClass", Map.class);
199 renameClassMethod.setAccessible(true);
200
201 for (AttributeInfo attribute : attributes) {
202 if (attribute instanceof SignatureAttribute) {
203 // this has to be handled specially because SignatureAttribute.renameClass() is buggy as hell
204 SignatureAttribute signatureAttribute = (SignatureAttribute)attribute;
205 String newSignature = type.rename(signatureAttribute.getSignature(), map);
206 if (newSignature != null) {
207 signatureAttribute.setSignature(newSignature);
208 }
209 } else if (attribute instanceof CodeAttribute) {
210 // code attributes have signature attributes too (indirectly)
211 CodeAttribute codeAttribute = (CodeAttribute)attribute;
212 renameAttributes(codeAttribute.getAttributes(), map, type);
213 } else if (attribute instanceof LocalVariableTypeAttribute) {
214 // lvt attributes have signature attributes too
215 LocalVariableTypeAttribute localVariableAttribute = (LocalVariableTypeAttribute)attribute;
216 renameLocalVariableTypeAttribute(localVariableAttribute, map);
217 } else {
218 renameClassMethod.invoke(attribute, map);
219 }
220 }
221
222 } catch(NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
223 throw new Error("Unable to call javassist methods by reflection!", ex);
224 }
225 }
226
227 private static void renameLocalVariableTypeAttribute(LocalVariableTypeAttribute attribute, ReplacerClassMap map) {
228
229 // adapted from LocalVariableAttribute.renameClass()
230 ConstPool cp = attribute.getConstPool();
231 int n = attribute.tableLength();
232 byte[] info = attribute.get();
233 for (int i = 0; i < n; ++i) {
234 int pos = i * 10 + 2;
235 int index = ByteArray.readU16bit(info, pos + 6);
236 if (index != 0) {
237 String signature = cp.getUtf8Info(index);
238 String newSignature = renameLocalVariableSignature(signature, map);
239 if (newSignature != null) {
240 ByteArray.write16bit(cp.addUtf8Info(newSignature), info, pos + 6);
241 }
242 }
243 }
244 }
245
246 private static String renameLocalVariableSignature(String signature, ReplacerClassMap map) {
247
248 // for some reason, signatures with . in them don't count as field signatures
249 // looks like anonymous classes delimit with . in stead of $
250 // convert the . to $, but keep track of how many we replace
251 // we need to put them back after we translate
252 int start = signature.lastIndexOf('$') + 1;
253 int numConverted = 0;
254 StringBuilder buf = new StringBuilder(signature);
255 for (int i=buf.length()-1; i>=start; i--) {
256 char c = buf.charAt(i);
257 if (c == '.') {
258 buf.setCharAt(i, '$');
259 numConverted++;
260 }
261 }
262 signature = buf.toString();
263
264 // translate
265 String newSignature = renameFieldSignature(signature, map);
266 if (newSignature != null) {
267
268 // put the delimiters back
269 buf = new StringBuilder(newSignature);
270 for (int i=buf.length()-1; i>=0 && numConverted > 0; i--) {
271 char c = buf.charAt(i);
272 if (c == '$') {
273 buf.setCharAt(i, '.');
274 numConverted--;
275 }
276 }
277 assert(numConverted == 0);
278 newSignature = buf.toString();
279
280 return newSignature;
281 }
282
283 return null;
284 }
285
286 private static String renameClassSignature(String signature, ReplacerClassMap map) {
287 try {
288 ClassSignature type = renameType(SignatureAttribute.toClassSignature(signature), map);
289 if (type != null) {
290 return type.encode();
291 }
292 return null;
293 } catch (BadBytecode ex) {
294 throw new Error("Can't parse field signature: " + signature);
295 }
296 }
297
298 private static String renameFieldSignature(String signature, ReplacerClassMap map) {
299 try {
300 ObjectType type = renameType(SignatureAttribute.toFieldSignature(signature), map);
301 if (type != null) {
302 return type.encode();
303 }
304 return null;
305 } catch (BadBytecode ex) {
306 throw new Error("Can't parse class signature: " + signature);
307 }
308 }
309
310 private static String renameMethodSignature(String signature, ReplacerClassMap map) {
311 try {
312 MethodSignature type = renameType(SignatureAttribute.toMethodSignature(signature), map);
313 if (type != null) {
314 return type.encode();
315 }
316 return null;
317 } catch (BadBytecode ex) {
318 throw new Error("Can't parse method signature: " + signature);
319 }
320 }
321
322 private static ClassSignature renameType(ClassSignature type, ReplacerClassMap map) {
323
324 TypeParameter[] typeParamTypes = type.getParameters();
325 if (typeParamTypes != null) {
326 typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length);
327 for (int i=0; i<typeParamTypes.length; i++) {
328 TypeParameter newParamType = renameType(typeParamTypes[i], map);
329 if (newParamType != null) {
330 typeParamTypes[i] = newParamType;
331 }
332 }
333 }
334
335 ClassType superclassType = type.getSuperClass();
336 if (superclassType != ClassType.OBJECT) {
337 ClassType newSuperclassType = renameType(superclassType, map);
338 if (newSuperclassType != null) {
339 superclassType = newSuperclassType;
340 }
341 }
342
343 ClassType[] interfaceTypes = type.getInterfaces();
344 if (interfaceTypes != null) {
345 interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length);
346 for (int i=0; i<interfaceTypes.length; i++) {
347 ClassType newInterfaceType = renameType(interfaceTypes[i], map);
348 if (newInterfaceType != null) {
349 interfaceTypes[i] = newInterfaceType;
350 }
351 }
352 }
353
354 return new ClassSignature(typeParamTypes, superclassType, interfaceTypes);
355 }
356
357 private static MethodSignature renameType(MethodSignature type, ReplacerClassMap map) {
358
359 TypeParameter[] typeParamTypes = type.getTypeParameters();
360 if (typeParamTypes != null) {
361 typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length);
362 for (int i=0; i<typeParamTypes.length; i++) {
363 TypeParameter newParamType = renameType(typeParamTypes[i], map);
364 if (newParamType != null) {
365 typeParamTypes[i] = newParamType;
366 }
367 }
368 }
369
370 Type[] paramTypes = type.getParameterTypes();
371 if (paramTypes != null) {
372 paramTypes = Arrays.copyOf(paramTypes, paramTypes.length);
373 for (int i=0; i<paramTypes.length; i++) {
374 Type newParamType = renameType(paramTypes[i], map);
375 if (newParamType != null) {
376 paramTypes[i] = newParamType;
377 }
378 }
379 }
380
381 Type returnType = type.getReturnType();
382 if (returnType != null) {
383 Type newReturnType = renameType(returnType, map);
384 if (newReturnType != null) {
385 returnType = newReturnType;
386 }
387 }
388
389 ObjectType[] exceptionTypes = type.getExceptionTypes();
390 if (exceptionTypes != null) {
391 exceptionTypes = Arrays.copyOf(exceptionTypes, exceptionTypes.length);
392 for (int i=0; i<exceptionTypes.length; i++) {
393 ObjectType newExceptionType = renameType(exceptionTypes[i], map);
394 if (newExceptionType != null) {
395 exceptionTypes[i] = newExceptionType;
396 }
397 }
398 }
399
400 return new MethodSignature(typeParamTypes, paramTypes, returnType, exceptionTypes);
401 }
402
403 private static Type renameType(Type type, ReplacerClassMap map) {
404 if (type instanceof ObjectType) {
405 return renameType((ObjectType)type, map);
406 } else if (type instanceof BaseType) {
407 return renameType((BaseType)type, map);
408 } else {
409 throw new Error("Don't know how to rename type " + type.getClass());
410 }
411 }
412
413 private static ObjectType renameType(ObjectType type, ReplacerClassMap map) {
414 if (type instanceof ArrayType) {
415 return renameType((ArrayType)type, map);
416 } else if (type instanceof ClassType) {
417 return renameType((ClassType)type, map);
418 } else if (type instanceof TypeVariable) {
419 return renameType((TypeVariable)type, map);
420 } else {
421 throw new Error("Don't know how to rename type " + type.getClass());
422 }
423 }
424
425 private static BaseType renameType(BaseType type, ReplacerClassMap map) {
426 // don't have to rename primitives
427 return null;
428 }
429
430 private static TypeVariable renameType(TypeVariable type, ReplacerClassMap map) {
431 // don't have to rename template args
432 return null;
433 }
434
435 private static ClassType renameType(ClassType type, ReplacerClassMap map) {
436
437 // translate type args
438 TypeArgument[] args = type.getTypeArguments();
439 if (args != null) {
440 args = Arrays.copyOf(args, args.length);
441 for (int i=0; i<args.length; i++) {
442 TypeArgument newType = renameType(args[i], map);
443 if (newType != null) {
444 args[i] = newType;
445 }
446 }
447 }
448
449 if (type instanceof NestedClassType) {
450 NestedClassType nestedType = (NestedClassType)type;
451
452 // translate the name
453 String name = getClassName(type);
454 String newName = map.get(name);
455 if (newName != null) {
456 name = new ClassEntry(newName).getInnermostClassName();
457 }
458
459 // translate the parent class too
460 ClassType parent = renameType(nestedType.getDeclaringClass(), map);
461 if (parent == null) {
462 parent = nestedType.getDeclaringClass();
463 }
464
465 return new NestedClassType(parent, name, args);
466 } else {
467
468 // translate the name
469 String name = type.getName();
470 String newName = renameClassName(name, map);
471 if (newName != null) {
472 name = newName;
473 }
474
475 return new ClassType(name, args);
476 }
477 }
478
479 private static String getClassName(ClassType type) {
480 if (type instanceof NestedClassType) {
481 NestedClassType nestedType = (NestedClassType)type;
482 return getClassName(nestedType.getDeclaringClass()) + "$" + Descriptor.toJvmName(type.getName().replace('.', '$'));
483 } else {
484 return Descriptor.toJvmName(type.getName());
485 }
486 }
487
488 private static String renameClassName(String name, ReplacerClassMap map) {
489 String newName = map.get(Descriptor.toJvmName(name));
490 if (newName != null) {
491 return Descriptor.toJavaName(newName);
492 }
493 return null;
494 }
495
496 private static TypeArgument renameType(TypeArgument type, ReplacerClassMap map) {
497 ObjectType subType = type.getType();
498 if (subType != null) {
499 ObjectType newSubType = renameType(subType, map);
500 if (newSubType != null) {
501 switch (type.getKind()) {
502 case ' ': return new TypeArgument(newSubType);
503 case '+': return TypeArgument.subclassOf(newSubType);
504 case '-': return TypeArgument.superOf(newSubType);
505 default:
506 throw new Error("Unknown type kind: " + type.getKind());
507 }
508 }
509 }
510 return null;
511 }
512
513 private static ArrayType renameType(ArrayType type, ReplacerClassMap map) {
514 Type newSubType = renameType(type.getComponentType(), map);
515 if (newSubType != null) {
516 return new ArrayType(type.getDimension(), newSubType);
517 }
518 return null;
519 }
520
521 private static TypeParameter renameType(TypeParameter type, ReplacerClassMap map) {
522
523 ObjectType superclassType = type.getClassBound();
524 if (superclassType != null) {
525 ObjectType newSuperclassType = renameType(superclassType, map);
526 if (newSuperclassType != null) {
527 superclassType = newSuperclassType;
528 }
529 }
530
531 ObjectType[] interfaceTypes = type.getInterfaceBound();
532 if (interfaceTypes != null) {
533 interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length);
534 for (int i=0; i<interfaceTypes.length; i++) {
535 ObjectType newInterfaceType = renameType(interfaceTypes[i], map);
536 if (newInterfaceType != null) {
537 interfaceTypes[i] = newInterfaceType;
538 }
539 }
540 }
541
542 return new TypeParameter(type.getName(), superclassType, interfaceTypes);
543 }
544}
diff --git a/src/cuchaz/enigma/bytecode/ClassTranslator.java b/src/cuchaz/enigma/bytecode/ClassTranslator.java
deleted file mode 100644
index 74024598..00000000
--- a/src/cuchaz/enigma/bytecode/ClassTranslator.java
+++ /dev/null
@@ -1,157 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.CtField;
16import javassist.CtMethod;
17import javassist.bytecode.ConstPool;
18import javassist.bytecode.Descriptor;
19import javassist.bytecode.EnclosingMethodAttribute;
20import javassist.bytecode.SourceFileAttribute;
21import cuchaz.enigma.mapping.BehaviorEntry;
22import cuchaz.enigma.mapping.ClassEntry;
23import cuchaz.enigma.mapping.EntryFactory;
24import cuchaz.enigma.mapping.FieldEntry;
25import cuchaz.enigma.mapping.Signature;
26import cuchaz.enigma.mapping.Translator;
27import cuchaz.enigma.mapping.Type;
28
29public class ClassTranslator {
30
31 private Translator m_translator;
32
33 public ClassTranslator(Translator translator) {
34 m_translator = translator;
35 }
36
37 public void translate(CtClass c) {
38
39 // NOTE: the order of these translations is very important
40
41 // translate all the field and method references in the code by editing the constant pool
42 ConstPool constants = c.getClassFile().getConstPool();
43 ConstPoolEditor editor = new ConstPoolEditor(constants);
44 for (int i = 1; i < constants.getSize(); i++) {
45 switch (constants.getTag(i)) {
46
47 case ConstPool.CONST_Fieldref: {
48
49 // translate the name and type
50 FieldEntry entry = EntryFactory.getFieldEntry(
51 Descriptor.toJvmName(constants.getFieldrefClassName(i)),
52 constants.getFieldrefName(i),
53 constants.getFieldrefType(i)
54 );
55 FieldEntry translatedEntry = m_translator.translateEntry(entry);
56 if (!entry.equals(translatedEntry)) {
57 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getType().toString());
58 }
59 }
60 break;
61
62 case ConstPool.CONST_Methodref:
63 case ConstPool.CONST_InterfaceMethodref: {
64
65 // translate the name and type (ie signature)
66 BehaviorEntry entry = EntryFactory.getBehaviorEntry(
67 Descriptor.toJvmName(editor.getMemberrefClassname(i)),
68 editor.getMemberrefName(i),
69 editor.getMemberrefType(i)
70 );
71 BehaviorEntry translatedEntry = m_translator.translateEntry(entry);
72 if (!entry.equals(translatedEntry)) {
73 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString());
74 }
75 }
76 break;
77 }
78 }
79
80 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
81
82 // translate all the fields
83 for (CtField field : c.getDeclaredFields()) {
84
85 // translate the name
86 FieldEntry entry = EntryFactory.getFieldEntry(field);
87 String translatedName = m_translator.translate(entry);
88 if (translatedName != null) {
89 field.setName(translatedName);
90 }
91
92 // translate the type
93 Type translatedType = m_translator.translateType(entry.getType());
94 field.getFieldInfo().setDescriptor(translatedType.toString());
95 }
96
97 // translate all the methods and constructors
98 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
99
100 BehaviorEntry entry = EntryFactory.getBehaviorEntry(behavior);
101
102 if (behavior instanceof CtMethod) {
103 CtMethod method = (CtMethod)behavior;
104
105 // translate the name
106 String translatedName = m_translator.translate(entry);
107 if (translatedName != null) {
108 method.setName(translatedName);
109 }
110 }
111
112 if (entry.getSignature() != null) {
113 // translate the signature
114 Signature translatedSignature = m_translator.translateSignature(entry.getSignature());
115 behavior.getMethodInfo().setDescriptor(translatedSignature.toString());
116 }
117 }
118
119 // translate the EnclosingMethod attribute
120 EnclosingMethodAttribute enclosingMethodAttr = (EnclosingMethodAttribute)c.getClassFile().getAttribute(EnclosingMethodAttribute.tag);
121 if (enclosingMethodAttr != null) {
122
123 if (enclosingMethodAttr.methodIndex() == 0) {
124 BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(Descriptor.toJvmName(enclosingMethodAttr.className()));
125 BehaviorEntry deobfBehaviorEntry = m_translator.translateEntry(obfBehaviorEntry);
126 c.getClassFile().addAttribute(new EnclosingMethodAttribute(
127 constants,
128 deobfBehaviorEntry.getClassName()
129 ));
130 } else {
131 BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(
132 Descriptor.toJvmName(enclosingMethodAttr.className()),
133 enclosingMethodAttr.methodName(),
134 enclosingMethodAttr.methodDescriptor()
135 );
136 BehaviorEntry deobfBehaviorEntry = m_translator.translateEntry(obfBehaviorEntry);
137 c.getClassFile().addAttribute(new EnclosingMethodAttribute(
138 constants,
139 deobfBehaviorEntry.getClassName(),
140 deobfBehaviorEntry.getName(),
141 deobfBehaviorEntry.getSignature().toString()
142 ));
143 }
144 }
145
146 // translate all the class names referenced in the code
147 // the above code only changed method/field/reference names and types, but not the rest of the class references
148 ClassRenamer.renameClasses(c, m_translator);
149
150 // translate the source file attribute too
151 ClassEntry deobfClassEntry = m_translator.translateEntry(classEntry);
152 if (deobfClassEntry != null) {
153 String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOutermostClassEntry().getSimpleName()) + ".java";
154 c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile));
155 }
156 }
157}
diff --git a/src/cuchaz/enigma/bytecode/ConstPoolEditor.java b/src/cuchaz/enigma/bytecode/ConstPoolEditor.java
deleted file mode 100644
index a00b86b5..00000000
--- a/src/cuchaz/enigma/bytecode/ConstPoolEditor.java
+++ /dev/null
@@ -1,263 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.io.DataInputStream;
14import java.io.DataOutputStream;
15import java.lang.reflect.Constructor;
16import java.lang.reflect.Field;
17import java.lang.reflect.Method;
18import java.util.HashMap;
19
20import javassist.bytecode.ConstPool;
21import javassist.bytecode.Descriptor;
22import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor;
23import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
24import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor;
25
26public class ConstPoolEditor {
27
28 private static Method m_getItem;
29 private static Method m_addItem;
30 private static Method m_addItem0;
31 private static Field m_items;
32 private static Field m_cache;
33 private static Field m_numItems;
34 private static Field m_objects;
35 private static Field m_elements;
36 private static Method m_methodWritePool;
37 private static Constructor<ConstPool> m_constructorPool;
38
39 static {
40 try {
41 m_getItem = ConstPool.class.getDeclaredMethod("getItem", int.class);
42 m_getItem.setAccessible(true);
43
44 m_addItem = ConstPool.class.getDeclaredMethod("addItem", Class.forName("javassist.bytecode.ConstInfo"));
45 m_addItem.setAccessible(true);
46
47 m_addItem0 = ConstPool.class.getDeclaredMethod("addItem0", Class.forName("javassist.bytecode.ConstInfo"));
48 m_addItem0.setAccessible(true);
49
50 m_items = ConstPool.class.getDeclaredField("items");
51 m_items.setAccessible(true);
52
53 m_cache = ConstPool.class.getDeclaredField("itemsCache");
54 m_cache.setAccessible(true);
55
56 m_numItems = ConstPool.class.getDeclaredField("numOfItems");
57 m_numItems.setAccessible(true);
58
59 m_objects = Class.forName("javassist.bytecode.LongVector").getDeclaredField("objects");
60 m_objects.setAccessible(true);
61
62 m_elements = Class.forName("javassist.bytecode.LongVector").getDeclaredField("elements");
63 m_elements.setAccessible(true);
64
65 m_methodWritePool = ConstPool.class.getDeclaredMethod("write", DataOutputStream.class);
66 m_methodWritePool.setAccessible(true);
67
68 m_constructorPool = ConstPool.class.getDeclaredConstructor(DataInputStream.class);
69 m_constructorPool.setAccessible(true);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74
75 private ConstPool m_pool;
76
77 public ConstPoolEditor(ConstPool pool) {
78 m_pool = pool;
79 }
80
81 public void writePool(DataOutputStream out) {
82 try {
83 m_methodWritePool.invoke(m_pool, out);
84 } catch (Exception ex) {
85 throw new Error(ex);
86 }
87 }
88
89 public static ConstPool readPool(DataInputStream in) {
90 try {
91 return m_constructorPool.newInstance(in);
92 } catch (Exception ex) {
93 throw new Error(ex);
94 }
95 }
96
97 public String getMemberrefClassname(int memberrefIndex) {
98 return Descriptor.toJvmName(m_pool.getClassInfo(m_pool.getMemberClass(memberrefIndex)));
99 }
100
101 public String getMemberrefName(int memberrefIndex) {
102 return m_pool.getUtf8Info(m_pool.getNameAndTypeName(m_pool.getMemberNameAndType(memberrefIndex)));
103 }
104
105 public String getMemberrefType(int memberrefIndex) {
106 return m_pool.getUtf8Info(m_pool.getNameAndTypeDescriptor(m_pool.getMemberNameAndType(memberrefIndex)));
107 }
108
109 public ConstInfoAccessor getItem(int index) {
110 try {
111 Object entry = m_getItem.invoke(m_pool, index);
112 if (entry == null) {
113 return null;
114 }
115 return new ConstInfoAccessor(entry);
116 } catch (Exception ex) {
117 throw new Error(ex);
118 }
119 }
120
121 public int addItem(Object item) {
122 try {
123 return (Integer)m_addItem.invoke(m_pool, item);
124 } catch (Exception ex) {
125 throw new Error(ex);
126 }
127 }
128
129 public int addItemForceNew(Object item) {
130 try {
131 return (Integer)m_addItem0.invoke(m_pool, item);
132 } catch (Exception ex) {
133 throw new Error(ex);
134 }
135 }
136
137 @SuppressWarnings("rawtypes")
138 public void removeLastItem() {
139 try {
140 // remove the item from the cache
141 HashMap cache = getCache();
142 if (cache != null) {
143 Object item = getItem(m_pool.getSize() - 1);
144 cache.remove(item);
145 }
146
147 // remove the actual item
148 // based off of LongVector.addElement()
149 Object items = m_items.get(m_pool);
150 Object[][] objects = (Object[][])m_objects.get(items);
151 int numElements = (Integer)m_elements.get(items) - 1;
152 int nth = numElements >> 7;
153 int offset = numElements & (128 - 1);
154 objects[nth][offset] = null;
155
156 // decrement the number of items
157 m_elements.set(items, numElements);
158 m_numItems.set(m_pool, (Integer)m_numItems.get(m_pool) - 1);
159 } catch (Exception ex) {
160 throw new Error(ex);
161 }
162 }
163
164 @SuppressWarnings("rawtypes")
165 public HashMap getCache() {
166 try {
167 return (HashMap)m_cache.get(m_pool);
168 } catch (Exception ex) {
169 throw new Error(ex);
170 }
171 }
172
173 @SuppressWarnings({ "rawtypes", "unchecked" })
174 public void changeMemberrefNameAndType(int memberrefIndex, String newName, String newType) {
175 // NOTE: when changing values, we always need to copy-on-write
176 try {
177 // get the memberref item
178 Object item = getItem(memberrefIndex).getItem();
179
180 // update the cache
181 HashMap cache = getCache();
182 if (cache != null) {
183 cache.remove(item);
184 }
185
186 new MemberRefInfoAccessor(item).setNameAndTypeIndex(m_pool.addNameAndTypeInfo(newName, newType));
187
188 // update the cache
189 if (cache != null) {
190 cache.put(item, item);
191 }
192 } catch (Exception ex) {
193 throw new Error(ex);
194 }
195
196 // make sure the change worked
197 assert (newName.equals(getMemberrefName(memberrefIndex)));
198 assert (newType.equals(getMemberrefType(memberrefIndex)));
199 }
200
201 @SuppressWarnings({ "rawtypes", "unchecked" })
202 public void changeClassName(int classNameIndex, String newName) {
203 // NOTE: when changing values, we always need to copy-on-write
204 try {
205 // get the class item
206 Object item = getItem(classNameIndex).getItem();
207
208 // update the cache
209 HashMap cache = getCache();
210 if (cache != null) {
211 cache.remove(item);
212 }
213
214 // add the new name and repoint the name-and-type to it
215 new ClassInfoAccessor(item).setNameIndex(m_pool.addUtf8Info(newName));
216
217 // update the cache
218 if (cache != null) {
219 cache.put(item, item);
220 }
221 } catch (Exception ex) {
222 throw new Error(ex);
223 }
224 }
225
226 public static ConstPool newConstPool() {
227 // const pool expects the name of a class to initialize itself
228 // but we want an empty pool
229 // so give it a bogus name, and then clear the entries afterwards
230 ConstPool pool = new ConstPool("a");
231
232 ConstPoolEditor editor = new ConstPoolEditor(pool);
233 int size = pool.getSize();
234 for (int i = 0; i < size - 1; i++) {
235 editor.removeLastItem();
236 }
237
238 // make sure the pool is actually empty
239 // although, in this case "empty" means one thing in it
240 // the JVM spec says index 0 should be reserved
241 assert (pool.getSize() == 1);
242 assert (editor.getItem(0) == null);
243 assert (editor.getItem(1) == null);
244 assert (editor.getItem(2) == null);
245 assert (editor.getItem(3) == null);
246
247 // also, clear the cache
248 editor.getCache().clear();
249
250 return pool;
251 }
252
253 public String dump() {
254 StringBuilder buf = new StringBuilder();
255 for (int i = 1; i < m_pool.getSize(); i++) {
256 buf.append(String.format("%4d", i));
257 buf.append(" ");
258 buf.append(getItem(i).toString());
259 buf.append("\n");
260 }
261 return buf.toString();
262 }
263}
diff --git a/src/cuchaz/enigma/bytecode/InfoType.java b/src/cuchaz/enigma/bytecode/InfoType.java
deleted file mode 100644
index 08f2b3e2..00000000
--- a/src/cuchaz/enigma/bytecode/InfoType.java
+++ /dev/null
@@ -1,317 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.Collection;
14import java.util.List;
15import java.util.Map;
16
17import com.google.common.collect.Lists;
18import com.google.common.collect.Maps;
19
20import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor;
21import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
22import cuchaz.enigma.bytecode.accessors.InvokeDynamicInfoAccessor;
23import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor;
24import cuchaz.enigma.bytecode.accessors.MethodHandleInfoAccessor;
25import cuchaz.enigma.bytecode.accessors.MethodTypeInfoAccessor;
26import cuchaz.enigma.bytecode.accessors.NameAndTypeInfoAccessor;
27import cuchaz.enigma.bytecode.accessors.StringInfoAccessor;
28
29public enum InfoType {
30
31 Utf8Info( 1, 0 ),
32 IntegerInfo( 3, 0 ),
33 FloatInfo( 4, 0 ),
34 LongInfo( 5, 0 ),
35 DoubleInfo( 6, 0 ),
36 ClassInfo( 7, 1 ) {
37
38 @Override
39 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
40 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
41 gatherIndexTree(indices, editor, accessor.getNameIndex());
42 }
43
44 @Override
45 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
46 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
47 accessor.setNameIndex(remapIndex(map, accessor.getNameIndex()));
48 }
49
50 @Override
51 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
52 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
53 ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex());
54 return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag();
55 }
56 },
57 StringInfo( 8, 1 ) {
58
59 @Override
60 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
61 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
62 gatherIndexTree(indices, editor, accessor.getStringIndex());
63 }
64
65 @Override
66 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
67 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
68 accessor.setStringIndex(remapIndex(map, accessor.getStringIndex()));
69 }
70
71 @Override
72 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
73 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
74 ConstInfoAccessor stringEntry = pool.getItem(accessor.getStringIndex());
75 return stringEntry != null && stringEntry.getTag() == Utf8Info.getTag();
76 }
77 },
78 FieldRefInfo( 9, 2 ) {
79
80 @Override
81 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
82 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
83 gatherIndexTree(indices, editor, accessor.getClassIndex());
84 gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex());
85 }
86
87 @Override
88 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
89 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
90 accessor.setClassIndex(remapIndex(map, accessor.getClassIndex()));
91 accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex()));
92 }
93
94 @Override
95 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
96 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
97 ConstInfoAccessor classEntry = pool.getItem(accessor.getClassIndex());
98 ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex());
99 return classEntry != null && classEntry.getTag() == ClassInfo.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag();
100 }
101 },
102 // same as FieldRefInfo
103 MethodRefInfo( 10, 2 ) {
104
105 @Override
106 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
107 FieldRefInfo.gatherIndexTree(indices, editor, entry);
108 }
109
110 @Override
111 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
112 FieldRefInfo.remapIndices(map, entry);
113 }
114
115 @Override
116 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
117 return FieldRefInfo.subIndicesAreValid(entry, pool);
118 }
119 },
120 // same as FieldRefInfo
121 InterfaceMethodRefInfo( 11, 2 ) {
122
123 @Override
124 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
125 FieldRefInfo.gatherIndexTree(indices, editor, entry);
126 }
127
128 @Override
129 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
130 FieldRefInfo.remapIndices(map, entry);
131 }
132
133 @Override
134 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
135 return FieldRefInfo.subIndicesAreValid(entry, pool);
136 }
137 },
138 NameAndTypeInfo( 12, 1 ) {
139
140 @Override
141 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
142 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
143 gatherIndexTree(indices, editor, accessor.getNameIndex());
144 gatherIndexTree(indices, editor, accessor.getTypeIndex());
145 }
146
147 @Override
148 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
149 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
150 accessor.setNameIndex(remapIndex(map, accessor.getNameIndex()));
151 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
152 }
153
154 @Override
155 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
156 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
157 ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex());
158 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
159 return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag() && typeEntry != null && typeEntry.getTag() == Utf8Info.getTag();
160 }
161 },
162 MethodHandleInfo( 15, 3 ) {
163
164 @Override
165 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
166 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
167 gatherIndexTree(indices, editor, accessor.getTypeIndex());
168 gatherIndexTree(indices, editor, accessor.getMethodRefIndex());
169 }
170
171 @Override
172 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
173 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
174 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
175 accessor.setMethodRefIndex(remapIndex(map, accessor.getMethodRefIndex()));
176 }
177
178 @Override
179 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
180 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
181 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
182 ConstInfoAccessor methodRefEntry = pool.getItem(accessor.getMethodRefIndex());
183 return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag() && methodRefEntry != null && methodRefEntry.getTag() == MethodRefInfo.getTag();
184 }
185 },
186 MethodTypeInfo( 16, 1 ) {
187
188 @Override
189 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
190 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
191 gatherIndexTree(indices, editor, accessor.getTypeIndex());
192 }
193
194 @Override
195 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
196 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
197 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
198 }
199
200 @Override
201 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
202 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
203 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
204 return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag();
205 }
206 },
207 InvokeDynamicInfo( 18, 2 ) {
208
209 @Override
210 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
211 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
212 gatherIndexTree(indices, editor, accessor.getBootstrapIndex());
213 gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex());
214 }
215
216 @Override
217 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
218 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
219 accessor.setBootstrapIndex(remapIndex(map, accessor.getBootstrapIndex()));
220 accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex()));
221 }
222
223 @Override
224 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
225 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
226 ConstInfoAccessor bootstrapEntry = pool.getItem(accessor.getBootstrapIndex());
227 ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex());
228 return bootstrapEntry != null && bootstrapEntry.getTag() == Utf8Info.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag();
229 }
230 };
231
232 private static Map<Integer,InfoType> m_types;
233
234 static {
235 m_types = Maps.newTreeMap();
236 for (InfoType type : values()) {
237 m_types.put(type.getTag(), type);
238 }
239 }
240
241 private int m_tag;
242 private int m_level;
243
244 private InfoType(int tag, int level) {
245 m_tag = tag;
246 m_level = level;
247 }
248
249 public int getTag() {
250 return m_tag;
251 }
252
253 public int getLevel() {
254 return m_level;
255 }
256
257 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
258 // by default, do nothing
259 }
260
261 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
262 // by default, do nothing
263 }
264
265 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
266 // by default, everything is good
267 return true;
268 }
269
270 public boolean selfIndexIsValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
271 ConstInfoAccessor entryCheck = pool.getItem(entry.getIndex());
272 if (entryCheck == null) {
273 return false;
274 }
275 return entryCheck.getItem().equals(entry.getItem());
276 }
277
278 public static InfoType getByTag(int tag) {
279 return m_types.get(tag);
280 }
281
282 public static List<InfoType> getByLevel(int level) {
283 List<InfoType> types = Lists.newArrayList();
284 for (InfoType type : values()) {
285 if (type.getLevel() == level) {
286 types.add(type);
287 }
288 }
289 return types;
290 }
291
292 public static List<InfoType> getSortedByLevel() {
293 List<InfoType> types = Lists.newArrayList();
294 types.addAll(getByLevel(0));
295 types.addAll(getByLevel(1));
296 types.addAll(getByLevel(2));
297 types.addAll(getByLevel(3));
298 return types;
299 }
300
301 public static void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, int index) {
302 // add own index
303 indices.add(index);
304
305 // recurse
306 ConstInfoAccessor entry = editor.getItem(index);
307 entry.getType().gatherIndexTree(indices, editor, entry);
308 }
309
310 private static int remapIndex(Map<Integer,Integer> map, int index) {
311 Integer newIndex = map.get(index);
312 if (newIndex == null) {
313 newIndex = index;
314 }
315 return newIndex;
316 }
317}
diff --git a/src/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/cuchaz/enigma/bytecode/InnerClassWriter.java
deleted file mode 100644
index bdb1b5df..00000000
--- a/src/cuchaz/enigma/bytecode/InnerClassWriter.java
+++ /dev/null
@@ -1,132 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.Collection;
14import java.util.List;
15
16import com.google.common.collect.Lists;
17
18import javassist.CtClass;
19import javassist.bytecode.AccessFlag;
20import javassist.bytecode.ConstPool;
21import javassist.bytecode.EnclosingMethodAttribute;
22import javassist.bytecode.InnerClassesAttribute;
23import cuchaz.enigma.analysis.JarIndex;
24import cuchaz.enigma.mapping.BehaviorEntry;
25import cuchaz.enigma.mapping.ClassEntry;
26import cuchaz.enigma.mapping.EntryFactory;
27
28public class InnerClassWriter {
29
30 private JarIndex m_index;
31
32 public InnerClassWriter(JarIndex index) {
33 m_index = index;
34 }
35
36 public void write(CtClass c) {
37
38 // don't change anything if there's already an attribute there
39 InnerClassesAttribute oldAttr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
40 if (oldAttr != null) {
41 // bail!
42 return;
43 }
44
45 ClassEntry obfClassEntry = EntryFactory.getClassEntry(c);
46 List<ClassEntry> obfClassChain = m_index.getObfClassChain(obfClassEntry);
47
48 boolean isInnerClass = obfClassChain.size() > 1;
49 if (isInnerClass) {
50
51 // it's an inner class, rename it to the fully qualified name
52 c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName());
53
54 BehaviorEntry caller = m_index.getAnonymousClassCaller(obfClassEntry);
55 if (caller != null) {
56
57 // write the enclosing method attribute
58 if (caller.getName().equals("<clinit>")) {
59 c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName()));
60 } else {
61 c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature().toString()));
62 }
63 }
64 }
65
66 // does this class have any inner classes?
67 Collection<ClassEntry> obfInnerClassEntries = m_index.getInnerClasses(obfClassEntry);
68
69 if (isInnerClass || !obfInnerClassEntries.isEmpty()) {
70
71 // create an inner class attribute
72 InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool());
73 c.getClassFile().addAttribute(attr);
74
75 // write the ancestry, but not the outermost class
76 for (int i=1; i<obfClassChain.size(); i++) {
77 ClassEntry obfInnerClassEntry = obfClassChain.get(i);
78 writeInnerClass(attr, obfClassChain, obfInnerClassEntry);
79
80 // update references to use the fully qualified inner class name
81 c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(obfClassChain).getName());
82 }
83
84 // write the inner classes
85 for (ClassEntry obfInnerClassEntry : obfInnerClassEntries) {
86
87 // extend the class chain
88 List<ClassEntry> extendedObfClassChain = Lists.newArrayList(obfClassChain);
89 extendedObfClassChain.add(obfInnerClassEntry);
90
91 writeInnerClass(attr, extendedObfClassChain, obfInnerClassEntry);
92
93 // update references to use the fully qualified inner class name
94 c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName());
95 }
96 }
97 }
98
99 private void writeInnerClass(InnerClassesAttribute attr, List<ClassEntry> obfClassChain, ClassEntry obfClassEntry) {
100
101 // get the new inner class name
102 ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain);
103 ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry();
104
105 // here's what the JVM spec says about the InnerClasses attribute
106 // append(inner, parent, 0 if anonymous else simple name, flags);
107
108 // update the attribute with this inner class
109 ConstPool constPool = attr.getConstPool();
110 int innerClassIndex = constPool.addClassInfo(obfInnerClassEntry.getName());
111 int parentClassIndex = constPool.addClassInfo(obfOuterClassEntry.getName());
112 int innerClassNameIndex = 0;
113 int accessFlags = AccessFlag.PUBLIC;
114 // TODO: need to figure out if we can put static or not
115 if (!m_index.isAnonymousClass(obfClassEntry)) {
116 innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnermostClassName());
117 }
118
119 attr.append(innerClassIndex, parentClassIndex, innerClassNameIndex, accessFlags);
120
121 /* DEBUG
122 System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)",
123 obfClassEntry,
124 attr.innerClass(attr.tableLength() - 1),
125 attr.outerClass(attr.tableLength() - 1),
126 attr.innerName(attr.tableLength() - 1),
127 Constants.NonePackage + "/" + obfInnerClassName,
128 obfClassEntry.getName()
129 ));
130 */
131 }
132}
diff --git a/src/cuchaz/enigma/bytecode/LocalVariableRenamer.java b/src/cuchaz/enigma/bytecode/LocalVariableRenamer.java
deleted file mode 100644
index ae0455f9..00000000
--- a/src/cuchaz/enigma/bytecode/LocalVariableRenamer.java
+++ /dev/null
@@ -1,123 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.bytecode.ByteArray;
16import javassist.bytecode.CodeAttribute;
17import javassist.bytecode.ConstPool;
18import javassist.bytecode.LocalVariableAttribute;
19import javassist.bytecode.LocalVariableTypeAttribute;
20import cuchaz.enigma.mapping.ArgumentEntry;
21import cuchaz.enigma.mapping.BehaviorEntry;
22import cuchaz.enigma.mapping.EntryFactory;
23import cuchaz.enigma.mapping.Translator;
24
25
26public class LocalVariableRenamer {
27
28 private Translator m_translator;
29
30 public LocalVariableRenamer(Translator translator) {
31 m_translator = translator;
32 }
33
34 public void rename(CtClass c) {
35 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
36
37 // if there's a local variable table, just rename everything to v1, v2, v3, ... for now
38 CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute();
39 if (codeAttribute == null) {
40 continue;
41 }
42
43 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
44 ConstPool constants = c.getClassFile().getConstPool();
45
46 LocalVariableAttribute table = (LocalVariableAttribute)codeAttribute.getAttribute(LocalVariableAttribute.tag);
47 if (table != null) {
48 renameLVT(behaviorEntry, constants, table);
49 }
50
51 LocalVariableTypeAttribute typeTable = (LocalVariableTypeAttribute)codeAttribute.getAttribute(LocalVariableAttribute.typeTag);
52 if (typeTable != null) {
53 renameLVTT(typeTable, table);
54 }
55 }
56 }
57
58 // DEBUG
59 @SuppressWarnings("unused")
60 private void dumpTable(LocalVariableAttribute table) {
61 for (int i=0; i<table.tableLength(); i++) {
62 System.out.println(String.format("\t%d (%d): %s %s",
63 i, table.index(i), table.variableName(i), table.descriptor(i)
64 ));
65 }
66 }
67
68 private void renameLVT(BehaviorEntry behaviorEntry, ConstPool constants, LocalVariableAttribute table) {
69
70 // skip empty tables
71 if (table.tableLength() <= 0) {
72 return;
73 }
74
75 // where do we start counting variables?
76 int starti = 0;
77 if (table.variableName(0).equals("this")) {
78 // skip the "this" variable
79 starti = 1;
80 }
81
82 // rename method arguments first
83 int numArgs = 0;
84 if (behaviorEntry.getSignature() != null) {
85 numArgs = behaviorEntry.getSignature().getArgumentTypes().size();
86 for (int i=starti; i<starti + numArgs && i<table.tableLength(); i++) {
87 int argi = i - starti;
88 String argName = m_translator.translate(new ArgumentEntry(behaviorEntry, argi, ""));
89 if (argName == null) {
90 argName = "a" + (argi + 1);
91 }
92 renameVariable(table, i, constants.addUtf8Info(argName));
93 }
94 }
95
96 // then rename the rest of the args, if any
97 for (int i=starti + numArgs; i<table.tableLength(); i++) {
98 int firstIndex = table.index(starti + numArgs);
99 renameVariable(table, i, constants.addUtf8Info("v" + (table.index(i) - firstIndex + 1)));
100 }
101 }
102
103 private void renameLVTT(LocalVariableTypeAttribute typeTable, LocalVariableAttribute table) {
104 // rename args to the same names as in the LVT
105 for (int i=0; i<typeTable.tableLength(); i++) {
106 renameVariable(typeTable, i, getNameIndex(table, typeTable.index(i)));
107 }
108 }
109
110 private void renameVariable(LocalVariableAttribute table, int i, int stringId) {
111 // based off of LocalVariableAttribute.nameIndex()
112 ByteArray.write16bit(stringId, table.get(), i*10 + 6);
113 }
114
115 private int getNameIndex(LocalVariableAttribute table, int index) {
116 for (int i=0; i<table.tableLength(); i++) {
117 if (table.index(i) == index) {
118 return table.nameIndex(i);
119 }
120 }
121 return 0;
122 }
123}
diff --git a/src/cuchaz/enigma/bytecode/MethodParameterWriter.java b/src/cuchaz/enigma/bytecode/MethodParameterWriter.java
deleted file mode 100644
index 0bdf47a4..00000000
--- a/src/cuchaz/enigma/bytecode/MethodParameterWriter.java
+++ /dev/null
@@ -1,70 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.ArrayList;
14import java.util.List;
15
16import javassist.CtBehavior;
17import javassist.CtClass;
18import javassist.bytecode.CodeAttribute;
19import javassist.bytecode.LocalVariableAttribute;
20import cuchaz.enigma.mapping.ArgumentEntry;
21import cuchaz.enigma.mapping.BehaviorEntry;
22import cuchaz.enigma.mapping.EntryFactory;
23import cuchaz.enigma.mapping.Signature;
24import cuchaz.enigma.mapping.Translator;
25
26public class MethodParameterWriter {
27
28 private Translator m_translator;
29
30 public MethodParameterWriter(Translator translator) {
31 m_translator = translator;
32 }
33
34 public void writeMethodArguments(CtClass c) {
35
36 // Procyon will read method arguments from the "MethodParameters" attribute, so write those
37 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
38
39 // if there's a local variable table here, don't write a MethodParameters attribute
40 // let the local variable writer deal with it instead
41 // procyon starts doing really weird things if we give it both attributes
42 CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute();
43 if (codeAttribute != null && codeAttribute.getAttribute(LocalVariableAttribute.tag) != null) {
44 continue;
45 }
46
47 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
48
49 // get the number of arguments
50 Signature signature = behaviorEntry.getSignature();
51 if (signature == null) {
52 // static initializers have no signatures, or arguments
53 continue;
54 }
55 int numParams = signature.getArgumentTypes().size();
56 if (numParams <= 0) {
57 continue;
58 }
59
60 // get the list of argument names
61 List<String> names = new ArrayList<String>(numParams);
62 for (int i = 0; i < numParams; i++) {
63 names.add(m_translator.translate(new ArgumentEntry(behaviorEntry, i, "")));
64 }
65
66 // save the mappings to the class
67 MethodParametersAttribute.updateClass(behavior.getMethodInfo(), names);
68 }
69 }
70}
diff --git a/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java b/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java
deleted file mode 100644
index 512e65a0..00000000
--- a/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java
+++ /dev/null
@@ -1,86 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.io.ByteArrayOutputStream;
14import java.io.DataOutputStream;
15import java.io.IOException;
16import java.util.ArrayList;
17import java.util.List;
18
19import javassist.bytecode.AttributeInfo;
20import javassist.bytecode.ConstPool;
21import javassist.bytecode.MethodInfo;
22
23public class MethodParametersAttribute extends AttributeInfo {
24
25 private MethodParametersAttribute(ConstPool pool, List<Integer> parameterNameIndices) {
26 super(pool, "MethodParameters", writeStruct(parameterNameIndices));
27 }
28
29 public static void updateClass(MethodInfo info, List<String> names) {
30
31 // add the names to the class const pool
32 ConstPool constPool = info.getConstPool();
33 List<Integer> parameterNameIndices = new ArrayList<Integer>();
34 for (String name : names) {
35 if (name != null) {
36 parameterNameIndices.add(constPool.addUtf8Info(name));
37 } else {
38 parameterNameIndices.add(0);
39 }
40 }
41
42 // add the attribute to the method
43 info.addAttribute(new MethodParametersAttribute(constPool, parameterNameIndices));
44 }
45
46 private static byte[] writeStruct(List<Integer> parameterNameIndices) {
47 // JVM 8 Spec says the struct looks like this:
48 // http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.24
49 // uint8 num_params
50 // for each param:
51 // uint16 name_index -> points to UTF8 entry in constant pool, or 0 for no entry
52 // uint16 access_flags -> don't care, just set to 0
53
54 ByteArrayOutputStream buf = new ByteArrayOutputStream();
55 DataOutputStream out = new DataOutputStream(buf);
56
57 // NOTE: java hates unsigned integers, so we have to be careful here
58 // the writeShort(), writeByte() methods will read 16,8 low-order bits from the int argument
59 // as long as the int argument is in range of the unsigned short/byte type, it will be written as an unsigned short/byte
60 // if the int is out of range, the byte stream won't look the way we want and weird things will happen
61 final int SIZEOF_UINT8 = 1;
62 final int SIZEOF_UINT16 = 2;
63 final int MAX_UINT8 = (1 << 8) - 1;
64 final int MAX_UINT16 = (1 << 16) - 1;
65
66 try {
67 assert (parameterNameIndices.size() >= 0 && parameterNameIndices.size() <= MAX_UINT8);
68 out.writeByte(parameterNameIndices.size());
69
70 for (Integer index : parameterNameIndices) {
71 assert (index >= 0 && index <= MAX_UINT16);
72 out.writeShort(index);
73
74 // just write 0 for the access flags
75 out.writeShort(0);
76 }
77
78 out.close();
79 byte[] data = buf.toByteArray();
80 assert (data.length == SIZEOF_UINT8 + parameterNameIndices.size() * (SIZEOF_UINT16 + SIZEOF_UINT16));
81 return data;
82 } catch (IOException ex) {
83 throw new Error(ex);
84 }
85 }
86}
diff --git a/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
deleted file mode 100644
index 9072c29a..00000000
--- a/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
+++ /dev/null
@@ -1,55 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class ClassInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_nameIndex;
19
20 static {
21 try {
22 m_class = Class.forName("javassist.bytecode.ClassInfo");
23 m_nameIndex = m_class.getDeclaredField("name");
24 m_nameIndex.setAccessible(true);
25 } catch (Exception ex) {
26 throw new Error(ex);
27 }
28 }
29
30 public static boolean isType(ConstInfoAccessor accessor) {
31 return m_class.isAssignableFrom(accessor.getItem().getClass());
32 }
33
34 private Object m_item;
35
36 public ClassInfoAccessor(Object item) {
37 m_item = item;
38 }
39
40 public int getNameIndex() {
41 try {
42 return (Integer)m_nameIndex.get(m_item);
43 } catch (Exception ex) {
44 throw new Error(ex);
45 }
46 }
47
48 public void setNameIndex(int val) {
49 try {
50 m_nameIndex.set(m_item, val);
51 } catch (Exception ex) {
52 throw new Error(ex);
53 }
54 }
55}
diff --git a/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java
deleted file mode 100644
index ede04738..00000000
--- a/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java
+++ /dev/null
@@ -1,156 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.io.ByteArrayInputStream;
14import java.io.ByteArrayOutputStream;
15import java.io.DataInputStream;
16import java.io.DataOutputStream;
17import java.io.IOException;
18import java.io.PrintWriter;
19import java.lang.reflect.Constructor;
20import java.lang.reflect.Field;
21import java.lang.reflect.Method;
22
23import cuchaz.enigma.bytecode.InfoType;
24
25public class ConstInfoAccessor {
26
27 private static Class<?> m_class;
28 private static Field m_index;
29 private static Method m_getTag;
30
31 static {
32 try {
33 m_class = Class.forName("javassist.bytecode.ConstInfo");
34 m_index = m_class.getDeclaredField("index");
35 m_index.setAccessible(true);
36 m_getTag = m_class.getMethod("getTag");
37 m_getTag.setAccessible(true);
38 } catch (Exception ex) {
39 throw new Error(ex);
40 }
41 }
42
43 private Object m_item;
44
45 public ConstInfoAccessor(Object item) {
46 if (item == null) {
47 throw new IllegalArgumentException("item cannot be null!");
48 }
49 m_item = item;
50 }
51
52 public ConstInfoAccessor(DataInputStream in) throws IOException {
53 try {
54 // read the entry
55 String className = in.readUTF();
56 int oldIndex = in.readInt();
57
58 // NOTE: ConstInfo instances write a type id (a "tag"), but they don't read it back
59 // so we have to read it here
60 in.readByte();
61
62 Constructor<?> constructor = Class.forName(className).getConstructor(DataInputStream.class, int.class);
63 constructor.setAccessible(true);
64 m_item = constructor.newInstance(in, oldIndex);
65 } catch (IOException ex) {
66 throw ex;
67 } catch (Exception ex) {
68 throw new Error(ex);
69 }
70 }
71
72 public Object getItem() {
73 return m_item;
74 }
75
76 public int getIndex() {
77 try {
78 return (Integer)m_index.get(m_item);
79 } catch (Exception ex) {
80 throw new Error(ex);
81 }
82 }
83
84 public void setIndex(int val) {
85 try {
86 m_index.set(m_item, val);
87 } catch (Exception ex) {
88 throw new Error(ex);
89 }
90 }
91
92 public int getTag() {
93 try {
94 return (Integer)m_getTag.invoke(m_item);
95 } catch (Exception ex) {
96 throw new Error(ex);
97 }
98 }
99
100 public ConstInfoAccessor copy() {
101 return new ConstInfoAccessor(copyItem());
102 }
103
104 public Object copyItem() {
105 // I don't know of a simpler way to copy one of these silly things...
106 try {
107 // serialize the item
108 ByteArrayOutputStream buf = new ByteArrayOutputStream();
109 DataOutputStream out = new DataOutputStream(buf);
110 write(out);
111
112 // deserialize the item
113 DataInputStream in = new DataInputStream(new ByteArrayInputStream(buf.toByteArray()));
114 Object item = new ConstInfoAccessor(in).getItem();
115 in.close();
116
117 return item;
118 } catch (Exception ex) {
119 throw new Error(ex);
120 }
121 }
122
123 public void write(DataOutputStream out) throws IOException {
124 try {
125 out.writeUTF(m_item.getClass().getName());
126 out.writeInt(getIndex());
127
128 Method method = m_item.getClass().getMethod("write", DataOutputStream.class);
129 method.setAccessible(true);
130 method.invoke(m_item, out);
131 } catch (IOException ex) {
132 throw ex;
133 } catch (Exception ex) {
134 throw new Error(ex);
135 }
136 }
137
138 @Override
139 public String toString() {
140 try {
141 ByteArrayOutputStream buf = new ByteArrayOutputStream();
142 PrintWriter out = new PrintWriter(buf);
143 Method print = m_item.getClass().getMethod("print", PrintWriter.class);
144 print.setAccessible(true);
145 print.invoke(m_item, out);
146 out.close();
147 return buf.toString().replace("\n", "");
148 } catch (Exception ex) {
149 throw new Error(ex);
150 }
151 }
152
153 public InfoType getType() {
154 return InfoType.getByTag(getTag());
155 }
156}
diff --git a/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java
deleted file mode 100644
index 82af0b99..00000000
--- a/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java
+++ /dev/null
@@ -1,74 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class InvokeDynamicInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_bootstrapIndex;
19 private static Field m_nameAndTypeIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.InvokeDynamicInfo");
24 m_bootstrapIndex = m_class.getDeclaredField("bootstrap");
25 m_bootstrapIndex.setAccessible(true);
26 m_nameAndTypeIndex = m_class.getDeclaredField("nameAndType");
27 m_nameAndTypeIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public InvokeDynamicInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getBootstrapIndex() {
44 try {
45 return (Integer)m_bootstrapIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setBootstrapIndex(int val) {
52 try {
53 m_bootstrapIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getNameAndTypeIndex() {
60 try {
61 return (Integer)m_nameAndTypeIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setNameAndTypeIndex(int val) {
68 try {
69 m_nameAndTypeIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java
deleted file mode 100644
index 71ee5b73..00000000
--- a/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java
+++ /dev/null
@@ -1,74 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class MemberRefInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_classIndex;
19 private static Field m_nameAndTypeIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.MemberrefInfo");
24 m_classIndex = m_class.getDeclaredField("classIndex");
25 m_classIndex.setAccessible(true);
26 m_nameAndTypeIndex = m_class.getDeclaredField("nameAndTypeIndex");
27 m_nameAndTypeIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public MemberRefInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getClassIndex() {
44 try {
45 return (Integer)m_classIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setClassIndex(int val) {
52 try {
53 m_classIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getNameAndTypeIndex() {
60 try {
61 return (Integer)m_nameAndTypeIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setNameAndTypeIndex(int val) {
68 try {
69 m_nameAndTypeIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java
deleted file mode 100644
index 172b0c51..00000000
--- a/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java
+++ /dev/null
@@ -1,74 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class MethodHandleInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_kindIndex;
19 private static Field m_indexIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.MethodHandleInfo");
24 m_kindIndex = m_class.getDeclaredField("refKind");
25 m_kindIndex.setAccessible(true);
26 m_indexIndex = m_class.getDeclaredField("refIndex");
27 m_indexIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public MethodHandleInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getTypeIndex() {
44 try {
45 return (Integer)m_kindIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setTypeIndex(int val) {
52 try {
53 m_kindIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getMethodRefIndex() {
60 try {
61 return (Integer)m_indexIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setMethodRefIndex(int val) {
68 try {
69 m_indexIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java
deleted file mode 100644
index 0099a843..00000000
--- a/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java
+++ /dev/null
@@ -1,55 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class MethodTypeInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_descriptorIndex;
19
20 static {
21 try {
22 m_class = Class.forName("javassist.bytecode.MethodTypeInfo");
23 m_descriptorIndex = m_class.getDeclaredField("descriptor");
24 m_descriptorIndex.setAccessible(true);
25 } catch (Exception ex) {
26 throw new Error(ex);
27 }
28 }
29
30 public static boolean isType(ConstInfoAccessor accessor) {
31 return m_class.isAssignableFrom(accessor.getItem().getClass());
32 }
33
34 private Object m_item;
35
36 public MethodTypeInfoAccessor(Object item) {
37 m_item = item;
38 }
39
40 public int getTypeIndex() {
41 try {
42 return (Integer)m_descriptorIndex.get(m_item);
43 } catch (Exception ex) {
44 throw new Error(ex);
45 }
46 }
47
48 public void setTypeIndex(int val) {
49 try {
50 m_descriptorIndex.set(m_item, val);
51 } catch (Exception ex) {
52 throw new Error(ex);
53 }
54 }
55}
diff --git a/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java
deleted file mode 100644
index 3ecc1297..00000000
--- a/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java
+++ /dev/null
@@ -1,74 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class NameAndTypeInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_nameIndex;
19 private static Field m_typeIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.NameAndTypeInfo");
24 m_nameIndex = m_class.getDeclaredField("memberName");
25 m_nameIndex.setAccessible(true);
26 m_typeIndex = m_class.getDeclaredField("typeDescriptor");
27 m_typeIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public NameAndTypeInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getNameIndex() {
44 try {
45 return (Integer)m_nameIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setNameIndex(int val) {
52 try {
53 m_nameIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getTypeIndex() {
60 try {
61 return (Integer)m_typeIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setTypeIndex(int val) {
68 try {
69 m_typeIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java
deleted file mode 100644
index f150612e..00000000
--- a/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java
+++ /dev/null
@@ -1,55 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class StringInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_stringIndex;
19
20 static {
21 try {
22 m_class = Class.forName("javassist.bytecode.StringInfo");
23 m_stringIndex = m_class.getDeclaredField("string");
24 m_stringIndex.setAccessible(true);
25 } catch (Exception ex) {
26 throw new Error(ex);
27 }
28 }
29
30 public static boolean isType(ConstInfoAccessor accessor) {
31 return m_class.isAssignableFrom(accessor.getItem().getClass());
32 }
33
34 private Object m_item;
35
36 public StringInfoAccessor(Object item) {
37 m_item = item;
38 }
39
40 public int getStringIndex() {
41 try {
42 return (Integer)m_stringIndex.get(m_item);
43 } catch (Exception ex) {
44 throw new Error(ex);
45 }
46 }
47
48 public void setStringIndex(int val) {
49 try {
50 m_stringIndex.set(m_item, val);
51 } catch (Exception ex) {
52 throw new Error(ex);
53 }
54 }
55}
diff --git a/src/cuchaz/enigma/convert/ClassForest.java b/src/cuchaz/enigma/convert/ClassForest.java
deleted file mode 100644
index 0407730e..00000000
--- a/src/cuchaz/enigma/convert/ClassForest.java
+++ /dev/null
@@ -1,60 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Collection;
14
15import com.google.common.collect.HashMultimap;
16import com.google.common.collect.Multimap;
17
18import cuchaz.enigma.mapping.ClassEntry;
19
20
21public class ClassForest {
22
23 private ClassIdentifier m_identifier;
24 private Multimap<ClassIdentity,ClassEntry> m_forest;
25
26 public ClassForest(ClassIdentifier identifier) {
27 m_identifier = identifier;
28 m_forest = HashMultimap.create();
29 }
30
31 public void addAll(Iterable<ClassEntry> entries) {
32 for (ClassEntry entry : entries) {
33 add(entry);
34 }
35 }
36
37 public void add(ClassEntry entry) {
38 try {
39 m_forest.put(m_identifier.identify(entry), entry);
40 } catch (ClassNotFoundException ex) {
41 throw new Error("Unable to find class " + entry.getName());
42 }
43 }
44
45 public Collection<ClassIdentity> identities() {
46 return m_forest.keySet();
47 }
48
49 public Collection<ClassEntry> classes() {
50 return m_forest.values();
51 }
52
53 public Collection<ClassEntry> getClasses(ClassIdentity identity) {
54 return m_forest.get(identity);
55 }
56
57 public boolean containsIdentity(ClassIdentity identity) {
58 return m_forest.containsKey(identity);
59 }
60}
diff --git a/src/cuchaz/enigma/convert/ClassIdentifier.java b/src/cuchaz/enigma/convert/ClassIdentifier.java
deleted file mode 100644
index ee5e9033..00000000
--- a/src/cuchaz/enigma/convert/ClassIdentifier.java
+++ /dev/null
@@ -1,54 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Map;
14import java.util.jar.JarFile;
15
16import com.google.common.collect.Maps;
17
18import javassist.CtClass;
19import cuchaz.enigma.TranslatingTypeLoader;
20import cuchaz.enigma.analysis.JarIndex;
21import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
22import cuchaz.enigma.mapping.ClassEntry;
23
24
25public class ClassIdentifier {
26
27 private JarIndex m_index;
28 private SidedClassNamer m_namer;
29 private boolean m_useReferences;
30 private TranslatingTypeLoader m_loader;
31 private Map<ClassEntry,ClassIdentity> m_cache;
32
33 public ClassIdentifier(JarFile jar, JarIndex index, SidedClassNamer namer, boolean useReferences) {
34 m_index = index;
35 m_namer = namer;
36 m_useReferences = useReferences;
37 m_loader = new TranslatingTypeLoader(jar, index);
38 m_cache = Maps.newHashMap();
39 }
40
41 public ClassIdentity identify(ClassEntry classEntry)
42 throws ClassNotFoundException {
43 ClassIdentity identity = m_cache.get(classEntry);
44 if (identity == null) {
45 CtClass c = m_loader.loadClass(classEntry.getName());
46 if (c == null) {
47 throw new ClassNotFoundException(classEntry.getName());
48 }
49 identity = new ClassIdentity(c, m_namer, m_index, m_useReferences);
50 m_cache.put(classEntry, identity);
51 }
52 return identity;
53 }
54}
diff --git a/src/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java
deleted file mode 100644
index d9ed08ea..00000000
--- a/src/cuchaz/enigma/convert/ClassIdentity.java
+++ /dev/null
@@ -1,473 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.io.UnsupportedEncodingException;
14import java.security.MessageDigest;
15import java.security.NoSuchAlgorithmException;
16import java.util.Enumeration;
17import java.util.List;
18import java.util.Map;
19import java.util.Set;
20
21import javassist.CannotCompileException;
22import javassist.CtBehavior;
23import javassist.CtClass;
24import javassist.CtConstructor;
25import javassist.CtField;
26import javassist.CtMethod;
27import javassist.bytecode.BadBytecode;
28import javassist.bytecode.CodeIterator;
29import javassist.bytecode.ConstPool;
30import javassist.bytecode.Descriptor;
31import javassist.bytecode.Opcode;
32import javassist.expr.ConstructorCall;
33import javassist.expr.ExprEditor;
34import javassist.expr.FieldAccess;
35import javassist.expr.MethodCall;
36import javassist.expr.NewExpr;
37
38import com.google.common.collect.HashMultiset;
39import com.google.common.collect.Lists;
40import com.google.common.collect.Maps;
41import com.google.common.collect.Multiset;
42import com.google.common.collect.Sets;
43
44import cuchaz.enigma.Constants;
45import cuchaz.enigma.Util;
46import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
47import cuchaz.enigma.analysis.EntryReference;
48import cuchaz.enigma.analysis.JarIndex;
49import cuchaz.enigma.bytecode.ConstPoolEditor;
50import cuchaz.enigma.bytecode.InfoType;
51import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
52import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
53import cuchaz.enigma.mapping.BehaviorEntry;
54import cuchaz.enigma.mapping.ClassEntry;
55import cuchaz.enigma.mapping.ClassNameReplacer;
56import cuchaz.enigma.mapping.Entry;
57import cuchaz.enigma.mapping.EntryFactory;
58import cuchaz.enigma.mapping.FieldEntry;
59import cuchaz.enigma.mapping.Signature;
60import cuchaz.enigma.mapping.Type;
61
62public class ClassIdentity {
63
64 private ClassEntry m_classEntry;
65 private SidedClassNamer m_namer;
66 private Multiset<String> m_fields;
67 private Multiset<String> m_methods;
68 private Multiset<String> m_constructors;
69 private String m_staticInitializer;
70 private String m_extends;
71 private Multiset<String> m_implements;
72 private Set<String> m_stringLiterals;
73 private Multiset<String> m_implementations;
74 private Multiset<String> m_references;
75 private String m_outer;
76
77 private final ClassNameReplacer m_classNameReplacer = new ClassNameReplacer() {
78
79 private Map<String,String> m_classNames = Maps.newHashMap();
80
81 @Override
82 public String replace(String className) {
83
84 // classes not in the none package can be passed through
85 ClassEntry classEntry = new ClassEntry(className);
86 if (!classEntry.getPackageName().equals(Constants.NonePackage)) {
87 return className;
88 }
89
90 // is this class ourself?
91 if (className.equals(m_classEntry.getName())) {
92 return "CSelf";
93 }
94
95 // try the namer
96 if (m_namer != null) {
97 String newName = m_namer.getName(className);
98 if (newName != null) {
99 return newName;
100 }
101 }
102
103 // otherwise, use local naming
104 if (!m_classNames.containsKey(className)) {
105 m_classNames.put(className, getNewClassName());
106 }
107 return m_classNames.get(className);
108 }
109
110 private String getNewClassName() {
111 return String.format("C%03d", m_classNames.size());
112 }
113 };
114
115 public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) {
116 m_namer = namer;
117
118 // stuff from the bytecode
119
120 m_classEntry = EntryFactory.getClassEntry(c);
121 m_fields = HashMultiset.create();
122 for (CtField field : c.getDeclaredFields()) {
123 m_fields.add(scrubType(field.getSignature()));
124 }
125 m_methods = HashMultiset.create();
126 for (CtMethod method : c.getDeclaredMethods()) {
127 m_methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method));
128 }
129 m_constructors = HashMultiset.create();
130 for (CtConstructor constructor : c.getDeclaredConstructors()) {
131 m_constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor));
132 }
133 m_staticInitializer = "";
134 if (c.getClassInitializer() != null) {
135 m_staticInitializer = getBehaviorSignature(c.getClassInitializer());
136 }
137 m_extends = "";
138 if (c.getClassFile().getSuperclass() != null) {
139 m_extends = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
140 }
141 m_implements = HashMultiset.create();
142 for (String interfaceName : c.getClassFile().getInterfaces()) {
143 m_implements.add(scrubClassName(Descriptor.toJvmName(interfaceName)));
144 }
145
146 m_stringLiterals = Sets.newHashSet();
147 ConstPool constants = c.getClassFile().getConstPool();
148 for (int i=1; i<constants.getSize(); i++) {
149 if (constants.getTag(i) == ConstPool.CONST_String) {
150 m_stringLiterals.add(constants.getStringInfo(i));
151 }
152 }
153
154 // stuff from the jar index
155
156 m_implementations = HashMultiset.create();
157 ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, m_classEntry);
158 if (implementationsNode != null) {
159 @SuppressWarnings("unchecked")
160 Enumeration<ClassImplementationsTreeNode> implementations = implementationsNode.children();
161 while (implementations.hasMoreElements()) {
162 ClassImplementationsTreeNode node = implementations.nextElement();
163 m_implementations.add(scrubClassName(node.getClassEntry().getName()));
164 }
165 }
166
167 m_references = HashMultiset.create();
168 if (useReferences) {
169 for (CtField field : c.getDeclaredFields()) {
170 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
171 for (EntryReference<FieldEntry,BehaviorEntry> reference : index.getFieldReferences(fieldEntry)) {
172 addReference(reference);
173 }
174 }
175 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
176 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
177 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(behaviorEntry)) {
178 addReference(reference);
179 }
180 }
181 }
182
183 m_outer = null;
184 if (m_classEntry.isInnerClass()) {
185 m_outer = m_classEntry.getOuterClassName();
186 }
187 }
188
189 private void addReference(EntryReference<? extends Entry,BehaviorEntry> reference) {
190 if (reference.context.getSignature() != null) {
191 m_references.add(String.format("%s_%s",
192 scrubClassName(reference.context.getClassName()),
193 scrubSignature(reference.context.getSignature())
194 ));
195 } else {
196 m_references.add(String.format("%s_<clinit>",
197 scrubClassName(reference.context.getClassName())
198 ));
199 }
200 }
201
202 public ClassEntry getClassEntry() {
203 return m_classEntry;
204 }
205
206 @Override
207 public String toString() {
208 StringBuilder buf = new StringBuilder();
209 buf.append("class: ");
210 buf.append(m_classEntry.getName());
211 buf.append(" ");
212 buf.append(hashCode());
213 buf.append("\n");
214 for (String field : m_fields) {
215 buf.append("\tfield ");
216 buf.append(field);
217 buf.append("\n");
218 }
219 for (String method : m_methods) {
220 buf.append("\tmethod ");
221 buf.append(method);
222 buf.append("\n");
223 }
224 for (String constructor : m_constructors) {
225 buf.append("\tconstructor ");
226 buf.append(constructor);
227 buf.append("\n");
228 }
229 if (m_staticInitializer.length() > 0) {
230 buf.append("\tinitializer ");
231 buf.append(m_staticInitializer);
232 buf.append("\n");
233 }
234 if (m_extends.length() > 0) {
235 buf.append("\textends ");
236 buf.append(m_extends);
237 buf.append("\n");
238 }
239 for (String interfaceName : m_implements) {
240 buf.append("\timplements ");
241 buf.append(interfaceName);
242 buf.append("\n");
243 }
244 for (String implementation : m_implementations) {
245 buf.append("\timplemented by ");
246 buf.append(implementation);
247 buf.append("\n");
248 }
249 for (String reference : m_references) {
250 buf.append("\treference ");
251 buf.append(reference);
252 buf.append("\n");
253 }
254 buf.append("\touter ");
255 buf.append(m_outer);
256 buf.append("\n");
257 return buf.toString();
258 }
259
260 private String scrubClassName(String className) {
261 return m_classNameReplacer.replace(className);
262 }
263
264 private String scrubType(String typeName) {
265 return scrubType(new Type(typeName)).toString();
266 }
267
268 private Type scrubType(Type type) {
269 if (type.hasClass()) {
270 return new Type(type, m_classNameReplacer);
271 } else {
272 return type;
273 }
274 }
275
276 private String scrubSignature(String signature) {
277 return scrubSignature(new Signature(signature)).toString();
278 }
279
280 private Signature scrubSignature(Signature signature) {
281 return new Signature(signature, m_classNameReplacer);
282 }
283
284 private boolean isClassMatchedUniquely(String className) {
285 return m_namer != null && m_namer.getName(Descriptor.toJvmName(className)) != null;
286 }
287
288 private String getBehaviorSignature(CtBehavior behavior) {
289 try {
290 // does this method have an implementation?
291 if (behavior.getMethodInfo().getCodeAttribute() == null) {
292 return "(none)";
293 }
294
295 // compute the hash from the opcodes
296 ConstPool constants = behavior.getMethodInfo().getConstPool();
297 final MessageDigest digest = MessageDigest.getInstance("MD5");
298 CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator();
299 while (iter.hasNext()) {
300 int pos = iter.next();
301
302 // update the hash with the opcode
303 int opcode = iter.byteAt(pos);
304 digest.update((byte)opcode);
305
306 switch (opcode) {
307 case Opcode.LDC: {
308 int constIndex = iter.byteAt(pos + 1);
309 updateHashWithConstant(digest, constants, constIndex);
310 }
311 break;
312
313 case Opcode.LDC_W:
314 case Opcode.LDC2_W: {
315 int constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2);
316 updateHashWithConstant(digest, constants, constIndex);
317 }
318 break;
319 }
320 }
321
322 // update hash with method and field accesses
323 behavior.instrument(new ExprEditor() {
324 @Override
325 public void edit(MethodCall call) {
326 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
327 updateHashWithString(digest, scrubSignature(call.getSignature()));
328 if (isClassMatchedUniquely(call.getClassName())) {
329 updateHashWithString(digest, call.getMethodName());
330 }
331 }
332
333 @Override
334 public void edit(FieldAccess access) {
335 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName())));
336 updateHashWithString(digest, scrubType(access.getSignature()));
337 if (isClassMatchedUniquely(access.getClassName())) {
338 updateHashWithString(digest, access.getFieldName());
339 }
340 }
341
342 @Override
343 public void edit(ConstructorCall call) {
344 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
345 updateHashWithString(digest, scrubSignature(call.getSignature()));
346 }
347
348 @Override
349 public void edit(NewExpr expr) {
350 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName())));
351 }
352 });
353
354 // convert the hash to a hex string
355 return toHex(digest.digest());
356 } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) {
357 throw new Error(ex);
358 }
359 }
360
361 private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) {
362 ConstPoolEditor editor = new ConstPoolEditor(constants);
363 ConstInfoAccessor item = editor.getItem(index);
364 if (item.getType() == InfoType.StringInfo) {
365 updateHashWithString(digest, constants.getStringInfo(index));
366 }
367 // TODO: other constants
368 }
369
370 private void updateHashWithString(MessageDigest digest, String val) {
371 try {
372 digest.update(val.getBytes("UTF8"));
373 } catch (UnsupportedEncodingException ex) {
374 throw new Error(ex);
375 }
376 }
377
378 private String toHex(byte[] bytes) {
379 // function taken from:
380 // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java
381 final char[] hexArray = "0123456789ABCDEF".toCharArray();
382 char[] hexChars = new char[bytes.length * 2];
383 for (int j = 0; j < bytes.length; j++) {
384 int v = bytes[j] & 0xFF;
385 hexChars[j * 2] = hexArray[v >>> 4];
386 hexChars[j * 2 + 1] = hexArray[v & 0x0F];
387 }
388 return new String(hexChars);
389 }
390
391 @Override
392 public boolean equals(Object other) {
393 if (other instanceof ClassIdentity) {
394 return equals((ClassIdentity)other);
395 }
396 return false;
397 }
398
399 public boolean equals(ClassIdentity other) {
400 return m_fields.equals(other.m_fields)
401 && m_methods.equals(other.m_methods)
402 && m_constructors.equals(other.m_constructors)
403 && m_staticInitializer.equals(other.m_staticInitializer)
404 && m_extends.equals(other.m_extends)
405 && m_implements.equals(other.m_implements)
406 && m_implementations.equals(other.m_implementations)
407 && m_references.equals(other.m_references);
408 }
409
410 @Override
411 public int hashCode() {
412 List<Object> objs = Lists.newArrayList();
413 objs.addAll(m_fields);
414 objs.addAll(m_methods);
415 objs.addAll(m_constructors);
416 objs.add(m_staticInitializer);
417 objs.add(m_extends);
418 objs.addAll(m_implements);
419 objs.addAll(m_implementations);
420 objs.addAll(m_references);
421 return Util.combineHashesOrdered(objs);
422 }
423
424 public int getMatchScore(ClassIdentity other) {
425 return 2*getNumMatches(m_extends, other.m_extends)
426 + 2*getNumMatches(m_outer, other.m_outer)
427 + 2*getNumMatches(m_implements, other.m_implements)
428 + getNumMatches(m_stringLiterals, other.m_stringLiterals)
429 + getNumMatches(m_fields, other.m_fields)
430 + getNumMatches(m_methods, other.m_methods)
431 + getNumMatches(m_constructors, other.m_constructors);
432 }
433
434 public int getMaxMatchScore() {
435 return 2 + 2 + 2*m_implements.size() + m_stringLiterals.size() + m_fields.size() + m_methods.size() + m_constructors.size();
436 }
437
438 public boolean matches(CtClass c) {
439 // just compare declaration counts
440 return m_fields.size() == c.getDeclaredFields().length
441 && m_methods.size() == c.getDeclaredMethods().length
442 && m_constructors.size() == c.getDeclaredConstructors().length;
443 }
444
445 private int getNumMatches(Set<String> a, Set<String> b) {
446 int numMatches = 0;
447 for (String val : a) {
448 if (b.contains(val)) {
449 numMatches++;
450 }
451 }
452 return numMatches;
453 }
454
455 private int getNumMatches(Multiset<String> a, Multiset<String> b) {
456 int numMatches = 0;
457 for (String val : a) {
458 if (b.contains(val)) {
459 numMatches++;
460 }
461 }
462 return numMatches;
463 }
464
465 private int getNumMatches(String a, String b) {
466 if (a == null && b == null) {
467 return 1;
468 } else if (a != null && b != null && a.equals(b)) {
469 return 1;
470 }
471 return 0;
472 }
473}
diff --git a/src/cuchaz/enigma/convert/ClassMatch.java b/src/cuchaz/enigma/convert/ClassMatch.java
deleted file mode 100644
index 8c50a624..00000000
--- a/src/cuchaz/enigma/convert/ClassMatch.java
+++ /dev/null
@@ -1,88 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Collection;
14import java.util.Set;
15
16import com.google.common.collect.Sets;
17
18import cuchaz.enigma.Util;
19import cuchaz.enigma.mapping.ClassEntry;
20
21
22public class ClassMatch {
23
24 public Set<ClassEntry> sourceClasses;
25 public Set<ClassEntry> destClasses;
26
27 public ClassMatch(Collection<ClassEntry> sourceClasses, Collection<ClassEntry> destClasses) {
28 this.sourceClasses = Sets.newHashSet(sourceClasses);
29 this.destClasses = Sets.newHashSet(destClasses);
30 }
31
32 public ClassMatch(ClassEntry sourceClass, ClassEntry destClass) {
33 sourceClasses = Sets.newHashSet();
34 if (sourceClass != null) {
35 sourceClasses.add(sourceClass);
36 }
37 destClasses = Sets.newHashSet();
38 if (destClass != null) {
39 destClasses.add(destClass);
40 }
41 }
42
43 public boolean isMatched() {
44 return sourceClasses.size() > 0 && destClasses.size() > 0;
45 }
46
47 public boolean isAmbiguous() {
48 return sourceClasses.size() > 1 || destClasses.size() > 1;
49 }
50
51 public ClassEntry getUniqueSource() {
52 if (sourceClasses.size() != 1) {
53 throw new IllegalStateException("Match has ambiguous source!");
54 }
55 return sourceClasses.iterator().next();
56 }
57
58 public ClassEntry getUniqueDest() {
59 if (destClasses.size() != 1) {
60 throw new IllegalStateException("Match has ambiguous source!");
61 }
62 return destClasses.iterator().next();
63 }
64
65 public Set<ClassEntry> intersectSourceClasses(Set<ClassEntry> classes) {
66 Set<ClassEntry> intersection = Sets.newHashSet(sourceClasses);
67 intersection.retainAll(classes);
68 return intersection;
69 }
70
71 @Override
72 public int hashCode() {
73 return Util.combineHashesOrdered(sourceClasses, destClasses);
74 }
75
76 @Override
77 public boolean equals(Object other) {
78 if (other instanceof ClassMatch) {
79 return equals((ClassMatch)other);
80 }
81 return false;
82 }
83
84 public boolean equals(ClassMatch other) {
85 return this.sourceClasses.equals(other.sourceClasses)
86 && this.destClasses.equals(other.destClasses);
87 }
88}
diff --git a/src/cuchaz/enigma/convert/ClassMatches.java b/src/cuchaz/enigma/convert/ClassMatches.java
deleted file mode 100644
index f70c1805..00000000
--- a/src/cuchaz/enigma/convert/ClassMatches.java
+++ /dev/null
@@ -1,163 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.ArrayList;
14import java.util.Collection;
15import java.util.Iterator;
16import java.util.Map;
17import java.util.Set;
18
19import com.google.common.collect.BiMap;
20import com.google.common.collect.HashBiMap;
21import com.google.common.collect.Maps;
22import com.google.common.collect.Sets;
23
24import cuchaz.enigma.mapping.ClassEntry;
25
26
27public class ClassMatches implements Iterable<ClassMatch> {
28
29 Collection<ClassMatch> m_matches;
30 Map<ClassEntry,ClassMatch> m_matchesBySource;
31 Map<ClassEntry,ClassMatch> m_matchesByDest;
32 BiMap<ClassEntry,ClassEntry> m_uniqueMatches;
33 Map<ClassEntry,ClassMatch> m_ambiguousMatchesBySource;
34 Map<ClassEntry,ClassMatch> m_ambiguousMatchesByDest;
35 Set<ClassEntry> m_unmatchedSourceClasses;
36 Set<ClassEntry> m_unmatchedDestClasses;
37
38 public ClassMatches() {
39 this(new ArrayList<ClassMatch>());
40 }
41
42 public ClassMatches(Collection<ClassMatch> matches) {
43 m_matches = matches;
44 m_matchesBySource = Maps.newHashMap();
45 m_matchesByDest = Maps.newHashMap();
46 m_uniqueMatches = HashBiMap.create();
47 m_ambiguousMatchesBySource = Maps.newHashMap();
48 m_ambiguousMatchesByDest = Maps.newHashMap();
49 m_unmatchedSourceClasses = Sets.newHashSet();
50 m_unmatchedDestClasses = Sets.newHashSet();
51
52 for (ClassMatch match : matches) {
53 indexMatch(match);
54 }
55 }
56
57 public void add(ClassMatch match) {
58 m_matches.add(match);
59 indexMatch(match);
60 }
61
62 public void remove(ClassMatch match) {
63 for (ClassEntry sourceClass : match.sourceClasses) {
64 m_matchesBySource.remove(sourceClass);
65 m_uniqueMatches.remove(sourceClass);
66 m_ambiguousMatchesBySource.remove(sourceClass);
67 m_unmatchedSourceClasses.remove(sourceClass);
68 }
69 for (ClassEntry destClass : match.destClasses) {
70 m_matchesByDest.remove(destClass);
71 m_uniqueMatches.inverse().remove(destClass);
72 m_ambiguousMatchesByDest.remove(destClass);
73 m_unmatchedDestClasses.remove(destClass);
74 }
75 m_matches.remove(match);
76 }
77
78 public int size() {
79 return m_matches.size();
80 }
81
82 @Override
83 public Iterator<ClassMatch> iterator() {
84 return m_matches.iterator();
85 }
86
87 private void indexMatch(ClassMatch match) {
88 if (!match.isMatched()) {
89 // unmatched
90 m_unmatchedSourceClasses.addAll(match.sourceClasses);
91 m_unmatchedDestClasses.addAll(match.destClasses);
92 } else {
93 if (match.isAmbiguous()) {
94 // ambiguously matched
95 for (ClassEntry entry : match.sourceClasses) {
96 m_ambiguousMatchesBySource.put(entry, match);
97 }
98 for (ClassEntry entry : match.destClasses) {
99 m_ambiguousMatchesByDest.put(entry, match);
100 }
101 } else {
102 // uniquely matched
103 m_uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest());
104 }
105 }
106 for (ClassEntry entry : match.sourceClasses) {
107 m_matchesBySource.put(entry, match);
108 }
109 for (ClassEntry entry : match.destClasses) {
110 m_matchesByDest.put(entry, match);
111 }
112 }
113
114 public BiMap<ClassEntry,ClassEntry> getUniqueMatches() {
115 return m_uniqueMatches;
116 }
117
118 public Set<ClassEntry> getUnmatchedSourceClasses() {
119 return m_unmatchedSourceClasses;
120 }
121
122 public Set<ClassEntry> getUnmatchedDestClasses() {
123 return m_unmatchedDestClasses;
124 }
125
126 public Set<ClassEntry> getAmbiguouslyMatchedSourceClasses() {
127 return m_ambiguousMatchesBySource.keySet();
128 }
129
130 public ClassMatch getAmbiguousMatchBySource(ClassEntry sourceClass) {
131 return m_ambiguousMatchesBySource.get(sourceClass);
132 }
133
134 public ClassMatch getMatchBySource(ClassEntry sourceClass) {
135 return m_matchesBySource.get(sourceClass);
136 }
137
138 public ClassMatch getMatchByDest(ClassEntry destClass) {
139 return m_matchesByDest.get(destClass);
140 }
141
142 public void removeSource(ClassEntry sourceClass) {
143 ClassMatch match = m_matchesBySource.get(sourceClass);
144 if (match != null) {
145 remove(match);
146 match.sourceClasses.remove(sourceClass);
147 if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) {
148 add(match);
149 }
150 }
151 }
152
153 public void removeDest(ClassEntry destClass) {
154 ClassMatch match = m_matchesByDest.get(destClass);
155 if (match != null) {
156 remove(match);
157 match.destClasses.remove(destClass);
158 if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) {
159 add(match);
160 }
161 }
162 }
163}
diff --git a/src/cuchaz/enigma/convert/ClassMatching.java b/src/cuchaz/enigma/convert/ClassMatching.java
deleted file mode 100644
index 633d1ac7..00000000
--- a/src/cuchaz/enigma/convert/ClassMatching.java
+++ /dev/null
@@ -1,155 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.ArrayList;
14import java.util.Collection;
15import java.util.List;
16import java.util.Map.Entry;
17import java.util.Set;
18
19import com.google.common.collect.BiMap;
20import com.google.common.collect.HashBiMap;
21import com.google.common.collect.Lists;
22import com.google.common.collect.Sets;
23
24import cuchaz.enigma.mapping.ClassEntry;
25
26public class ClassMatching {
27
28 private ClassForest m_sourceClasses;
29 private ClassForest m_destClasses;
30 private BiMap<ClassEntry,ClassEntry> m_knownMatches;
31
32 public ClassMatching(ClassIdentifier sourceIdentifier, ClassIdentifier destIdentifier) {
33 m_sourceClasses = new ClassForest(sourceIdentifier);
34 m_destClasses = new ClassForest(destIdentifier);
35 m_knownMatches = HashBiMap.create();
36 }
37
38 public void addKnownMatches(BiMap<ClassEntry,ClassEntry> knownMatches) {
39 m_knownMatches.putAll(knownMatches);
40 }
41
42 public void match(Iterable<ClassEntry> sourceClasses, Iterable<ClassEntry> destClasses) {
43 for (ClassEntry sourceClass : sourceClasses) {
44 if (!m_knownMatches.containsKey(sourceClass)) {
45 m_sourceClasses.add(sourceClass);
46 }
47 }
48 for (ClassEntry destClass : destClasses) {
49 if (!m_knownMatches.containsValue(destClass)) {
50 m_destClasses.add(destClass);
51 }
52 }
53 }
54
55 public Collection<ClassMatch> matches() {
56 List<ClassMatch> matches = Lists.newArrayList();
57 for (Entry<ClassEntry,ClassEntry> entry : m_knownMatches.entrySet()) {
58 matches.add(new ClassMatch(
59 entry.getKey(),
60 entry.getValue()
61 ));
62 }
63 for (ClassIdentity identity : m_sourceClasses.identities()) {
64 matches.add(new ClassMatch(
65 m_sourceClasses.getClasses(identity),
66 m_destClasses.getClasses(identity)
67 ));
68 }
69 for (ClassIdentity identity : m_destClasses.identities()) {
70 if (!m_sourceClasses.containsIdentity(identity)) {
71 matches.add(new ClassMatch(
72 new ArrayList<ClassEntry>(),
73 m_destClasses.getClasses(identity)
74 ));
75 }
76 }
77 return matches;
78 }
79
80 public Collection<ClassEntry> sourceClasses() {
81 Set<ClassEntry> classes = Sets.newHashSet();
82 for (ClassMatch match : matches()) {
83 classes.addAll(match.sourceClasses);
84 }
85 return classes;
86 }
87
88 public Collection<ClassEntry> destClasses() {
89 Set<ClassEntry> classes = Sets.newHashSet();
90 for (ClassMatch match : matches()) {
91 classes.addAll(match.destClasses);
92 }
93 return classes;
94 }
95
96 public BiMap<ClassEntry,ClassEntry> uniqueMatches() {
97 BiMap<ClassEntry,ClassEntry> uniqueMatches = HashBiMap.create();
98 for (ClassMatch match : matches()) {
99 if (match.isMatched() && !match.isAmbiguous()) {
100 uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest());
101 }
102 }
103 return uniqueMatches;
104 }
105
106 public Collection<ClassMatch> ambiguousMatches() {
107 List<ClassMatch> ambiguousMatches = Lists.newArrayList();
108 for (ClassMatch match : matches()) {
109 if (match.isMatched() && match.isAmbiguous()) {
110 ambiguousMatches.add(match);
111 }
112 }
113 return ambiguousMatches;
114 }
115
116 public Collection<ClassEntry> unmatchedSourceClasses() {
117 List<ClassEntry> classes = Lists.newArrayList();
118 for (ClassMatch match : matches()) {
119 if (!match.isMatched() && !match.sourceClasses.isEmpty()) {
120 classes.addAll(match.sourceClasses);
121 }
122 }
123 return classes;
124 }
125
126 public Collection<ClassEntry> unmatchedDestClasses() {
127 List<ClassEntry> classes = Lists.newArrayList();
128 for (ClassMatch match : matches()) {
129 if (!match.isMatched() && !match.destClasses.isEmpty()) {
130 classes.addAll(match.destClasses);
131 }
132 }
133 return classes;
134 }
135
136 @Override
137 public String toString() {
138
139 // count the ambiguous classes
140 int numAmbiguousSource = 0;
141 int numAmbiguousDest = 0;
142 for (ClassMatch match : ambiguousMatches()) {
143 numAmbiguousSource += match.sourceClasses.size();
144 numAmbiguousDest += match.destClasses.size();
145 }
146
147 StringBuilder buf = new StringBuilder();
148 buf.append(String.format("%20s%8s%8s\n", "", "Source", "Dest"));
149 buf.append(String.format("%20s%8d%8d\n", "Classes", sourceClasses().size(), destClasses().size()));
150 buf.append(String.format("%20s%8d%8d\n", "Uniquely matched", uniqueMatches().size(), uniqueMatches().size()));
151 buf.append(String.format("%20s%8d%8d\n", "Ambiguously matched", numAmbiguousSource, numAmbiguousDest));
152 buf.append(String.format("%20s%8d%8d\n", "Unmatched", unmatchedSourceClasses().size(), unmatchedDestClasses().size()));
153 return buf.toString();
154 }
155}
diff --git a/src/cuchaz/enigma/convert/ClassNamer.java b/src/cuchaz/enigma/convert/ClassNamer.java
deleted file mode 100644
index e8fa7303..00000000
--- a/src/cuchaz/enigma/convert/ClassNamer.java
+++ /dev/null
@@ -1,66 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Map;
14
15import com.google.common.collect.BiMap;
16import com.google.common.collect.Maps;
17
18import cuchaz.enigma.mapping.ClassEntry;
19
20public class ClassNamer {
21
22 public interface SidedClassNamer {
23 String getName(String name);
24 }
25
26 private Map<String,String> m_sourceNames;
27 private Map<String,String> m_destNames;
28
29 public ClassNamer(BiMap<ClassEntry,ClassEntry> mappings) {
30 // convert the identity mappings to name maps
31 m_sourceNames = Maps.newHashMap();
32 m_destNames = Maps.newHashMap();
33 int i = 0;
34 for (Map.Entry<ClassEntry,ClassEntry> entry : mappings.entrySet()) {
35 String name = String.format("M%04d", i++);
36 m_sourceNames.put(entry.getKey().getName(), name);
37 m_destNames.put(entry.getValue().getName(), name);
38 }
39 }
40
41 public String getSourceName(String name) {
42 return m_sourceNames.get(name);
43 }
44
45 public String getDestName(String name) {
46 return m_destNames.get(name);
47 }
48
49 public SidedClassNamer getSourceNamer() {
50 return new SidedClassNamer() {
51 @Override
52 public String getName(String name) {
53 return getSourceName(name);
54 }
55 };
56 }
57
58 public SidedClassNamer getDestNamer() {
59 return new SidedClassNamer() {
60 @Override
61 public String getName(String name) {
62 return getDestName(name);
63 }
64 };
65 }
66}
diff --git a/src/cuchaz/enigma/convert/FieldMatches.java b/src/cuchaz/enigma/convert/FieldMatches.java
deleted file mode 100644
index 8439a84c..00000000
--- a/src/cuchaz/enigma/convert/FieldMatches.java
+++ /dev/null
@@ -1,155 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Collection;
14import java.util.Set;
15
16import com.google.common.collect.BiMap;
17import com.google.common.collect.HashBiMap;
18import com.google.common.collect.HashMultimap;
19import com.google.common.collect.Multimap;
20import com.google.common.collect.Sets;
21
22import cuchaz.enigma.mapping.ClassEntry;
23import cuchaz.enigma.mapping.FieldEntry;
24
25
26public class FieldMatches {
27
28 private BiMap<FieldEntry,FieldEntry> m_matches;
29 private Multimap<ClassEntry,FieldEntry> m_matchedSourceFields;
30 private Multimap<ClassEntry,FieldEntry> m_unmatchedSourceFields;
31 private Multimap<ClassEntry,FieldEntry> m_unmatchedDestFields;
32 private Multimap<ClassEntry,FieldEntry> m_unmatchableSourceFields;
33
34 public FieldMatches() {
35 m_matches = HashBiMap.create();
36 m_matchedSourceFields = HashMultimap.create();
37 m_unmatchedSourceFields = HashMultimap.create();
38 m_unmatchedDestFields = HashMultimap.create();
39 m_unmatchableSourceFields = HashMultimap.create();
40 }
41
42 public void addMatch(FieldEntry srcField, FieldEntry destField) {
43 boolean wasAdded = m_matches.put(srcField, destField) == null;
44 assert (wasAdded);
45 wasAdded = m_matchedSourceFields.put(srcField.getClassEntry(), srcField);
46 assert (wasAdded);
47 }
48
49 public void addUnmatchedSourceField(FieldEntry fieldEntry) {
50 boolean wasAdded = m_unmatchedSourceFields.put(fieldEntry.getClassEntry(), fieldEntry);
51 assert (wasAdded);
52 }
53
54 public void addUnmatchedSourceFields(Iterable<FieldEntry> fieldEntries) {
55 for (FieldEntry fieldEntry : fieldEntries) {
56 addUnmatchedSourceField(fieldEntry);
57 }
58 }
59
60 public void addUnmatchedDestField(FieldEntry fieldEntry) {
61 boolean wasAdded = m_unmatchedDestFields.put(fieldEntry.getClassEntry(), fieldEntry);
62 assert (wasAdded);
63 }
64
65 public void addUnmatchedDestFields(Iterable<FieldEntry> fieldEntries) {
66 for (FieldEntry fieldEntry : fieldEntries) {
67 addUnmatchedDestField(fieldEntry);
68 }
69 }
70
71 public void addUnmatchableSourceField(FieldEntry sourceField) {
72 boolean wasAdded = m_unmatchableSourceFields.put(sourceField.getClassEntry(), sourceField);
73 assert (wasAdded);
74 }
75
76 public Set<ClassEntry> getSourceClassesWithUnmatchedFields() {
77 return m_unmatchedSourceFields.keySet();
78 }
79
80 public Collection<ClassEntry> getSourceClassesWithoutUnmatchedFields() {
81 Set<ClassEntry> out = Sets.newHashSet();
82 out.addAll(m_matchedSourceFields.keySet());
83 out.removeAll(m_unmatchedSourceFields.keySet());
84 return out;
85 }
86
87 public Collection<FieldEntry> getUnmatchedSourceFields() {
88 return m_unmatchedSourceFields.values();
89 }
90
91 public Collection<FieldEntry> getUnmatchedSourceFields(ClassEntry sourceClass) {
92 return m_unmatchedSourceFields.get(sourceClass);
93 }
94
95 public Collection<FieldEntry> getUnmatchedDestFields() {
96 return m_unmatchedDestFields.values();
97 }
98
99 public Collection<FieldEntry> getUnmatchedDestFields(ClassEntry destClass) {
100 return m_unmatchedDestFields.get(destClass);
101 }
102
103 public Collection<FieldEntry> getUnmatchableSourceFields() {
104 return m_unmatchableSourceFields.values();
105 }
106
107 public boolean hasSource(FieldEntry fieldEntry) {
108 return m_matches.containsKey(fieldEntry) || m_unmatchedSourceFields.containsValue(fieldEntry);
109 }
110
111 public boolean hasDest(FieldEntry fieldEntry) {
112 return m_matches.containsValue(fieldEntry) || m_unmatchedDestFields.containsValue(fieldEntry);
113 }
114
115 public BiMap<FieldEntry,FieldEntry> matches() {
116 return m_matches;
117 }
118
119 public boolean isMatchedSourceField(FieldEntry sourceField) {
120 return m_matches.containsKey(sourceField);
121 }
122
123 public boolean isMatchedDestField(FieldEntry destField) {
124 return m_matches.containsValue(destField);
125 }
126
127 public void makeMatch(FieldEntry sourceField, FieldEntry destField) {
128 boolean wasRemoved = m_unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
129 assert (wasRemoved);
130 wasRemoved = m_unmatchedDestFields.remove(destField.getClassEntry(), destField);
131 assert (wasRemoved);
132 addMatch(sourceField, destField);
133 }
134
135 public boolean isMatched(FieldEntry sourceField, FieldEntry destField) {
136 FieldEntry match = m_matches.get(sourceField);
137 return match != null && match.equals(destField);
138 }
139
140 public void unmakeMatch(FieldEntry sourceField, FieldEntry destField) {
141 boolean wasRemoved = m_matches.remove(sourceField) != null;
142 assert (wasRemoved);
143 wasRemoved = m_matchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
144 assert (wasRemoved);
145 addUnmatchedSourceField(sourceField);
146 addUnmatchedDestField(destField);
147 }
148
149 public void makeSourceUnmatchable(FieldEntry sourceField) {
150 assert(!isMatchedSourceField(sourceField));
151 boolean wasRemoved = m_unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
152 assert (wasRemoved);
153 addUnmatchableSourceField(sourceField);
154 }
155}
diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java
deleted file mode 100644
index 958a17cf..00000000
--- a/src/cuchaz/enigma/convert/MappingsConverter.java
+++ /dev/null
@@ -1,602 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Arrays;
14import java.util.Collection;
15import java.util.Collections;
16import java.util.Iterator;
17import java.util.LinkedHashMap;
18import java.util.List;
19import java.util.Map;
20import java.util.Set;
21import java.util.jar.JarFile;
22
23import com.google.common.collect.BiMap;
24import com.google.common.collect.HashMultimap;
25import com.google.common.collect.Lists;
26import com.google.common.collect.Maps;
27import com.google.common.collect.Multimap;
28import com.google.common.collect.Sets;
29
30import cuchaz.enigma.Constants;
31import cuchaz.enigma.Deobfuscator;
32import cuchaz.enigma.analysis.JarIndex;
33import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
34import cuchaz.enigma.mapping.BehaviorEntry;
35import cuchaz.enigma.mapping.ClassEntry;
36import cuchaz.enigma.mapping.ClassMapping;
37import cuchaz.enigma.mapping.ClassNameReplacer;
38import cuchaz.enigma.mapping.ConstructorEntry;
39import cuchaz.enigma.mapping.Entry;
40import cuchaz.enigma.mapping.FieldEntry;
41import cuchaz.enigma.mapping.FieldMapping;
42import cuchaz.enigma.mapping.Mappings;
43import cuchaz.enigma.mapping.MappingsChecker;
44import cuchaz.enigma.mapping.MemberMapping;
45import cuchaz.enigma.mapping.MethodEntry;
46import cuchaz.enigma.mapping.MethodMapping;
47import cuchaz.enigma.mapping.Signature;
48import cuchaz.enigma.mapping.Type;
49
50public class MappingsConverter {
51
52 public static ClassMatches computeClassMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) {
53
54 // index jars
55 System.out.println("Indexing source jar...");
56 JarIndex sourceIndex = new JarIndex();
57 sourceIndex.indexJar(sourceJar, false);
58 System.out.println("Indexing dest jar...");
59 JarIndex destIndex = new JarIndex();
60 destIndex.indexJar(destJar, false);
61
62 // compute the matching
63 ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null);
64 return new ClassMatches(matching.matches());
65 }
66
67 public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap<ClassEntry,ClassEntry> knownMatches) {
68
69 System.out.println("Iteratively matching classes");
70
71 ClassMatching lastMatching = null;
72 int round = 0;
73 SidedClassNamer sourceNamer = null;
74 SidedClassNamer destNamer = null;
75 for (boolean useReferences : Arrays.asList(false, true)) {
76
77 int numUniqueMatchesLastTime = 0;
78 if (lastMatching != null) {
79 numUniqueMatchesLastTime = lastMatching.uniqueMatches().size();
80 }
81
82 while (true) {
83
84 System.out.println("Round " + (++round) + "...");
85
86 // init the matching with identity settings
87 ClassMatching matching = new ClassMatching(
88 new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences),
89 new ClassIdentifier(destJar, destIndex, destNamer, useReferences)
90 );
91
92 if (knownMatches != null) {
93 matching.addKnownMatches(knownMatches);
94 }
95
96 if (lastMatching == null) {
97 // search all classes
98 matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries());
99 } else {
100 // we already know about these matches from last time
101 matching.addKnownMatches(lastMatching.uniqueMatches());
102
103 // search unmatched and ambiguously-matched classes
104 matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses());
105 for (ClassMatch match : lastMatching.ambiguousMatches()) {
106 matching.match(match.sourceClasses, match.destClasses);
107 }
108 }
109 System.out.println(matching);
110 BiMap<ClassEntry,ClassEntry> uniqueMatches = matching.uniqueMatches();
111
112 // did we match anything new this time?
113 if (uniqueMatches.size() > numUniqueMatchesLastTime) {
114 numUniqueMatchesLastTime = uniqueMatches.size();
115 lastMatching = matching;
116 } else {
117 break;
118 }
119
120 // update the namers
121 ClassNamer namer = new ClassNamer(uniqueMatches);
122 sourceNamer = namer.getSourceNamer();
123 destNamer = namer.getDestNamer();
124 }
125 }
126
127 return lastMatching;
128 }
129
130 public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
131
132 // sort the unique matches by size of inner class chain
133 Multimap<Integer,java.util.Map.Entry<ClassEntry,ClassEntry>> matchesByDestChainSize = HashMultimap.create();
134 for (java.util.Map.Entry<ClassEntry,ClassEntry> match : matches.getUniqueMatches().entrySet()) {
135 int chainSize = destDeobfuscator.getJarIndex().getObfClassChain(match.getValue()).size();
136 matchesByDestChainSize.put(chainSize, match);
137 }
138
139 // build the mappings (in order of small-to-large inner chains)
140 Mappings newMappings = new Mappings();
141 List<Integer> chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet());
142 Collections.sort(chainSizes);
143 for (int chainSize : chainSizes) {
144 for (java.util.Map.Entry<ClassEntry,ClassEntry> match : matchesByDestChainSize.get(chainSize)) {
145
146 // get class info
147 ClassEntry obfSourceClassEntry = match.getKey();
148 ClassEntry obfDestClassEntry = match.getValue();
149 List<ClassEntry> destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(obfDestClassEntry);
150
151 ClassMapping sourceMapping = sourceDeobfuscator.getMappings().getClassByObf(obfSourceClassEntry);
152 if (sourceMapping == null) {
153 // if this class was never deobfuscated, don't try to match it
154 continue;
155 }
156
157 // find out where to make the dest class mapping
158 if (destClassChain.size() == 1) {
159 // not an inner class, add directly to mappings
160 newMappings.addClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, false));
161 } else {
162 // inner class, find the outer class mapping
163 ClassMapping destMapping = null;
164 for (int i=0; i<destClassChain.size()-1; i++) {
165 ClassEntry destChainClassEntry = destClassChain.get(i);
166 if (destMapping == null) {
167 destMapping = newMappings.getClassByObf(destChainClassEntry);
168 if (destMapping == null) {
169 destMapping = new ClassMapping(destChainClassEntry.getName());
170 newMappings.addClassMapping(destMapping);
171 }
172 } else {
173 destMapping = destMapping.getInnerClassByObfSimple(destChainClassEntry.getInnermostClassName());
174 if (destMapping == null) {
175 destMapping = new ClassMapping(destChainClassEntry.getName());
176 destMapping.addInnerClassMapping(destMapping);
177 }
178 }
179 }
180 destMapping.addInnerClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, true));
181 }
182 }
183 }
184 return newMappings;
185 }
186
187 private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping oldClassMapping, final ClassMatches matches, boolean useSimpleName) {
188
189 ClassNameReplacer replacer = new ClassNameReplacer() {
190 @Override
191 public String replace(String className) {
192 ClassEntry newClassEntry = matches.getUniqueMatches().get(new ClassEntry(className));
193 if (newClassEntry != null) {
194 return newClassEntry.getName();
195 }
196 return null;
197 }
198 };
199
200 ClassMapping newClassMapping;
201 String deobfName = oldClassMapping.getDeobfName();
202 if (deobfName != null) {
203 if (useSimpleName) {
204 deobfName = new ClassEntry(deobfName).getSimpleName();
205 }
206 newClassMapping = new ClassMapping(newObfClass.getName(), deobfName);
207 } else {
208 newClassMapping = new ClassMapping(newObfClass.getName());
209 }
210
211 // migrate fields
212 for (FieldMapping oldFieldMapping : oldClassMapping.fields()) {
213 if (canMigrate(oldFieldMapping.getObfType(), matches)) {
214 newClassMapping.addFieldMapping(new FieldMapping(oldFieldMapping, replacer));
215 } else {
216 System.out.println(String.format("Can't map field, dropping: %s.%s %s",
217 oldClassMapping.getDeobfName(),
218 oldFieldMapping.getDeobfName(),
219 oldFieldMapping.getObfType()
220 ));
221 }
222 }
223
224 // migrate methods
225 for (MethodMapping oldMethodMapping : oldClassMapping.methods()) {
226 if (canMigrate(oldMethodMapping.getObfSignature(), matches)) {
227 newClassMapping.addMethodMapping(new MethodMapping(oldMethodMapping, replacer));
228 } else {
229 System.out.println(String.format("Can't map method, dropping: %s.%s %s",
230 oldClassMapping.getDeobfName(),
231 oldMethodMapping.getDeobfName(),
232 oldMethodMapping.getObfSignature()
233 ));
234 }
235 }
236
237 return newClassMapping;
238 }
239
240 private static boolean canMigrate(Signature oldObfSignature, ClassMatches classMatches) {
241 for (Type oldObfType : oldObfSignature.types()) {
242 if (!canMigrate(oldObfType, classMatches)) {
243 return false;
244 }
245 }
246 return true;
247 }
248
249 private static boolean canMigrate(Type oldObfType, ClassMatches classMatches) {
250
251 // non classes can be migrated
252 if (!oldObfType.hasClass()) {
253 return true;
254 }
255
256 // non obfuscated classes can be migrated
257 ClassEntry classEntry = oldObfType.getClassEntry();
258 if (!classEntry.getPackageName().equals(Constants.NonePackage)) {
259 return true;
260 }
261
262 // obfuscated classes with mappings can be migrated
263 return classMatches.getUniqueMatches().containsKey(classEntry);
264 }
265
266 public static void convertMappings(Mappings mappings, BiMap<ClassEntry,ClassEntry> changes) {
267
268 // sort the changes so classes are renamed in the correct order
269 // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b
270 LinkedHashMap<ClassEntry,ClassEntry> sortedChanges = Maps.newLinkedHashMap();
271 int numChangesLeft = changes.size();
272 while (!changes.isEmpty()) {
273 Iterator<Map.Entry<ClassEntry,ClassEntry>> iter = changes.entrySet().iterator();
274 while (iter.hasNext()) {
275 Map.Entry<ClassEntry,ClassEntry> change = iter.next();
276 if (changes.containsKey(change.getValue())) {
277 sortedChanges.put(change.getKey(), change.getValue());
278 iter.remove();
279 }
280 }
281
282 // did we remove any changes?
283 if (numChangesLeft - changes.size() > 0) {
284 // keep going
285 numChangesLeft = changes.size();
286 } else {
287 // can't sort anymore. There must be a loop
288 break;
289 }
290 }
291 if (!changes.isEmpty()) {
292 throw new Error("Unable to sort class changes! There must be a cycle.");
293 }
294
295 // convert the mappings in the correct class order
296 for (Map.Entry<ClassEntry,ClassEntry> entry : sortedChanges.entrySet()) {
297 mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName());
298 }
299 }
300
301 public static interface Doer<T extends Entry> {
302 Collection<T> getDroppedEntries(MappingsChecker checker);
303 Collection<T> getObfEntries(JarIndex jarIndex);
304 Collection<? extends MemberMapping<T>> getMappings(ClassMapping destClassMapping);
305 Set<T> filterEntries(Collection<T> obfEntries, T obfSourceEntry, ClassMatches classMatches);
306 void setUpdateObfMember(ClassMapping classMapping, MemberMapping<T> memberMapping, T newEntry);
307 boolean hasObfMember(ClassMapping classMapping, T obfEntry);
308 void removeMemberByObf(ClassMapping classMapping, T obfEntry);
309 }
310
311 public static Doer<FieldEntry> getFieldDoer() {
312 return new Doer<FieldEntry>() {
313
314 @Override
315 public Collection<FieldEntry> getDroppedEntries(MappingsChecker checker) {
316 return checker.getDroppedFieldMappings().keySet();
317 }
318
319 @Override
320 public Collection<FieldEntry> getObfEntries(JarIndex jarIndex) {
321 return jarIndex.getObfFieldEntries();
322 }
323
324 @Override
325 public Collection<? extends MemberMapping<FieldEntry>> getMappings(ClassMapping destClassMapping) {
326 return (Collection<? extends MemberMapping<FieldEntry>>)destClassMapping.fields();
327 }
328
329 @Override
330 public Set<FieldEntry> filterEntries(Collection<FieldEntry> obfDestFields, FieldEntry obfSourceField, ClassMatches classMatches) {
331 Set<FieldEntry> out = Sets.newHashSet();
332 for (FieldEntry obfDestField : obfDestFields) {
333 Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse());
334 if (translatedDestType.equals(obfSourceField.getType())) {
335 out.add(obfDestField);
336 }
337 }
338 return out;
339 }
340
341 @Override
342 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<FieldEntry> memberMapping, FieldEntry newField) {
343 FieldMapping fieldMapping = (FieldMapping)memberMapping;
344 classMapping.setFieldObfNameAndType(fieldMapping.getObfName(), fieldMapping.getObfType(), newField.getName(), newField.getType());
345 }
346
347 @Override
348 public boolean hasObfMember(ClassMapping classMapping, FieldEntry obfField) {
349 return classMapping.getFieldByObf(obfField.getName(), obfField.getType()) != null;
350 }
351
352 @Override
353 public void removeMemberByObf(ClassMapping classMapping, FieldEntry obfField) {
354 classMapping.removeFieldMapping(classMapping.getFieldByObf(obfField.getName(), obfField.getType()));
355 }
356 };
357 }
358
359 public static Doer<BehaviorEntry> getMethodDoer() {
360 return new Doer<BehaviorEntry>() {
361
362 @Override
363 public Collection<BehaviorEntry> getDroppedEntries(MappingsChecker checker) {
364 return checker.getDroppedMethodMappings().keySet();
365 }
366
367 @Override
368 public Collection<BehaviorEntry> getObfEntries(JarIndex jarIndex) {
369 return jarIndex.getObfBehaviorEntries();
370 }
371
372 @Override
373 public Collection<? extends MemberMapping<BehaviorEntry>> getMappings(ClassMapping destClassMapping) {
374 return (Collection<? extends MemberMapping<BehaviorEntry>>)destClassMapping.methods();
375 }
376
377 @Override
378 public Set<BehaviorEntry> filterEntries(Collection<BehaviorEntry> obfDestFields, BehaviorEntry obfSourceField, ClassMatches classMatches) {
379 Set<BehaviorEntry> out = Sets.newHashSet();
380 for (BehaviorEntry obfDestField : obfDestFields) {
381 Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse());
382 if (translatedDestSignature == null && obfSourceField.getSignature() == null) {
383 out.add(obfDestField);
384 } else if (translatedDestSignature == null || obfSourceField.getSignature() == null) {
385 // skip it
386 } else if (translatedDestSignature.equals(obfSourceField.getSignature())) {
387 out.add(obfDestField);
388 }
389 }
390 return out;
391 }
392
393 @Override
394 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<BehaviorEntry> memberMapping, BehaviorEntry newBehavior) {
395 MethodMapping methodMapping = (MethodMapping)memberMapping;
396 classMapping.setMethodObfNameAndSignature(methodMapping.getObfName(), methodMapping.getObfSignature(), newBehavior.getName(), newBehavior.getSignature());
397 }
398
399 @Override
400 public boolean hasObfMember(ClassMapping classMapping, BehaviorEntry obfBehavior) {
401 return classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()) != null;
402 }
403
404 @Override
405 public void removeMemberByObf(ClassMapping classMapping, BehaviorEntry obfBehavior) {
406 classMapping.removeMethodMapping(classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()));
407 }
408 };
409 }
410
411 public static <T extends Entry> MemberMatches<T> computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer<T> doer) {
412
413 MemberMatches<T> memberMatches = new MemberMatches<T>();
414
415 // unmatched source fields are easy
416 MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex());
417 checker.dropBrokenMappings(destMappings);
418 for (T destObfEntry : doer.getDroppedEntries(checker)) {
419 T srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse());
420 memberMatches.addUnmatchedSourceEntry(srcObfEntry);
421 }
422
423 // get matched fields (anything that's left after the checks/drops is matched(
424 for (ClassMapping classMapping : destMappings.classes()) {
425 collectMatchedFields(memberMatches, classMapping, classMatches, doer);
426 }
427
428 // get unmatched dest fields
429 for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) {
430 if (!memberMatches.isMatchedDestEntry(destEntry)) {
431 memberMatches.addUnmatchedDestEntry(destEntry);
432 }
433 }
434
435 System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries...");
436
437 // go through the unmatched source fields and try to pick out the easy matches
438 for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) {
439 for (T obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) {
440
441 // get the possible dest matches
442 ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass);
443
444 // filter by type/signature
445 Set<T> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches);
446
447 if (obfDestEntries.size() == 1) {
448 // make the easy match
449 memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next());
450 } else if (obfDestEntries.isEmpty()) {
451 // no match is possible =(
452 memberMatches.makeSourceUnmatchable(obfSourceEntry);
453 }
454 }
455 }
456
457 System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries",
458 memberMatches.getUnmatchedSourceEntries().size(),
459 memberMatches.getUnmatchableSourceEntries().size()
460 ));
461
462 return memberMatches;
463 }
464
465 private static <T extends Entry> void collectMatchedFields(MemberMatches<T> memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer<T> doer) {
466
467 // get the fields for this class
468 for (MemberMapping<T> destEntryMapping : doer.getMappings(destClassMapping)) {
469 T destObfField = destEntryMapping.getObfEntry(destClassMapping.getObfEntry());
470 T srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse());
471 memberMatches.addMatch(srcObfField, destObfField);
472 }
473
474 // recurse
475 for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) {
476 collectMatchedFields(memberMatches, destInnerClassMapping, classMatches, doer);
477 }
478 }
479
480 @SuppressWarnings("unchecked")
481 private static <T extends Entry> T translate(T in, BiMap<ClassEntry,ClassEntry> map) {
482 if (in instanceof FieldEntry) {
483 return (T)new FieldEntry(
484 map.get(in.getClassEntry()),
485 in.getName(),
486 translate(((FieldEntry)in).getType(), map)
487 );
488 } else if (in instanceof MethodEntry) {
489 return (T)new MethodEntry(
490 map.get(in.getClassEntry()),
491 in.getName(),
492 translate(((MethodEntry)in).getSignature(), map)
493 );
494 } else if (in instanceof ConstructorEntry) {
495 return (T)new ConstructorEntry(
496 map.get(in.getClassEntry()),
497 translate(((ConstructorEntry)in).getSignature(), map)
498 );
499 }
500 throw new Error("Unhandled entry type: " + in.getClass());
501 }
502
503 private static Type translate(Type type, final BiMap<ClassEntry,ClassEntry> map) {
504 return new Type(type, new ClassNameReplacer() {
505 @Override
506 public String replace(String inClassName) {
507 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName));
508 if (outClassEntry == null) {
509 return null;
510 }
511 return outClassEntry.getName();
512 }
513 });
514 }
515
516 private static Signature translate(Signature signature, final BiMap<ClassEntry,ClassEntry> map) {
517 if (signature == null) {
518 return null;
519 }
520 return new Signature(signature, new ClassNameReplacer() {
521 @Override
522 public String replace(String inClassName) {
523 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName));
524 if (outClassEntry == null) {
525 return null;
526 }
527 return outClassEntry.getName();
528 }
529 });
530 }
531
532 public static <T extends Entry> void applyMemberMatches(Mappings mappings, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) {
533 for (ClassMapping classMapping : mappings.classes()) {
534 applyMemberMatches(classMapping, classMatches, memberMatches, doer);
535 }
536 }
537
538 private static <T extends Entry> void applyMemberMatches(ClassMapping classMapping, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) {
539
540 // get the classes
541 ClassEntry obfDestClass = classMapping.getObfEntry();
542
543 // make a map of all the renames we need to make
544 Map<T,T> renames = Maps.newHashMap();
545 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) {
546 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass);
547 T obfSourceEntry = getSourceEntryFromDestMapping(memberMapping, obfDestClass, classMatches);
548
549 // but drop the unmatchable things
550 if (memberMatches.isUnmatchableSourceEntry(obfSourceEntry)) {
551 doer.removeMemberByObf(classMapping, obfOldDestEntry);
552 continue;
553 }
554
555 T obfNewDestEntry = memberMatches.matches().get(obfSourceEntry);
556 if (obfNewDestEntry != null && !obfOldDestEntry.getName().equals(obfNewDestEntry.getName())) {
557 renames.put(obfOldDestEntry, obfNewDestEntry);
558 }
559 }
560
561 if (!renames.isEmpty()) {
562
563 // apply to this class (should never need more than n passes)
564 int numRenamesAppliedThisRound;
565 do {
566 numRenamesAppliedThisRound = 0;
567
568 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) {
569 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass);
570 T obfNewDestEntry = renames.get(obfOldDestEntry);
571 if (obfNewDestEntry != null) {
572 // make sure this rename won't cause a collision
573 // otherwise, save it for the next round and try again next time
574 if (!doer.hasObfMember(classMapping, obfNewDestEntry)) {
575 doer.setUpdateObfMember(classMapping, memberMapping, obfNewDestEntry);
576 renames.remove(obfOldDestEntry);
577 numRenamesAppliedThisRound++;
578 }
579 }
580 }
581 } while(numRenamesAppliedThisRound > 0);
582
583 if (!renames.isEmpty()) {
584 System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d renames left.",
585 classMapping.getObfFullName(), renames.size()
586 ));
587 for (Map.Entry<T,T> entry : renames.entrySet()) {
588 System.err.println(String.format("\t%s -> %s", entry.getKey().getName(), entry.getValue().getName()));
589 }
590 }
591 }
592
593 // recurse
594 for (ClassMapping innerClassMapping : classMapping.innerClasses()) {
595 applyMemberMatches(innerClassMapping, classMatches, memberMatches, doer);
596 }
597 }
598
599 private static <T extends Entry> T getSourceEntryFromDestMapping(MemberMapping<T> destMemberMapping, ClassEntry obfDestClass, ClassMatches classMatches) {
600 return translate(destMemberMapping.getObfEntry(obfDestClass), classMatches.getUniqueMatches().inverse());
601 }
602}
diff --git a/src/cuchaz/enigma/convert/MatchesReader.java b/src/cuchaz/enigma/convert/MatchesReader.java
deleted file mode 100644
index 7514e2a9..00000000
--- a/src/cuchaz/enigma/convert/MatchesReader.java
+++ /dev/null
@@ -1,113 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.io.BufferedReader;
14import java.io.File;
15import java.io.FileReader;
16import java.io.IOException;
17import java.util.Collection;
18import java.util.List;
19
20import com.google.common.collect.Lists;
21
22import cuchaz.enigma.mapping.ClassEntry;
23import cuchaz.enigma.mapping.Entry;
24import cuchaz.enigma.mapping.EntryFactory;
25import cuchaz.enigma.mapping.FieldEntry;
26import cuchaz.enigma.mapping.Type;
27
28
29public class MatchesReader {
30
31 public static ClassMatches readClasses(File file)
32 throws IOException {
33 try (BufferedReader in = new BufferedReader(new FileReader(file))) {
34 ClassMatches matches = new ClassMatches();
35 String line = null;
36 while ((line = in.readLine()) != null) {
37 matches.add(readClassMatch(line));
38 }
39 return matches;
40 }
41 }
42
43 private static ClassMatch readClassMatch(String line)
44 throws IOException {
45 String[] sides = line.split(":", 2);
46 return new ClassMatch(readClasses(sides[0]), readClasses(sides[1]));
47 }
48
49 private static Collection<ClassEntry> readClasses(String in) {
50 List<ClassEntry> entries = Lists.newArrayList();
51 for (String className : in.split(",")) {
52 className = className.trim();
53 if (className.length() > 0) {
54 entries.add(new ClassEntry(className));
55 }
56 }
57 return entries;
58 }
59
60 public static <T extends Entry> MemberMatches<T> readMembers(File file)
61 throws IOException {
62 try (BufferedReader in = new BufferedReader(new FileReader(file))) {
63 MemberMatches<T> matches = new MemberMatches<T>();
64 String line = null;
65 while ((line = in.readLine()) != null) {
66 readMemberMatch(matches, line);
67 }
68 return matches;
69 }
70 }
71
72 private static <T extends Entry> void readMemberMatch(MemberMatches<T> matches, String line) {
73 if (line.startsWith("!")) {
74 T source = readEntry(line.substring(1));
75 matches.addUnmatchableSourceEntry(source);
76 } else {
77 String[] parts = line.split(":", 2);
78 T source = readEntry(parts[0]);
79 T dest = readEntry(parts[1]);
80 if (source != null && dest != null) {
81 matches.addMatch(source, dest);
82 } else if (source != null) {
83 matches.addUnmatchedSourceEntry(source);
84 } else if (dest != null) {
85 matches.addUnmatchedDestEntry(dest);
86 }
87 }
88 }
89
90 @SuppressWarnings("unchecked")
91 private static <T extends Entry> T readEntry(String in) {
92 if (in.length() <= 0) {
93 return null;
94 }
95 String[] parts = in.split(" ");
96 if (parts.length == 3 && parts[2].indexOf('(') < 0) {
97 return (T)new FieldEntry(
98 new ClassEntry(parts[0]),
99 parts[1],
100 new Type(parts[2])
101 );
102 } else {
103 assert(parts.length == 2 || parts.length == 3);
104 if (parts.length == 2) {
105 return (T)EntryFactory.getBehaviorEntry(parts[0], parts[1]);
106 } else if (parts.length == 3) {
107 return (T)EntryFactory.getBehaviorEntry(parts[0], parts[1], parts[2]);
108 } else {
109 throw new Error("Malformed behavior entry: " + in);
110 }
111 }
112 }
113}
diff --git a/src/cuchaz/enigma/convert/MatchesWriter.java b/src/cuchaz/enigma/convert/MatchesWriter.java
deleted file mode 100644
index 42c6b61b..00000000
--- a/src/cuchaz/enigma/convert/MatchesWriter.java
+++ /dev/null
@@ -1,121 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.io.File;
14import java.io.FileWriter;
15import java.io.IOException;
16import java.util.Map;
17
18import cuchaz.enigma.mapping.BehaviorEntry;
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.Entry;
21import cuchaz.enigma.mapping.FieldEntry;
22
23
24public class MatchesWriter {
25
26 public static void writeClasses(ClassMatches matches, File file)
27 throws IOException {
28 try (FileWriter out = new FileWriter(file)) {
29 for (ClassMatch match : matches) {
30 writeClassMatch(out, match);
31 }
32 }
33 }
34
35 private static void writeClassMatch(FileWriter out, ClassMatch match)
36 throws IOException {
37 writeClasses(out, match.sourceClasses);
38 out.write(":");
39 writeClasses(out, match.destClasses);
40 out.write("\n");
41 }
42
43 private static void writeClasses(FileWriter out, Iterable<ClassEntry> classes)
44 throws IOException {
45 boolean isFirst = true;
46 for (ClassEntry entry : classes) {
47 if (isFirst) {
48 isFirst = false;
49 } else {
50 out.write(",");
51 }
52 out.write(entry.toString());
53 }
54 }
55
56 public static <T extends Entry> void writeMembers(MemberMatches<T> matches, File file)
57 throws IOException {
58 try (FileWriter out = new FileWriter(file)) {
59 for (Map.Entry<T,T> match : matches.matches().entrySet()) {
60 writeMemberMatch(out, match.getKey(), match.getValue());
61 }
62 for (T entry : matches.getUnmatchedSourceEntries()) {
63 writeMemberMatch(out, entry, null);
64 }
65 for (T entry : matches.getUnmatchedDestEntries()) {
66 writeMemberMatch(out, null, entry);
67 }
68 for (T entry : matches.getUnmatchableSourceEntries()) {
69 writeUnmatchableEntry(out, entry);
70 }
71 }
72 }
73
74 private static <T extends Entry> void writeMemberMatch(FileWriter out, T source, T dest)
75 throws IOException {
76 if (source != null) {
77 writeEntry(out, source);
78 }
79 out.write(":");
80 if (dest != null) {
81 writeEntry(out, dest);
82 }
83 out.write("\n");
84 }
85
86 private static <T extends Entry> void writeUnmatchableEntry(FileWriter out, T entry)
87 throws IOException {
88 out.write("!");
89 writeEntry(out, entry);
90 out.write("\n");
91 }
92
93 private static <T extends Entry> void writeEntry(FileWriter out, T entry)
94 throws IOException {
95 if (entry instanceof FieldEntry) {
96 writeField(out, (FieldEntry)entry);
97 } else if (entry instanceof BehaviorEntry) {
98 writeBehavior(out, (BehaviorEntry)entry);
99 }
100 }
101
102 private static void writeField(FileWriter out, FieldEntry fieldEntry)
103 throws IOException {
104 out.write(fieldEntry.getClassName());
105 out.write(" ");
106 out.write(fieldEntry.getName());
107 out.write(" ");
108 out.write(fieldEntry.getType().toString());
109 }
110
111 private static void writeBehavior(FileWriter out, BehaviorEntry behaviorEntry)
112 throws IOException {
113 out.write(behaviorEntry.getClassName());
114 out.write(" ");
115 out.write(behaviorEntry.getName());
116 out.write(" ");
117 if (behaviorEntry.getSignature() != null) {
118 out.write(behaviorEntry.getSignature().toString());
119 }
120 }
121}
diff --git a/src/cuchaz/enigma/convert/MemberMatches.java b/src/cuchaz/enigma/convert/MemberMatches.java
deleted file mode 100644
index 29def159..00000000
--- a/src/cuchaz/enigma/convert/MemberMatches.java
+++ /dev/null
@@ -1,159 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Collection;
14import java.util.Set;
15
16import com.google.common.collect.BiMap;
17import com.google.common.collect.HashBiMap;
18import com.google.common.collect.HashMultimap;
19import com.google.common.collect.Multimap;
20import com.google.common.collect.Sets;
21
22import cuchaz.enigma.mapping.ClassEntry;
23import cuchaz.enigma.mapping.Entry;
24
25
26public class MemberMatches<T extends Entry> {
27
28 private BiMap<T,T> m_matches;
29 private Multimap<ClassEntry,T> m_matchedSourceEntries;
30 private Multimap<ClassEntry,T> m_unmatchedSourceEntries;
31 private Multimap<ClassEntry,T> m_unmatchedDestEntries;
32 private Multimap<ClassEntry,T> m_unmatchableSourceEntries;
33
34 public MemberMatches() {
35 m_matches = HashBiMap.create();
36 m_matchedSourceEntries = HashMultimap.create();
37 m_unmatchedSourceEntries = HashMultimap.create();
38 m_unmatchedDestEntries = HashMultimap.create();
39 m_unmatchableSourceEntries = HashMultimap.create();
40 }
41
42 public void addMatch(T srcEntry, T destEntry) {
43 boolean wasAdded = m_matches.put(srcEntry, destEntry) == null;
44 assert (wasAdded);
45 wasAdded = m_matchedSourceEntries.put(srcEntry.getClassEntry(), srcEntry);
46 assert (wasAdded);
47 }
48
49 public void addUnmatchedSourceEntry(T sourceEntry) {
50 boolean wasAdded = m_unmatchedSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry);
51 assert (wasAdded);
52 }
53
54 public void addUnmatchedSourceEntries(Iterable<T> sourceEntries) {
55 for (T sourceEntry : sourceEntries) {
56 addUnmatchedSourceEntry(sourceEntry);
57 }
58 }
59
60 public void addUnmatchedDestEntry(T destEntry) {
61 boolean wasAdded = m_unmatchedDestEntries.put(destEntry.getClassEntry(), destEntry);
62 assert (wasAdded);
63 }
64
65 public void addUnmatchedDestEntries(Iterable<T> destEntriesntries) {
66 for (T entry : destEntriesntries) {
67 addUnmatchedDestEntry(entry);
68 }
69 }
70
71 public void addUnmatchableSourceEntry(T sourceEntry) {
72 boolean wasAdded = m_unmatchableSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry);
73 assert (wasAdded);
74 }
75
76 public Set<ClassEntry> getSourceClassesWithUnmatchedEntries() {
77 return m_unmatchedSourceEntries.keySet();
78 }
79
80 public Collection<ClassEntry> getSourceClassesWithoutUnmatchedEntries() {
81 Set<ClassEntry> out = Sets.newHashSet();
82 out.addAll(m_matchedSourceEntries.keySet());
83 out.removeAll(m_unmatchedSourceEntries.keySet());
84 return out;
85 }
86
87 public Collection<T> getUnmatchedSourceEntries() {
88 return m_unmatchedSourceEntries.values();
89 }
90
91 public Collection<T> getUnmatchedSourceEntries(ClassEntry sourceClass) {
92 return m_unmatchedSourceEntries.get(sourceClass);
93 }
94
95 public Collection<T> getUnmatchedDestEntries() {
96 return m_unmatchedDestEntries.values();
97 }
98
99 public Collection<T> getUnmatchedDestEntries(ClassEntry destClass) {
100 return m_unmatchedDestEntries.get(destClass);
101 }
102
103 public Collection<T> getUnmatchableSourceEntries() {
104 return m_unmatchableSourceEntries.values();
105 }
106
107 public boolean hasSource(T sourceEntry) {
108 return m_matches.containsKey(sourceEntry) || m_unmatchedSourceEntries.containsValue(sourceEntry);
109 }
110
111 public boolean hasDest(T destEntry) {
112 return m_matches.containsValue(destEntry) || m_unmatchedDestEntries.containsValue(destEntry);
113 }
114
115 public BiMap<T,T> matches() {
116 return m_matches;
117 }
118
119 public boolean isMatchedSourceEntry(T sourceEntry) {
120 return m_matches.containsKey(sourceEntry);
121 }
122
123 public boolean isMatchedDestEntry(T destEntry) {
124 return m_matches.containsValue(destEntry);
125 }
126
127 public boolean isUnmatchableSourceEntry(T sourceEntry) {
128 return m_unmatchableSourceEntries.containsEntry(sourceEntry.getClassEntry(), sourceEntry);
129 }
130
131 public void makeMatch(T sourceEntry, T destEntry) {
132 boolean wasRemoved = m_unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
133 assert (wasRemoved);
134 wasRemoved = m_unmatchedDestEntries.remove(destEntry.getClassEntry(), destEntry);
135 assert (wasRemoved);
136 addMatch(sourceEntry, destEntry);
137 }
138
139 public boolean isMatched(T sourceEntry, T destEntry) {
140 T match = m_matches.get(sourceEntry);
141 return match != null && match.equals(destEntry);
142 }
143
144 public void unmakeMatch(T sourceEntry, T destEntry) {
145 boolean wasRemoved = m_matches.remove(sourceEntry) != null;
146 assert (wasRemoved);
147 wasRemoved = m_matchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
148 assert (wasRemoved);
149 addUnmatchedSourceEntry(sourceEntry);
150 addUnmatchedDestEntry(destEntry);
151 }
152
153 public void makeSourceUnmatchable(T sourceEntry) {
154 assert(!isMatchedSourceEntry(sourceEntry));
155 boolean wasRemoved = m_unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
156 assert (wasRemoved);
157 addUnmatchableSourceEntry(sourceEntry);
158 }
159}
diff --git a/src/cuchaz/enigma/gui/AboutDialog.java b/src/cuchaz/enigma/gui/AboutDialog.java
deleted file mode 100644
index 3eba1e50..00000000
--- a/src/cuchaz/enigma/gui/AboutDialog.java
+++ /dev/null
@@ -1,86 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14import java.awt.Container;
15import java.awt.Cursor;
16import java.awt.FlowLayout;
17import java.awt.event.ActionEvent;
18import java.awt.event.ActionListener;
19import java.io.IOException;
20
21import javax.swing.JButton;
22import javax.swing.JFrame;
23import javax.swing.JLabel;
24import javax.swing.JPanel;
25import javax.swing.WindowConstants;
26
27import cuchaz.enigma.Constants;
28import cuchaz.enigma.Util;
29
30public class AboutDialog {
31
32 public static void show(JFrame parent) {
33 // init frame
34 final JFrame frame = new JFrame(Constants.Name + " - About");
35 final Container pane = frame.getContentPane();
36 pane.setLayout(new FlowLayout());
37
38 // load the content
39 try {
40 String html = Util.readResourceToString("/about.html");
41 html = String.format(html, Constants.Name, Constants.Version);
42 JLabel label = new JLabel(html);
43 label.setHorizontalAlignment(JLabel.CENTER);
44 pane.add(label);
45 } catch (IOException ex) {
46 throw new Error(ex);
47 }
48
49 // show the link
50 String html = "<html><a href=\"%s\">%s</a></html>";
51 html = String.format(html, Constants.Url, Constants.Url);
52 JButton link = new JButton(html);
53 link.addActionListener(new ActionListener() {
54 @Override
55 public void actionPerformed(ActionEvent event) {
56 Util.openUrl(Constants.Url);
57 }
58 });
59 link.setBorderPainted(false);
60 link.setOpaque(false);
61 link.setBackground(Color.WHITE);
62 link.setCursor(new Cursor(Cursor.HAND_CURSOR));
63 link.setFocusable(false);
64 JPanel linkPanel = new JPanel();
65 linkPanel.add(link);
66 pane.add(linkPanel);
67
68 // show ok button
69 JButton okButton = new JButton("Ok");
70 pane.add(okButton);
71 okButton.addActionListener(new ActionListener() {
72 @Override
73 public void actionPerformed(ActionEvent arg0) {
74 frame.dispose();
75 }
76 });
77
78 // show the frame
79 pane.doLayout();
80 frame.setSize(400, 220);
81 frame.setResizable(false);
82 frame.setLocationRelativeTo(parent);
83 frame.setVisible(true);
84 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
85 }
86}
diff --git a/src/cuchaz/enigma/gui/BoxHighlightPainter.java b/src/cuchaz/enigma/gui/BoxHighlightPainter.java
deleted file mode 100644
index e5e05571..00000000
--- a/src/cuchaz/enigma/gui/BoxHighlightPainter.java
+++ /dev/null
@@ -1,64 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14import java.awt.Graphics;
15import java.awt.Rectangle;
16import java.awt.Shape;
17
18import javax.swing.text.BadLocationException;
19import javax.swing.text.Highlighter;
20import javax.swing.text.JTextComponent;
21
22public abstract class BoxHighlightPainter implements Highlighter.HighlightPainter {
23
24 private Color m_fillColor;
25 private Color m_borderColor;
26
27 protected BoxHighlightPainter(Color fillColor, Color borderColor) {
28 m_fillColor = fillColor;
29 m_borderColor = borderColor;
30 }
31
32 @Override
33 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) {
34 Rectangle bounds = getBounds(text, start, end);
35
36 // fill the area
37 if (m_fillColor != null) {
38 g.setColor(m_fillColor);
39 g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
40 }
41
42 // draw a box around the area
43 g.setColor(m_borderColor);
44 g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
45 }
46
47 protected static Rectangle getBounds(JTextComponent text, int start, int end) {
48 try {
49 // determine the bounds of the text
50 Rectangle bounds = text.modelToView(start).union(text.modelToView(end));
51
52 // adjust the box so it looks nice
53 bounds.x -= 2;
54 bounds.width += 2;
55 bounds.y += 1;
56 bounds.height -= 2;
57
58 return bounds;
59 } catch (BadLocationException ex) {
60 // don't care... just return something
61 return new Rectangle(0, 0, 0, 0);
62 }
63 }
64}
diff --git a/src/cuchaz/enigma/gui/BrowserCaret.java b/src/cuchaz/enigma/gui/BrowserCaret.java
deleted file mode 100644
index 6af4d248..00000000
--- a/src/cuchaz/enigma/gui/BrowserCaret.java
+++ /dev/null
@@ -1,45 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Graphics;
14import java.awt.Shape;
15
16import javax.swing.text.DefaultCaret;
17import javax.swing.text.Highlighter;
18import javax.swing.text.JTextComponent;
19
20public class BrowserCaret extends DefaultCaret {
21
22 private static final long serialVersionUID = 1158977422507969940L;
23
24 private static final Highlighter.HighlightPainter m_selectionPainter = new Highlighter.HighlightPainter() {
25 @Override
26 public void paint(Graphics g, int p0, int p1, Shape bounds, JTextComponent c) {
27 // don't paint anything
28 }
29 };
30
31 @Override
32 public boolean isSelectionVisible() {
33 return false;
34 }
35
36 @Override
37 public boolean isVisible() {
38 return true;
39 }
40
41 @Override
42 public Highlighter.HighlightPainter getSelectionPainter() {
43 return m_selectionPainter;
44 }
45}
diff --git a/src/cuchaz/enigma/gui/ClassMatchingGui.java b/src/cuchaz/enigma/gui/ClassMatchingGui.java
deleted file mode 100644
index 416b01f4..00000000
--- a/src/cuchaz/enigma/gui/ClassMatchingGui.java
+++ /dev/null
@@ -1,622 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.Dimension;
16import java.awt.FlowLayout;
17import java.awt.event.ActionEvent;
18import java.awt.event.ActionListener;
19import java.util.Collection;
20import java.util.Collections;
21import java.util.Comparator;
22import java.util.List;
23import java.util.Map;
24
25import javax.swing.BoxLayout;
26import javax.swing.ButtonGroup;
27import javax.swing.JButton;
28import javax.swing.JCheckBox;
29import javax.swing.JFrame;
30import javax.swing.JLabel;
31import javax.swing.JPanel;
32import javax.swing.JRadioButton;
33import javax.swing.JScrollPane;
34import javax.swing.JSplitPane;
35import javax.swing.SwingConstants;
36import javax.swing.WindowConstants;
37
38import com.google.common.collect.BiMap;
39import com.google.common.collect.Lists;
40import com.google.common.collect.Maps;
41
42import cuchaz.enigma.Constants;
43import cuchaz.enigma.Deobfuscator;
44import cuchaz.enigma.convert.ClassIdentifier;
45import cuchaz.enigma.convert.ClassIdentity;
46import cuchaz.enigma.convert.ClassMatch;
47import cuchaz.enigma.convert.ClassMatches;
48import cuchaz.enigma.convert.ClassMatching;
49import cuchaz.enigma.convert.ClassNamer;
50import cuchaz.enigma.convert.MappingsConverter;
51import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
52import cuchaz.enigma.mapping.ClassEntry;
53import cuchaz.enigma.mapping.Mappings;
54import cuchaz.enigma.mapping.MappingsChecker;
55import de.sciss.syntaxpane.DefaultSyntaxKit;
56
57
58public class ClassMatchingGui {
59
60 private static enum SourceType {
61 Matched {
62
63 @Override
64 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
65 return matches.getUniqueMatches().keySet();
66 }
67 },
68 Unmatched {
69
70 @Override
71 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
72 return matches.getUnmatchedSourceClasses();
73 }
74 },
75 Ambiguous {
76
77 @Override
78 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
79 return matches.getAmbiguouslyMatchedSourceClasses();
80 }
81 };
82
83 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
84 JRadioButton button = new JRadioButton(name(), this == getDefault());
85 button.setActionCommand(name());
86 button.addActionListener(listener);
87 group.add(button);
88 return button;
89 }
90
91 public abstract Collection<ClassEntry> getSourceClasses(ClassMatches matches);
92
93 public static SourceType getDefault() {
94 return values()[0];
95 }
96 }
97
98 public static interface SaveListener {
99 public void save(ClassMatches matches);
100 }
101
102 // controls
103 private JFrame m_frame;
104 private ClassSelector m_sourceClasses;
105 private ClassSelector m_destClasses;
106 private CodeReader m_sourceReader;
107 private CodeReader m_destReader;
108 private JLabel m_sourceClassLabel;
109 private JLabel m_destClassLabel;
110 private JButton m_matchButton;
111 private Map<SourceType,JRadioButton> m_sourceTypeButtons;
112 private JCheckBox m_advanceCheck;
113 private JCheckBox m_top10Matches;
114
115 private ClassMatches m_classMatches;
116 private Deobfuscator m_sourceDeobfuscator;
117 private Deobfuscator m_destDeobfuscator;
118 private ClassEntry m_sourceClass;
119 private ClassEntry m_destClass;
120 private SourceType m_sourceType;
121 private SaveListener m_saveListener;
122
123 public ClassMatchingGui(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
124
125 m_classMatches = matches;
126 m_sourceDeobfuscator = sourceDeobfuscator;
127 m_destDeobfuscator = destDeobfuscator;
128
129 // init frame
130 m_frame = new JFrame(Constants.Name + " - Class Matcher");
131 final Container pane = m_frame.getContentPane();
132 pane.setLayout(new BorderLayout());
133
134 // init source side
135 JPanel sourcePanel = new JPanel();
136 sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS));
137 sourcePanel.setPreferredSize(new Dimension(200, 0));
138 pane.add(sourcePanel, BorderLayout.WEST);
139 sourcePanel.add(new JLabel("Source Classes"));
140
141 // init source type radios
142 JPanel sourceTypePanel = new JPanel();
143 sourcePanel.add(sourceTypePanel);
144 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
145 ActionListener sourceTypeListener = new ActionListener() {
146 @Override
147 public void actionPerformed(ActionEvent event) {
148 setSourceType(SourceType.valueOf(event.getActionCommand()));
149 }
150 };
151 ButtonGroup sourceTypeButtons = new ButtonGroup();
152 m_sourceTypeButtons = Maps.newHashMap();
153 for (SourceType sourceType : SourceType.values()) {
154 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
155 m_sourceTypeButtons.put(sourceType, button);
156 sourceTypePanel.add(button);
157 }
158
159 m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
160 m_sourceClasses.setListener(new ClassSelectionListener() {
161 @Override
162 public void onSelectClass(ClassEntry classEntry) {
163 setSourceClass(classEntry);
164 }
165 });
166 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
167 sourcePanel.add(sourceScroller);
168
169 // init dest side
170 JPanel destPanel = new JPanel();
171 destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS));
172 destPanel.setPreferredSize(new Dimension(200, 0));
173 pane.add(destPanel, BorderLayout.WEST);
174 destPanel.add(new JLabel("Destination Classes"));
175
176 m_top10Matches = new JCheckBox("Show only top 10 matches");
177 destPanel.add(m_top10Matches);
178 m_top10Matches.addActionListener(new ActionListener() {
179 @Override
180 public void actionPerformed(ActionEvent event) {
181 toggleTop10Matches();
182 }
183 });
184
185 m_destClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
186 m_destClasses.setListener(new ClassSelectionListener() {
187 @Override
188 public void onSelectClass(ClassEntry classEntry) {
189 setDestClass(classEntry);
190 }
191 });
192 JScrollPane destScroller = new JScrollPane(m_destClasses);
193 destPanel.add(destScroller);
194
195 JButton autoMatchButton = new JButton("AutoMatch");
196 autoMatchButton.addActionListener(new ActionListener() {
197 @Override
198 public void actionPerformed(ActionEvent event) {
199 autoMatch();
200 }
201 });
202 destPanel.add(autoMatchButton);
203
204 // init source panels
205 DefaultSyntaxKit.initKit();
206 m_sourceReader = new CodeReader();
207 m_destReader = new CodeReader();
208
209 // init all the splits
210 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(m_sourceReader));
211 splitLeft.setResizeWeight(0); // let the right side take all the slack
212 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_destReader), destPanel);
213 splitRight.setResizeWeight(1); // let the left side take all the slack
214 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight);
215 splitCenter.setResizeWeight(0.5); // resize 50:50
216 pane.add(splitCenter, BorderLayout.CENTER);
217 splitCenter.resetToPreferredSizes();
218
219 // init bottom panel
220 JPanel bottomPanel = new JPanel();
221 bottomPanel.setLayout(new FlowLayout());
222
223 m_sourceClassLabel = new JLabel();
224 m_sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT);
225 m_destClassLabel = new JLabel();
226 m_destClassLabel.setHorizontalAlignment(SwingConstants.LEFT);
227
228 m_matchButton = new JButton();
229
230 m_advanceCheck = new JCheckBox("Advance to next likely match");
231 m_advanceCheck.addActionListener(new ActionListener() {
232 @Override
233 public void actionPerformed(ActionEvent event) {
234 if (m_advanceCheck.isSelected()) {
235 advance();
236 }
237 }
238 });
239
240 bottomPanel.add(m_sourceClassLabel);
241 bottomPanel.add(m_matchButton);
242 bottomPanel.add(m_destClassLabel);
243 bottomPanel.add(m_advanceCheck);
244 pane.add(bottomPanel, BorderLayout.SOUTH);
245
246 // show the frame
247 pane.doLayout();
248 m_frame.setSize(1024, 576);
249 m_frame.setMinimumSize(new Dimension(640, 480));
250 m_frame.setVisible(true);
251 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
252
253 // init state
254 updateDestMappings();
255 setSourceType(SourceType.getDefault());
256 updateMatchButton();
257 m_saveListener = null;
258 }
259
260 public void setSaveListener(SaveListener val) {
261 m_saveListener = val;
262 }
263
264 private void updateDestMappings() {
265
266 Mappings newMappings = MappingsConverter.newMappings(
267 m_classMatches,
268 m_sourceDeobfuscator.getMappings(),
269 m_sourceDeobfuscator,
270 m_destDeobfuscator
271 );
272
273 // look for dropped mappings
274 MappingsChecker checker = new MappingsChecker(m_destDeobfuscator.getJarIndex());
275 checker.dropBrokenMappings(newMappings);
276
277 // count them
278 int numDroppedFields = checker.getDroppedFieldMappings().size();
279 int numDroppedMethods = checker.getDroppedMethodMappings().size();
280 System.out.println(String.format(
281 "%d mappings from matched classes don't match the dest jar:\n\t%5d fields\n\t%5d methods",
282 numDroppedFields + numDroppedMethods,
283 numDroppedFields,
284 numDroppedMethods
285 ));
286
287 m_destDeobfuscator.setMappings(newMappings);
288 }
289
290 protected void setSourceType(SourceType val) {
291
292 // show the source classes
293 m_sourceType = val;
294 m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_classMatches), m_sourceDeobfuscator));
295
296 // update counts
297 for (SourceType sourceType : SourceType.values()) {
298 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
299 sourceType.name(),
300 sourceType.getSourceClasses(m_classMatches).size()
301 ));
302 }
303 }
304
305 private Collection<ClassEntry> deobfuscateClasses(Collection<ClassEntry> in, Deobfuscator deobfuscator) {
306 List<ClassEntry> out = Lists.newArrayList();
307 for (ClassEntry entry : in) {
308
309 ClassEntry deobf = deobfuscator.deobfuscateEntry(entry);
310
311 // make sure we preserve any scores
312 if (entry instanceof ScoredClassEntry) {
313 deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry)entry).getScore());
314 }
315
316 out.add(deobf);
317 }
318 return out;
319 }
320
321 protected void setSourceClass(ClassEntry classEntry) {
322
323 Runnable onGetDestClasses = null;
324 if (m_advanceCheck.isSelected()) {
325 onGetDestClasses = new Runnable() {
326 @Override
327 public void run() {
328 pickBestDestClass();
329 }
330 };
331 }
332
333 setSourceClass(classEntry, onGetDestClasses);
334 }
335
336 protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) {
337
338 // update the current source class
339 m_sourceClass = classEntry;
340 m_sourceClassLabel.setText(m_sourceClass != null ? m_sourceClass.getName() : "");
341
342 if (m_sourceClass != null) {
343
344 // show the dest class(es)
345 ClassMatch match = m_classMatches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass));
346 assert(match != null);
347 if (match.destClasses.isEmpty()) {
348
349 m_destClasses.setClasses(null);
350
351 // run in a separate thread to keep ui responsive
352 new Thread() {
353 @Override
354 public void run() {
355 m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator));
356 m_destClasses.expandAll();
357
358 if (onGetDestClasses != null) {
359 onGetDestClasses.run();
360 }
361 }
362 }.start();
363
364 } else {
365
366 m_destClasses.setClasses(deobfuscateClasses(match.destClasses, m_destDeobfuscator));
367 m_destClasses.expandAll();
368
369 if (onGetDestClasses != null) {
370 onGetDestClasses.run();
371 }
372 }
373 }
374
375 setDestClass(null);
376 m_sourceReader.decompileClass(m_sourceClass, m_sourceDeobfuscator, new Runnable() {
377 @Override
378 public void run() {
379 m_sourceReader.navigateToClassDeclaration(m_sourceClass);
380 }
381 });
382
383 updateMatchButton();
384 }
385
386 private Collection<ClassEntry> getLikelyMatches(ClassEntry sourceClass) {
387
388 ClassEntry obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
389
390 // set up identifiers
391 ClassNamer namer = new ClassNamer(m_classMatches.getUniqueMatches());
392 ClassIdentifier sourceIdentifier = new ClassIdentifier(
393 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
394 namer.getSourceNamer(), true
395 );
396 ClassIdentifier destIdentifier = new ClassIdentifier(
397 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
398 namer.getDestNamer(), true
399 );
400
401 try {
402
403 // rank all the unmatched dest classes against the source class
404 ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass);
405 List<ClassEntry> scoredDestClasses = Lists.newArrayList();
406 for (ClassEntry unmatchedDestClass : m_classMatches.getUnmatchedDestClasses()) {
407 ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass);
408 float score = 100.0f*(sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity))
409 /(sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore());
410 scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score));
411 }
412
413 if (m_top10Matches.isSelected() && scoredDestClasses.size() > 10) {
414 Collections.sort(scoredDestClasses, new Comparator<ClassEntry>() {
415 @Override
416 public int compare(ClassEntry a, ClassEntry b) {
417 ScoredClassEntry sa = (ScoredClassEntry)a;
418 ScoredClassEntry sb = (ScoredClassEntry)b;
419 return -Float.compare(sa.getScore(), sb.getScore());
420 }
421 });
422 scoredDestClasses = scoredDestClasses.subList(0, 10);
423 }
424
425 return scoredDestClasses;
426
427 } catch (ClassNotFoundException ex) {
428 throw new Error("Unable to find class " + ex.getMessage());
429 }
430 }
431
432 protected void setDestClass(ClassEntry classEntry) {
433
434 // update the current source class
435 m_destClass = classEntry;
436 m_destClassLabel.setText(m_destClass != null ? m_destClass.getName() : "");
437
438 m_destReader.decompileClass(m_destClass, m_destDeobfuscator, new Runnable() {
439 @Override
440 public void run() {
441 m_destReader.navigateToClassDeclaration(m_destClass);
442 }
443 });
444
445 updateMatchButton();
446 }
447
448 private void updateMatchButton() {
449
450 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
451 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
452
453 BiMap<ClassEntry,ClassEntry> uniqueMatches = m_classMatches.getUniqueMatches();
454 boolean twoSelected = m_sourceClass != null && m_destClass != null;
455 boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest);
456 boolean canMatch = !uniqueMatches.containsKey(obfSource) && ! uniqueMatches.containsValue(obfDest);
457
458 GuiTricks.deactivateButton(m_matchButton);
459 if (twoSelected) {
460 if (isMatched) {
461 GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() {
462 @Override
463 public void actionPerformed(ActionEvent event) {
464 onUnmatchClick();
465 }
466 });
467 } else if (canMatch) {
468 GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() {
469 @Override
470 public void actionPerformed(ActionEvent event) {
471 onMatchClick();
472 }
473 });
474 }
475 }
476 }
477
478 private void onMatchClick() {
479 // precondition: source and dest classes are set correctly
480
481 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
482 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
483
484 // remove the classes from their match
485 m_classMatches.removeSource(obfSource);
486 m_classMatches.removeDest(obfDest);
487
488 // add them as matched classes
489 m_classMatches.add(new ClassMatch(obfSource, obfDest));
490
491 ClassEntry nextClass = null;
492 if (m_advanceCheck.isSelected()) {
493 nextClass = m_sourceClasses.getNextClass(m_sourceClass);
494 }
495
496 save();
497 updateMatches();
498
499 if (nextClass != null) {
500 advance(nextClass);
501 }
502 }
503
504 private void onUnmatchClick() {
505 // precondition: source and dest classes are set to a unique match
506
507 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
508
509 // remove the source to break the match, then add the source back as unmatched
510 m_classMatches.removeSource(obfSource);
511 m_classMatches.add(new ClassMatch(obfSource, null));
512
513 save();
514 updateMatches();
515 }
516
517 private void updateMatches() {
518 updateDestMappings();
519 setDestClass(null);
520 m_destClasses.setClasses(null);
521 updateMatchButton();
522
523 // remember where we were in the source tree
524 String packageName = m_sourceClasses.getSelectedPackage();
525
526 setSourceType(m_sourceType);
527
528 m_sourceClasses.expandPackage(packageName);
529 }
530
531 private void save() {
532 if (m_saveListener != null) {
533 m_saveListener.save(m_classMatches);
534 }
535 }
536
537 private void autoMatch() {
538
539 System.out.println("Automatching...");
540
541 // compute a new matching
542 ClassMatching matching = MappingsConverter.computeMatching(
543 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
544 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
545 m_classMatches.getUniqueMatches()
546 );
547 ClassMatches newMatches = new ClassMatches(matching.matches());
548 System.out.println(String.format("Automatch found %d new matches",
549 newMatches.getUniqueMatches().size() - m_classMatches.getUniqueMatches().size()
550 ));
551
552 // update the current matches
553 m_classMatches = newMatches;
554 save();
555 updateMatches();
556 }
557
558 private void advance() {
559 advance(null);
560 }
561
562 private void advance(ClassEntry sourceClass) {
563
564 // make sure we have a source class
565 if (sourceClass == null) {
566 sourceClass = m_sourceClasses.getSelectedClass();
567 if (sourceClass != null) {
568 sourceClass = m_sourceClasses.getNextClass(sourceClass);
569 } else {
570 sourceClass = m_sourceClasses.getFirstClass();
571 }
572 }
573
574 // set the source class
575 setSourceClass(sourceClass, new Runnable() {
576 @Override
577 public void run() {
578 pickBestDestClass();
579 }
580 });
581 m_sourceClasses.setSelectionClass(sourceClass);
582 }
583
584 private void pickBestDestClass() {
585
586 // then, pick the best dest class
587 ClassEntry firstClass = null;
588 ScoredClassEntry bestDestClass = null;
589 for (ClassSelectorPackageNode packageNode : m_destClasses.packageNodes()) {
590 for (ClassSelectorClassNode classNode : m_destClasses.classNodes(packageNode)) {
591 if (firstClass == null) {
592 firstClass = classNode.getClassEntry();
593 }
594 if (classNode.getClassEntry() instanceof ScoredClassEntry) {
595 ScoredClassEntry scoredClass = (ScoredClassEntry)classNode.getClassEntry();
596 if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) {
597 bestDestClass = scoredClass;
598 }
599 }
600 }
601 }
602
603 // pick the entry to show
604 ClassEntry destClass = null;
605 if (bestDestClass != null) {
606 destClass = bestDestClass;
607 } else if (firstClass != null) {
608 destClass = firstClass;
609 }
610
611 setDestClass(destClass);
612 m_destClasses.setSelectionClass(destClass);
613 }
614
615 private void toggleTop10Matches() {
616 if (m_sourceClass != null) {
617 m_destClasses.clearSelection();
618 m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator));
619 m_destClasses.expandAll();
620 }
621 }
622}
diff --git a/src/cuchaz/enigma/gui/ClassSelector.java b/src/cuchaz/enigma/gui/ClassSelector.java
deleted file mode 100644
index 11333a96..00000000
--- a/src/cuchaz/enigma/gui/ClassSelector.java
+++ /dev/null
@@ -1,293 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.event.MouseAdapter;
14import java.awt.event.MouseEvent;
15import java.util.Collection;
16import java.util.Collections;
17import java.util.Comparator;
18import java.util.Enumeration;
19import java.util.List;
20import java.util.Map;
21
22import javax.swing.JTree;
23import javax.swing.tree.DefaultMutableTreeNode;
24import javax.swing.tree.DefaultTreeModel;
25import javax.swing.tree.TreePath;
26
27import com.google.common.collect.ArrayListMultimap;
28import com.google.common.collect.Lists;
29import com.google.common.collect.Maps;
30import com.google.common.collect.Multimap;
31
32import cuchaz.enigma.mapping.ClassEntry;
33
34public class ClassSelector extends JTree {
35
36 private static final long serialVersionUID = -7632046902384775977L;
37
38 public interface ClassSelectionListener {
39 void onSelectClass(ClassEntry classEntry);
40 }
41
42 public static Comparator<ClassEntry> ObfuscatedClassEntryComparator;
43 public static Comparator<ClassEntry> DeobfuscatedClassEntryComparator;
44
45 static {
46 ObfuscatedClassEntryComparator = new Comparator<ClassEntry>() {
47 @Override
48 public int compare(ClassEntry a, ClassEntry b) {
49 String aname = a.getName();
50 String bname = a.getName();
51 if (aname.length() != bname.length()) {
52 return aname.length() - bname.length();
53 }
54 return aname.compareTo(bname);
55 }
56 };
57
58 DeobfuscatedClassEntryComparator = new Comparator<ClassEntry>() {
59 @Override
60 public int compare(ClassEntry a, ClassEntry b) {
61 if (a instanceof ScoredClassEntry && b instanceof ScoredClassEntry) {
62 return Float.compare(
63 ((ScoredClassEntry)b).getScore(),
64 ((ScoredClassEntry)a).getScore()
65 );
66 }
67 return a.getName().compareTo(b.getName());
68 }
69 };
70 }
71
72 private ClassSelectionListener m_listener;
73 private Comparator<ClassEntry> m_comparator;
74
75 public ClassSelector(Comparator<ClassEntry> comparator) {
76 m_comparator = comparator;
77
78 // configure the tree control
79 setRootVisible(false);
80 setShowsRootHandles(false);
81 setModel(null);
82
83 // hook events
84 addMouseListener(new MouseAdapter() {
85 @Override
86 public void mouseClicked(MouseEvent event) {
87 if (m_listener != null && event.getClickCount() == 2) {
88 // get the selected node
89 TreePath path = getSelectionPath();
90 if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) {
91 ClassSelectorClassNode node = (ClassSelectorClassNode)path.getLastPathComponent();
92 m_listener.onSelectClass(node.getClassEntry());
93 }
94 }
95 }
96 });
97
98 // init defaults
99 m_listener = null;
100 }
101
102 public void setListener(ClassSelectionListener val) {
103 m_listener = val;
104 }
105
106 public void setClasses(Collection<ClassEntry> classEntries) {
107 if (classEntries == null) {
108 setModel(null);
109 return;
110 }
111
112 // build the package names
113 Map<String,ClassSelectorPackageNode> packages = Maps.newHashMap();
114 for (ClassEntry classEntry : classEntries) {
115 packages.put(classEntry.getPackageName(), null);
116 }
117
118 // sort the packages
119 List<String> sortedPackageNames = Lists.newArrayList(packages.keySet());
120 Collections.sort(sortedPackageNames, new Comparator<String>() {
121 @Override
122 public int compare(String a, String b) {
123 // I can never keep this rule straight when writing these damn things...
124 // a < b => -1, a == b => 0, a > b => +1
125
126 String[] aparts = a.split("/");
127 String[] bparts = b.split("/");
128 for (int i = 0; true; i++) {
129 if (i >= aparts.length) {
130 return -1;
131 } else if (i >= bparts.length) {
132 return 1;
133 }
134
135 int result = aparts[i].compareTo(bparts[i]);
136 if (result != 0) {
137 return result;
138 }
139 }
140 }
141 });
142
143 // create the root node and the package nodes
144 DefaultMutableTreeNode root = new DefaultMutableTreeNode();
145 for (String packageName : sortedPackageNames) {
146 ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName);
147 packages.put(packageName, node);
148 root.add(node);
149 }
150
151 // put the classes into packages
152 Multimap<String,ClassEntry> packagedClassEntries = ArrayListMultimap.create();
153 for (ClassEntry classEntry : classEntries) {
154 packagedClassEntries.put(classEntry.getPackageName(), classEntry);
155 }
156
157 // build the class nodes
158 for (String packageName : packagedClassEntries.keySet()) {
159 // sort the class entries
160 List<ClassEntry> classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName));
161 Collections.sort(classEntriesInPackage, m_comparator);
162
163 // create the nodes in order
164 for (ClassEntry classEntry : classEntriesInPackage) {
165 ClassSelectorPackageNode node = packages.get(packageName);
166 node.add(new ClassSelectorClassNode(classEntry));
167 }
168 }
169
170 // finally, update the tree control
171 setModel(new DefaultTreeModel(root));
172 }
173
174 public ClassEntry getSelectedClass() {
175 if (!isSelectionEmpty()) {
176 Object selectedNode = getSelectionPath().getLastPathComponent();
177 if (selectedNode instanceof ClassSelectorClassNode) {
178 ClassSelectorClassNode classNode = (ClassSelectorClassNode)selectedNode;
179 return classNode.getClassEntry();
180 }
181 }
182 return null;
183 }
184
185 public String getSelectedPackage() {
186 if (!isSelectionEmpty()) {
187 Object selectedNode = getSelectionPath().getLastPathComponent();
188 if (selectedNode instanceof ClassSelectorPackageNode) {
189 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode)selectedNode;
190 return packageNode.getPackageName();
191 } else if (selectedNode instanceof ClassSelectorClassNode) {
192 ClassSelectorClassNode classNode = (ClassSelectorClassNode)selectedNode;
193 return classNode.getClassEntry().getPackageName();
194 }
195 }
196 return null;
197 }
198
199 public Iterable<ClassSelectorPackageNode> packageNodes() {
200 List<ClassSelectorPackageNode> nodes = Lists.newArrayList();
201 DefaultMutableTreeNode root = (DefaultMutableTreeNode)getModel().getRoot();
202 Enumeration<?> children = root.children();
203 while (children.hasMoreElements()) {
204 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode)children.nextElement();
205 nodes.add(packageNode);
206 }
207 return nodes;
208 }
209
210 public Iterable<ClassSelectorClassNode> classNodes(ClassSelectorPackageNode packageNode) {
211 List<ClassSelectorClassNode> nodes = Lists.newArrayList();
212 Enumeration<?> children = packageNode.children();
213 while (children.hasMoreElements()) {
214 ClassSelectorClassNode classNode = (ClassSelectorClassNode)children.nextElement();
215 nodes.add(classNode);
216 }
217 return nodes;
218 }
219
220 public void expandPackage(String packageName) {
221 if (packageName == null) {
222 return;
223 }
224 for (ClassSelectorPackageNode packageNode : packageNodes()) {
225 if (packageNode.getPackageName().equals(packageName)) {
226 expandPath(new TreePath(new Object[] {getModel().getRoot(), packageNode}));
227 return;
228 }
229 }
230 }
231
232 public void expandAll() {
233 for (ClassSelectorPackageNode packageNode : packageNodes()) {
234 expandPath(new TreePath(new Object[] {getModel().getRoot(), packageNode}));
235 }
236 }
237
238 public ClassEntry getFirstClass() {
239 for (ClassSelectorPackageNode packageNode : packageNodes()) {
240 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
241 return classNode.getClassEntry();
242 }
243 }
244 return null;
245 }
246
247 public ClassSelectorPackageNode getPackageNode(ClassEntry entry) {
248 for (ClassSelectorPackageNode packageNode : packageNodes()) {
249 if (packageNode.getPackageName().equals(entry.getPackageName())) {
250 return packageNode;
251 }
252 }
253 return null;
254 }
255
256 public ClassEntry getNextClass(ClassEntry entry) {
257 boolean foundIt = false;
258 for (ClassSelectorPackageNode packageNode : packageNodes()) {
259 if (!foundIt) {
260 // skip to the package with our target in it
261 if (packageNode.getPackageName().equals(entry.getPackageName())) {
262 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
263 if (!foundIt) {
264 if (classNode.getClassEntry().equals(entry)) {
265 foundIt = true;
266 }
267 } else {
268 // return the next class
269 return classNode.getClassEntry();
270 }
271 }
272 }
273 } else {
274 // return the next class
275 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
276 return classNode.getClassEntry();
277 }
278 }
279 }
280 return null;
281 }
282
283 public void setSelectionClass(ClassEntry classEntry) {
284 expandPackage(classEntry.getPackageName());
285 for (ClassSelectorPackageNode packageNode : packageNodes()) {
286 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
287 if (classNode.getClassEntry().equals(classEntry)) {
288 setSelectionPath(new TreePath(new Object[] {getModel().getRoot(), packageNode, classNode}));
289 }
290 }
291 }
292 }
293}
diff --git a/src/cuchaz/enigma/gui/ClassSelectorClassNode.java b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java
deleted file mode 100644
index 1219e890..00000000
--- a/src/cuchaz/enigma/gui/ClassSelectorClassNode.java
+++ /dev/null
@@ -1,50 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import javax.swing.tree.DefaultMutableTreeNode;
14
15import cuchaz.enigma.mapping.ClassEntry;
16
17public class ClassSelectorClassNode extends DefaultMutableTreeNode {
18
19 private static final long serialVersionUID = -8956754339813257380L;
20
21 private ClassEntry m_classEntry;
22
23 public ClassSelectorClassNode(ClassEntry classEntry) {
24 m_classEntry = classEntry;
25 }
26
27 public ClassEntry getClassEntry() {
28 return m_classEntry;
29 }
30
31 @Override
32 public String toString() {
33 if (m_classEntry instanceof ScoredClassEntry) {
34 return String.format("%d%% %s", (int)((ScoredClassEntry)m_classEntry).getScore(), m_classEntry.getSimpleName());
35 }
36 return m_classEntry.getSimpleName();
37 }
38
39 @Override
40 public boolean equals(Object other) {
41 if (other instanceof ClassSelectorClassNode) {
42 return equals((ClassSelectorClassNode)other);
43 }
44 return false;
45 }
46
47 public boolean equals(ClassSelectorClassNode other) {
48 return m_classEntry.equals(other.m_classEntry);
49 }
50}
diff --git a/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java
deleted file mode 100644
index 7259f54d..00000000
--- a/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java
+++ /dev/null
@@ -1,45 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import javax.swing.tree.DefaultMutableTreeNode;
14
15public class ClassSelectorPackageNode extends DefaultMutableTreeNode {
16
17 private static final long serialVersionUID = -3730868701219548043L;
18
19 private String m_packageName;
20
21 public ClassSelectorPackageNode(String packageName) {
22 m_packageName = packageName;
23 }
24
25 public String getPackageName() {
26 return m_packageName;
27 }
28
29 @Override
30 public String toString() {
31 return m_packageName;
32 }
33
34 @Override
35 public boolean equals(Object other) {
36 if (other instanceof ClassSelectorPackageNode) {
37 return equals((ClassSelectorPackageNode)other);
38 }
39 return false;
40 }
41
42 public boolean equals(ClassSelectorPackageNode other) {
43 return m_packageName.equals(other.m_packageName);
44 }
45}
diff --git a/src/cuchaz/enigma/gui/CodeReader.java b/src/cuchaz/enigma/gui/CodeReader.java
deleted file mode 100644
index 5033a2cd..00000000
--- a/src/cuchaz/enigma/gui/CodeReader.java
+++ /dev/null
@@ -1,222 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Rectangle;
14import java.awt.event.ActionEvent;
15import java.awt.event.ActionListener;
16
17import javax.swing.JEditorPane;
18import javax.swing.SwingUtilities;
19import javax.swing.Timer;
20import javax.swing.event.CaretEvent;
21import javax.swing.event.CaretListener;
22import javax.swing.text.BadLocationException;
23import javax.swing.text.Highlighter.HighlightPainter;
24
25import com.strobel.decompiler.languages.java.ast.CompilationUnit;
26
27import cuchaz.enigma.Deobfuscator;
28import cuchaz.enigma.analysis.EntryReference;
29import cuchaz.enigma.analysis.SourceIndex;
30import cuchaz.enigma.analysis.Token;
31import cuchaz.enigma.mapping.ClassEntry;
32import cuchaz.enigma.mapping.Entry;
33import de.sciss.syntaxpane.DefaultSyntaxKit;
34
35
36public class CodeReader extends JEditorPane {
37
38 private static final long serialVersionUID = 3673180950485748810L;
39
40 private static final Object m_lock = new Object();
41
42 public static interface SelectionListener {
43 void onSelect(EntryReference<Entry,Entry> reference);
44 }
45
46 private SelectionHighlightPainter m_selectionHighlightPainter;
47 private SourceIndex m_sourceIndex;
48 private SelectionListener m_selectionListener;
49
50 public CodeReader() {
51
52 setEditable(false);
53 setContentType("text/java");
54
55 // turn off token highlighting (it's wrong most of the time anyway...)
56 DefaultSyntaxKit kit = (DefaultSyntaxKit)getEditorKit();
57 kit.toggleComponent(this, "de.sciss.syntaxpane.components.TokenMarker");
58
59 // hook events
60 addCaretListener(new CaretListener() {
61 @Override
62 public void caretUpdate(CaretEvent event) {
63 if (m_selectionListener != null && m_sourceIndex != null) {
64 Token token = m_sourceIndex.getReferenceToken(event.getDot());
65 if (token != null) {
66 m_selectionListener.onSelect(m_sourceIndex.getDeobfReference(token));
67 } else {
68 m_selectionListener.onSelect(null);
69 }
70 }
71 }
72 });
73
74 m_selectionHighlightPainter = new SelectionHighlightPainter();
75 m_sourceIndex = null;
76 m_selectionListener = null;
77 }
78
79 public void setSelectionListener(SelectionListener val) {
80 m_selectionListener = val;
81 }
82
83 public void setCode(String code) {
84 // sadly, the java lexer is not thread safe, so we have to serialize all these calls
85 synchronized (m_lock) {
86 setText(code);
87 }
88 }
89
90 public SourceIndex getSourceIndex() {
91 return m_sourceIndex;
92 }
93
94 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) {
95 decompileClass(classEntry, deobfuscator, null);
96 }
97
98 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator, Runnable callback) {
99 decompileClass(classEntry, deobfuscator, null, callback);
100 }
101
102 public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Boolean ignoreBadTokens, final Runnable callback) {
103
104 if (classEntry == null) {
105 setCode(null);
106 return;
107 }
108
109 setCode("(decompiling...)");
110
111 // run decompilation in a separate thread to keep ui responsive
112 new Thread() {
113 @Override
114 public void run() {
115
116 // decompile it
117 CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClassName());
118 String source = deobfuscator.getSource(sourceTree);
119 setCode(source);
120 m_sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens);
121
122 if (callback != null) {
123 callback.run();
124 }
125 }
126 }.start();
127 }
128
129 public void navigateToClassDeclaration(ClassEntry classEntry) {
130
131 // navigate to the class declaration
132 Token token = m_sourceIndex.getDeclarationToken(classEntry);
133 if (token == null) {
134 // couldn't find the class declaration token, might be an anonymous class
135 // look for any declaration in that class instead
136 for (Entry entry : m_sourceIndex.declarations()) {
137 if (entry.getClassEntry().equals(classEntry)) {
138 token = m_sourceIndex.getDeclarationToken(entry);
139 break;
140 }
141 }
142 }
143
144 if (token != null) {
145 navigateToToken(token);
146 } else {
147 // couldn't find anything =(
148 System.out.println("Unable to find declaration in source for " + classEntry);
149 }
150 }
151
152 public void navigateToToken(final Token token) {
153 navigateToToken(this, token, m_selectionHighlightPainter);
154 }
155
156 // HACKHACK: someday we can update the main GUI to use this code reader
157 public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) {
158
159 // set the caret position to the token
160 editor.setCaretPosition(token.start);
161 editor.grabFocus();
162
163 try {
164 // make sure the token is visible in the scroll window
165 Rectangle start = editor.modelToView(token.start);
166 Rectangle end = editor.modelToView(token.end);
167 final Rectangle show = start.union(end);
168 show.grow(start.width * 10, start.height * 6);
169 SwingUtilities.invokeLater(new Runnable() {
170 @Override
171 public void run() {
172 editor.scrollRectToVisible(show);
173 }
174 });
175 } catch (BadLocationException ex) {
176 throw new Error(ex);
177 }
178
179 // highlight the token momentarily
180 final Timer timer = new Timer(200, new ActionListener() {
181 private int m_counter = 0;
182 private Object m_highlight = null;
183
184 @Override
185 public void actionPerformed(ActionEvent event) {
186 if (m_counter % 2 == 0) {
187 try {
188 m_highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter);
189 } catch (BadLocationException ex) {
190 // don't care
191 }
192 } else if (m_highlight != null) {
193 editor.getHighlighter().removeHighlight(m_highlight);
194 }
195
196 if (m_counter++ > 6) {
197 Timer timer = (Timer)event.getSource();
198 timer.stop();
199 }
200 }
201 });
202 timer.start();
203 }
204
205 public void setHighlightedTokens(Iterable<Token> tokens, HighlightPainter painter) {
206 for (Token token : tokens) {
207 setHighlightedToken(token, painter);
208 }
209 }
210
211 public void setHighlightedToken(Token token, HighlightPainter painter) {
212 try {
213 getHighlighter().addHighlight(token.start, token.end, painter);
214 } catch (BadLocationException ex) {
215 throw new IllegalArgumentException(ex);
216 }
217 }
218
219 public void clearHighlights() {
220 getHighlighter().removeAllHighlights();
221 }
222}
diff --git a/src/cuchaz/enigma/gui/CrashDialog.java b/src/cuchaz/enigma/gui/CrashDialog.java
deleted file mode 100644
index 904273c1..00000000
--- a/src/cuchaz/enigma/gui/CrashDialog.java
+++ /dev/null
@@ -1,101 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.FlowLayout;
16import java.awt.event.ActionEvent;
17import java.awt.event.ActionListener;
18import java.io.PrintWriter;
19import java.io.StringWriter;
20
21import javax.swing.BorderFactory;
22import javax.swing.JButton;
23import javax.swing.JFrame;
24import javax.swing.JLabel;
25import javax.swing.JPanel;
26import javax.swing.JScrollPane;
27import javax.swing.JTextArea;
28import javax.swing.WindowConstants;
29
30import cuchaz.enigma.Constants;
31
32public class CrashDialog {
33
34 private static CrashDialog m_instance = null;
35
36 private JFrame m_frame;
37 private JTextArea m_text;
38
39 private CrashDialog(JFrame parent) {
40 // init frame
41 m_frame = new JFrame(Constants.Name + " - Crash Report");
42 final Container pane = m_frame.getContentPane();
43 pane.setLayout(new BorderLayout());
44
45 JLabel label = new JLabel(Constants.Name + " has crashed! =(");
46 label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
47 pane.add(label, BorderLayout.NORTH);
48
49 // report panel
50 m_text = new JTextArea();
51 m_text.setTabSize(2);
52 pane.add(new JScrollPane(m_text), BorderLayout.CENTER);
53
54 // buttons panel
55 JPanel buttonsPanel = new JPanel();
56 FlowLayout buttonsLayout = new FlowLayout();
57 buttonsLayout.setAlignment(FlowLayout.RIGHT);
58 buttonsPanel.setLayout(buttonsLayout);
59 buttonsPanel.add(GuiTricks.unboldLabel(new JLabel("If you choose exit, you will lose any unsaved work.")));
60 JButton ignoreButton = new JButton("Ignore");
61 ignoreButton.addActionListener(new ActionListener() {
62 @Override
63 public void actionPerformed(ActionEvent event) {
64 // close (hide) the dialog
65 m_frame.setVisible(false);
66 }
67 });
68 buttonsPanel.add(ignoreButton);
69 JButton exitButton = new JButton("Exit");
70 exitButton.addActionListener(new ActionListener() {
71 @Override
72 public void actionPerformed(ActionEvent event) {
73 // exit enigma
74 System.exit(1);
75 }
76 });
77 buttonsPanel.add(exitButton);
78 pane.add(buttonsPanel, BorderLayout.SOUTH);
79
80 // show the frame
81 m_frame.setSize(600, 400);
82 m_frame.setLocationRelativeTo(parent);
83 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
84 }
85
86 public static void init(JFrame parent) {
87 m_instance = new CrashDialog(parent);
88 }
89
90 public static void show(Throwable ex) {
91 // get the error report
92 StringWriter buf = new StringWriter();
93 ex.printStackTrace(new PrintWriter(buf));
94 String report = buf.toString();
95
96 // show it!
97 m_instance.m_text.setText(report);
98 m_instance.m_frame.doLayout();
99 m_instance.m_frame.setVisible(true);
100 }
101}
diff --git a/src/cuchaz/enigma/gui/Gui.java b/src/cuchaz/enigma/gui/Gui.java
deleted file mode 100644
index f9192d31..00000000
--- a/src/cuchaz/enigma/gui/Gui.java
+++ /dev/null
@@ -1,1122 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Color;
15import java.awt.Container;
16import java.awt.Dimension;
17import java.awt.FlowLayout;
18import java.awt.GridLayout;
19import java.awt.event.ActionEvent;
20import java.awt.event.ActionListener;
21import java.awt.event.InputEvent;
22import java.awt.event.KeyAdapter;
23import java.awt.event.KeyEvent;
24import java.awt.event.MouseAdapter;
25import java.awt.event.MouseEvent;
26import java.awt.event.WindowAdapter;
27import java.awt.event.WindowEvent;
28import java.io.File;
29import java.io.IOException;
30import java.lang.Thread.UncaughtExceptionHandler;
31import java.util.Collection;
32import java.util.Collections;
33import java.util.List;
34import java.util.Vector;
35import java.util.jar.JarFile;
36
37import javax.swing.BorderFactory;
38import javax.swing.JEditorPane;
39import javax.swing.JFileChooser;
40import javax.swing.JFrame;
41import javax.swing.JLabel;
42import javax.swing.JList;
43import javax.swing.JMenu;
44import javax.swing.JMenuBar;
45import javax.swing.JMenuItem;
46import javax.swing.JOptionPane;
47import javax.swing.JPanel;
48import javax.swing.JPopupMenu;
49import javax.swing.JScrollPane;
50import javax.swing.JSplitPane;
51import javax.swing.JTabbedPane;
52import javax.swing.JTextField;
53import javax.swing.JTree;
54import javax.swing.KeyStroke;
55import javax.swing.ListSelectionModel;
56import javax.swing.WindowConstants;
57import javax.swing.event.CaretEvent;
58import javax.swing.event.CaretListener;
59import javax.swing.text.BadLocationException;
60import javax.swing.text.Highlighter;
61import javax.swing.tree.DefaultTreeModel;
62import javax.swing.tree.TreeNode;
63import javax.swing.tree.TreePath;
64
65import com.google.common.collect.Lists;
66
67import cuchaz.enigma.Constants;
68import cuchaz.enigma.ExceptionIgnorer;
69import cuchaz.enigma.analysis.BehaviorReferenceTreeNode;
70import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
71import cuchaz.enigma.analysis.ClassInheritanceTreeNode;
72import cuchaz.enigma.analysis.EntryReference;
73import cuchaz.enigma.analysis.FieldReferenceTreeNode;
74import cuchaz.enigma.analysis.MethodImplementationsTreeNode;
75import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
76import cuchaz.enigma.analysis.ReferenceTreeNode;
77import cuchaz.enigma.analysis.Token;
78import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
79import cuchaz.enigma.mapping.ArgumentEntry;
80import cuchaz.enigma.mapping.ClassEntry;
81import cuchaz.enigma.mapping.ConstructorEntry;
82import cuchaz.enigma.mapping.Entry;
83import cuchaz.enigma.mapping.FieldEntry;
84import cuchaz.enigma.mapping.IllegalNameException;
85import cuchaz.enigma.mapping.MappingParseException;
86import cuchaz.enigma.mapping.MethodEntry;
87import cuchaz.enigma.mapping.Signature;
88import de.sciss.syntaxpane.DefaultSyntaxKit;
89
90public class Gui {
91
92 private GuiController m_controller;
93
94 // controls
95 private JFrame m_frame;
96 private ClassSelector m_obfClasses;
97 private ClassSelector m_deobfClasses;
98 private JEditorPane m_editor;
99 private JPanel m_classesPanel;
100 private JSplitPane m_splitClasses;
101 private JPanel m_infoPanel;
102 private ObfuscatedHighlightPainter m_obfuscatedHighlightPainter;
103 private DeobfuscatedHighlightPainter m_deobfuscatedHighlightPainter;
104 private OtherHighlightPainter m_otherHighlightPainter;
105 private SelectionHighlightPainter m_selectionHighlightPainter;
106 private JTree m_inheritanceTree;
107 private JTree m_implementationsTree;
108 private JTree m_callsTree;
109 private JList<Token> m_tokens;
110 private JTabbedPane m_tabs;
111
112 // dynamic menu items
113 private JMenuItem m_closeJarMenu;
114 private JMenuItem m_openMappingsMenu;
115 private JMenuItem m_saveMappingsMenu;
116 private JMenuItem m_saveMappingsAsMenu;
117 private JMenuItem m_closeMappingsMenu;
118 private JMenuItem m_renameMenu;
119 private JMenuItem m_showInheritanceMenu;
120 private JMenuItem m_openEntryMenu;
121 private JMenuItem m_openPreviousMenu;
122 private JMenuItem m_showCallsMenu;
123 private JMenuItem m_showImplementationsMenu;
124 private JMenuItem m_toggleMappingMenu;
125 private JMenuItem m_exportSourceMenu;
126 private JMenuItem m_exportJarMenu;
127
128 // state
129 private EntryReference<Entry,Entry> m_reference;
130 private JFileChooser m_jarFileChooser;
131 private JFileChooser m_mappingsFileChooser;
132 private JFileChooser m_exportSourceFileChooser;
133 private JFileChooser m_exportJarFileChooser;
134
135 public Gui() {
136
137 // init frame
138 m_frame = new JFrame(Constants.Name);
139 final Container pane = m_frame.getContentPane();
140 pane.setLayout(new BorderLayout());
141
142 if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) {
143 // install a global exception handler to the event thread
144 CrashDialog.init(m_frame);
145 Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
146 @Override
147 public void uncaughtException(Thread thread, Throwable t) {
148 t.printStackTrace(System.err);
149 if (!ExceptionIgnorer.shouldIgnore(t)) {
150 CrashDialog.show(t);
151 }
152 }
153 });
154 }
155
156 m_controller = new GuiController(this);
157
158 // init file choosers
159 m_jarFileChooser = new JFileChooser();
160 m_mappingsFileChooser = new JFileChooser();
161 m_exportSourceFileChooser = new JFileChooser();
162 m_exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
163 m_exportJarFileChooser = new JFileChooser();
164
165 // init obfuscated classes list
166 m_obfClasses = new ClassSelector(ClassSelector.ObfuscatedClassEntryComparator);
167 m_obfClasses.setListener(new ClassSelectionListener() {
168 @Override
169 public void onSelectClass(ClassEntry classEntry) {
170 navigateTo(classEntry);
171 }
172 });
173 JScrollPane obfScroller = new JScrollPane(m_obfClasses);
174 JPanel obfPanel = new JPanel();
175 obfPanel.setLayout(new BorderLayout());
176 obfPanel.add(new JLabel("Obfuscated Classes"), BorderLayout.NORTH);
177 obfPanel.add(obfScroller, BorderLayout.CENTER);
178
179 // init deobfuscated classes list
180 m_deobfClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
181 m_deobfClasses.setListener(new ClassSelectionListener() {
182 @Override
183 public void onSelectClass(ClassEntry classEntry) {
184 navigateTo(classEntry);
185 }
186 });
187 JScrollPane deobfScroller = new JScrollPane(m_deobfClasses);
188 JPanel deobfPanel = new JPanel();
189 deobfPanel.setLayout(new BorderLayout());
190 deobfPanel.add(new JLabel("De-obfuscated Classes"), BorderLayout.NORTH);
191 deobfPanel.add(deobfScroller, BorderLayout.CENTER);
192
193 // set up classes panel (don't add the splitter yet)
194 m_splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, obfPanel, deobfPanel);
195 m_splitClasses.setResizeWeight(0.3);
196 m_classesPanel = new JPanel();
197 m_classesPanel.setLayout(new BorderLayout());
198 m_classesPanel.setPreferredSize(new Dimension(250, 0));
199
200 // init info panel
201 m_infoPanel = new JPanel();
202 m_infoPanel.setLayout(new GridLayout(4, 1, 0, 0));
203 m_infoPanel.setPreferredSize(new Dimension(0, 100));
204 m_infoPanel.setBorder(BorderFactory.createTitledBorder("Identifier Info"));
205 clearReference();
206
207 // init editor
208 DefaultSyntaxKit.initKit();
209 m_obfuscatedHighlightPainter = new ObfuscatedHighlightPainter();
210 m_deobfuscatedHighlightPainter = new DeobfuscatedHighlightPainter();
211 m_otherHighlightPainter = new OtherHighlightPainter();
212 m_selectionHighlightPainter = new SelectionHighlightPainter();
213 m_editor = new JEditorPane();
214 m_editor.setEditable(false);
215 m_editor.setCaret(new BrowserCaret());
216 JScrollPane sourceScroller = new JScrollPane(m_editor);
217 m_editor.setContentType("text/java");
218 m_editor.addCaretListener(new CaretListener() {
219 @Override
220 public void caretUpdate(CaretEvent event) {
221 onCaretMove(event.getDot());
222 }
223 });
224 m_editor.addKeyListener(new KeyAdapter() {
225 @Override
226 public void keyPressed(KeyEvent event) {
227 switch (event.getKeyCode()) {
228 case KeyEvent.VK_R:
229 m_renameMenu.doClick();
230 break;
231
232 case KeyEvent.VK_I:
233 m_showInheritanceMenu.doClick();
234 break;
235
236 case KeyEvent.VK_M:
237 m_showImplementationsMenu.doClick();
238 break;
239
240 case KeyEvent.VK_N:
241 m_openEntryMenu.doClick();
242 break;
243
244 case KeyEvent.VK_P:
245 m_openPreviousMenu.doClick();
246 break;
247
248 case KeyEvent.VK_C:
249 m_showCallsMenu.doClick();
250 break;
251
252 case KeyEvent.VK_T:
253 m_toggleMappingMenu.doClick();
254 break;
255 }
256 }
257 });
258
259 // turn off token highlighting (it's wrong most of the time anyway...)
260 DefaultSyntaxKit kit = (DefaultSyntaxKit)m_editor.getEditorKit();
261 kit.toggleComponent(m_editor, "de.sciss.syntaxpane.components.TokenMarker");
262
263 // init editor popup menu
264 JPopupMenu popupMenu = new JPopupMenu();
265 m_editor.setComponentPopupMenu(popupMenu);
266 {
267 JMenuItem menu = new JMenuItem("Rename");
268 menu.addActionListener(new ActionListener() {
269 @Override
270 public void actionPerformed(ActionEvent event) {
271 startRename();
272 }
273 });
274 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0));
275 menu.setEnabled(false);
276 popupMenu.add(menu);
277 m_renameMenu = menu;
278 }
279 {
280 JMenuItem menu = new JMenuItem("Show Inheritance");
281 menu.addActionListener(new ActionListener() {
282 @Override
283 public void actionPerformed(ActionEvent event) {
284 showInheritance();
285 }
286 });
287 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, 0));
288 menu.setEnabled(false);
289 popupMenu.add(menu);
290 m_showInheritanceMenu = menu;
291 }
292 {
293 JMenuItem menu = new JMenuItem("Show Implementations");
294 menu.addActionListener(new ActionListener() {
295 @Override
296 public void actionPerformed(ActionEvent event) {
297 showImplementations();
298 }
299 });
300 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0));
301 menu.setEnabled(false);
302 popupMenu.add(menu);
303 m_showImplementationsMenu = menu;
304 }
305 {
306 JMenuItem menu = new JMenuItem("Show Calls");
307 menu.addActionListener(new ActionListener() {
308 @Override
309 public void actionPerformed(ActionEvent event) {
310 showCalls();
311 }
312 });
313 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0));
314 menu.setEnabled(false);
315 popupMenu.add(menu);
316 m_showCallsMenu = menu;
317 }
318 {
319 JMenuItem menu = new JMenuItem("Go to Declaration");
320 menu.addActionListener(new ActionListener() {
321 @Override
322 public void actionPerformed(ActionEvent event) {
323 navigateTo(m_reference.entry);
324 }
325 });
326 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0));
327 menu.setEnabled(false);
328 popupMenu.add(menu);
329 m_openEntryMenu = menu;
330 }
331 {
332 JMenuItem menu = new JMenuItem("Go to previous");
333 menu.addActionListener(new ActionListener() {
334 @Override
335 public void actionPerformed(ActionEvent event) {
336 m_controller.openPreviousReference();
337 }
338 });
339 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0));
340 menu.setEnabled(false);
341 popupMenu.add(menu);
342 m_openPreviousMenu = menu;
343 }
344 {
345 JMenuItem menu = new JMenuItem("Mark as deobfuscated");
346 menu.addActionListener(new ActionListener() {
347 @Override
348 public void actionPerformed(ActionEvent event) {
349 toggleMapping();
350 }
351 });
352 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0));
353 menu.setEnabled(false);
354 popupMenu.add(menu);
355 m_toggleMappingMenu = menu;
356 }
357
358 // init inheritance panel
359 m_inheritanceTree = new JTree();
360 m_inheritanceTree.setModel(null);
361 m_inheritanceTree.addMouseListener(new MouseAdapter() {
362 @Override
363 public void mouseClicked(MouseEvent event) {
364 if (event.getClickCount() == 2) {
365 // get the selected node
366 TreePath path = m_inheritanceTree.getSelectionPath();
367 if (path == null) {
368 return;
369 }
370
371 Object node = path.getLastPathComponent();
372 if (node instanceof ClassInheritanceTreeNode) {
373 ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode)node;
374 navigateTo(new ClassEntry(classNode.getObfClassName()));
375 } else if (node instanceof MethodInheritanceTreeNode) {
376 MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode)node;
377 if (methodNode.isImplemented()) {
378 navigateTo(methodNode.getMethodEntry());
379 }
380 }
381 }
382 }
383 });
384 JPanel inheritancePanel = new JPanel();
385 inheritancePanel.setLayout(new BorderLayout());
386 inheritancePanel.add(new JScrollPane(m_inheritanceTree));
387
388 // init implementations panel
389 m_implementationsTree = new JTree();
390 m_implementationsTree.setModel(null);
391 m_implementationsTree.addMouseListener(new MouseAdapter() {
392 @Override
393 public void mouseClicked(MouseEvent event) {
394 if (event.getClickCount() == 2) {
395 // get the selected node
396 TreePath path = m_implementationsTree.getSelectionPath();
397 if (path == null) {
398 return;
399 }
400
401 Object node = path.getLastPathComponent();
402 if (node instanceof ClassImplementationsTreeNode) {
403 ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode)node;
404 navigateTo(classNode.getClassEntry());
405 } else if (node instanceof MethodImplementationsTreeNode) {
406 MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode)node;
407 navigateTo(methodNode.getMethodEntry());
408 }
409 }
410 }
411 });
412 JPanel implementationsPanel = new JPanel();
413 implementationsPanel.setLayout(new BorderLayout());
414 implementationsPanel.add(new JScrollPane(m_implementationsTree));
415
416 // init call panel
417 m_callsTree = new JTree();
418 m_callsTree.setModel(null);
419 m_callsTree.addMouseListener(new MouseAdapter() {
420 @SuppressWarnings("unchecked")
421 @Override
422 public void mouseClicked(MouseEvent event) {
423 if (event.getClickCount() == 2) {
424 // get the selected node
425 TreePath path = m_callsTree.getSelectionPath();
426 if (path == null) {
427 return;
428 }
429
430 Object node = path.getLastPathComponent();
431 if (node instanceof ReferenceTreeNode) {
432 ReferenceTreeNode<Entry,Entry> referenceNode = ((ReferenceTreeNode<Entry,Entry>)node);
433 if (referenceNode.getReference() != null) {
434 navigateTo(referenceNode.getReference());
435 } else {
436 navigateTo(referenceNode.getEntry());
437 }
438 }
439 }
440 }
441 });
442 m_tokens = new JList<Token>();
443 m_tokens.setCellRenderer(new TokenListCellRenderer(m_controller));
444 m_tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
445 m_tokens.setLayoutOrientation(JList.VERTICAL);
446 m_tokens.addMouseListener(new MouseAdapter() {
447 @Override
448 public void mouseClicked(MouseEvent event) {
449 if (event.getClickCount() == 2) {
450 Token selected = m_tokens.getSelectedValue();
451 if (selected != null) {
452 showToken(selected);
453 }
454 }
455 }
456 });
457 m_tokens.setPreferredSize(new Dimension(0, 200));
458 m_tokens.setMinimumSize(new Dimension(0, 200));
459 JSplitPane callPanel = new JSplitPane(
460 JSplitPane.VERTICAL_SPLIT,
461 true,
462 new JScrollPane(m_callsTree),
463 new JScrollPane(m_tokens)
464 );
465 callPanel.setResizeWeight(1); // let the top side take all the slack
466 callPanel.resetToPreferredSizes();
467
468 // layout controls
469 JPanel centerPanel = new JPanel();
470 centerPanel.setLayout(new BorderLayout());
471 centerPanel.add(m_infoPanel, BorderLayout.NORTH);
472 centerPanel.add(sourceScroller, BorderLayout.CENTER);
473 m_tabs = new JTabbedPane();
474 m_tabs.setPreferredSize(new Dimension(250, 0));
475 m_tabs.addTab("Inheritance", inheritancePanel);
476 m_tabs.addTab("Implementations", implementationsPanel);
477 m_tabs.addTab("Call Graph", callPanel);
478 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, m_tabs);
479 splitRight.setResizeWeight(1); // let the left side take all the slack
480 splitRight.resetToPreferredSizes();
481 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, m_classesPanel, splitRight);
482 splitCenter.setResizeWeight(0); // let the right side take all the slack
483 pane.add(splitCenter, BorderLayout.CENTER);
484
485 // init menus
486 JMenuBar menuBar = new JMenuBar();
487 m_frame.setJMenuBar(menuBar);
488 {
489 JMenu menu = new JMenu("File");
490 menuBar.add(menu);
491 {
492 JMenuItem item = new JMenuItem("Open Jar...");
493 menu.add(item);
494 item.addActionListener(new ActionListener() {
495 @Override
496 public void actionPerformed(ActionEvent event) {
497 if (m_jarFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
498 // load the jar in a separate thread
499 new Thread() {
500 @Override
501 public void run() {
502 try {
503 m_controller.openJar(new JarFile(m_jarFileChooser.getSelectedFile()));
504 } catch (IOException ex) {
505 throw new Error(ex);
506 }
507 }
508 }.start();
509 }
510 }
511 });
512 }
513 {
514 JMenuItem item = new JMenuItem("Close Jar");
515 menu.add(item);
516 item.addActionListener(new ActionListener() {
517 @Override
518 public void actionPerformed(ActionEvent event) {
519 m_controller.closeJar();
520 }
521 });
522 m_closeJarMenu = item;
523 }
524 menu.addSeparator();
525 {
526 JMenuItem item = new JMenuItem("Open Mappings...");
527 menu.add(item);
528 item.addActionListener(new ActionListener() {
529 @Override
530 public void actionPerformed(ActionEvent event) {
531 if (m_mappingsFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
532 try {
533 m_controller.openMappings(m_mappingsFileChooser.getSelectedFile());
534 } catch (IOException ex) {
535 throw new Error(ex);
536 } catch (MappingParseException ex) {
537 JOptionPane.showMessageDialog(m_frame, ex.getMessage());
538 }
539 }
540 }
541 });
542 m_openMappingsMenu = item;
543 }
544 {
545 JMenuItem item = new JMenuItem("Save Mappings");
546 menu.add(item);
547 item.addActionListener(new ActionListener() {
548 @Override
549 public void actionPerformed(ActionEvent event) {
550 try {
551 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
552 } catch (IOException ex) {
553 throw new Error(ex);
554 }
555 }
556 });
557 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK));
558 m_saveMappingsMenu = item;
559 }
560 {
561 JMenuItem item = new JMenuItem("Save Mappings As...");
562 menu.add(item);
563 item.addActionListener(new ActionListener() {
564 @Override
565 public void actionPerformed(ActionEvent event) {
566 if (m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
567 try {
568 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
569 m_saveMappingsMenu.setEnabled(true);
570 } catch (IOException ex) {
571 throw new Error(ex);
572 }
573 }
574 }
575 });
576 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
577 m_saveMappingsAsMenu = item;
578 }
579 {
580 JMenuItem item = new JMenuItem("Close Mappings");
581 menu.add(item);
582 item.addActionListener(new ActionListener() {
583 @Override
584 public void actionPerformed(ActionEvent event) {
585 m_controller.closeMappings();
586 }
587 });
588 m_closeMappingsMenu = item;
589 }
590 menu.addSeparator();
591 {
592 JMenuItem item = new JMenuItem("Export Source...");
593 menu.add(item);
594 item.addActionListener(new ActionListener() {
595 @Override
596 public void actionPerformed(ActionEvent event) {
597 if (m_exportSourceFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
598 m_controller.exportSource(m_exportSourceFileChooser.getSelectedFile());
599 }
600 }
601 });
602 m_exportSourceMenu = item;
603 }
604 {
605 JMenuItem item = new JMenuItem("Export Jar...");
606 menu.add(item);
607 item.addActionListener(new ActionListener() {
608 @Override
609 public void actionPerformed(ActionEvent event) {
610 if (m_exportJarFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
611 m_controller.exportJar(m_exportJarFileChooser.getSelectedFile());
612 }
613 }
614 });
615 m_exportJarMenu = item;
616 }
617 menu.addSeparator();
618 {
619 JMenuItem item = new JMenuItem("Exit");
620 menu.add(item);
621 item.addActionListener(new ActionListener() {
622 @Override
623 public void actionPerformed(ActionEvent event) {
624 close();
625 }
626 });
627 }
628 }
629 {
630 JMenu menu = new JMenu("Help");
631 menuBar.add(menu);
632 {
633 JMenuItem item = new JMenuItem("About");
634 menu.add(item);
635 item.addActionListener(new ActionListener() {
636 @Override
637 public void actionPerformed(ActionEvent event) {
638 AboutDialog.show(m_frame);
639 }
640 });
641 }
642 }
643
644 // init state
645 onCloseJar();
646
647 m_frame.addWindowListener(new WindowAdapter() {
648 @Override
649 public void windowClosing(WindowEvent event) {
650 close();
651 }
652 });
653
654 // show the frame
655 pane.doLayout();
656 m_frame.setSize(1024, 576);
657 m_frame.setMinimumSize(new Dimension(640, 480));
658 m_frame.setVisible(true);
659 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
660 }
661
662 public JFrame getFrame() {
663 return m_frame;
664 }
665
666 public GuiController getController() {
667 return m_controller;
668 }
669
670 public void onStartOpenJar() {
671 m_classesPanel.removeAll();
672 JPanel panel = new JPanel();
673 panel.setLayout(new FlowLayout());
674 panel.add(new JLabel("Loading..."));
675 m_classesPanel.add(panel);
676 redraw();
677 }
678
679 public void onFinishOpenJar(String jarName) {
680 // update gui
681 m_frame.setTitle(Constants.Name + " - " + jarName);
682 m_classesPanel.removeAll();
683 m_classesPanel.add(m_splitClasses);
684 setSource(null);
685
686 // update menu
687 m_closeJarMenu.setEnabled(true);
688 m_openMappingsMenu.setEnabled(true);
689 m_saveMappingsMenu.setEnabled(false);
690 m_saveMappingsAsMenu.setEnabled(true);
691 m_closeMappingsMenu.setEnabled(true);
692 m_exportSourceMenu.setEnabled(true);
693 m_exportJarMenu.setEnabled(true);
694
695 redraw();
696 }
697
698 public void onCloseJar() {
699 // update gui
700 m_frame.setTitle(Constants.Name);
701 setObfClasses(null);
702 setDeobfClasses(null);
703 setSource(null);
704 m_classesPanel.removeAll();
705
706 // update menu
707 m_closeJarMenu.setEnabled(false);
708 m_openMappingsMenu.setEnabled(false);
709 m_saveMappingsMenu.setEnabled(false);
710 m_saveMappingsAsMenu.setEnabled(false);
711 m_closeMappingsMenu.setEnabled(false);
712 m_exportSourceMenu.setEnabled(false);
713 m_exportJarMenu.setEnabled(false);
714
715 redraw();
716 }
717
718 public void setObfClasses(Collection<ClassEntry> obfClasses) {
719 m_obfClasses.setClasses(obfClasses);
720 }
721
722 public void setDeobfClasses(Collection<ClassEntry> deobfClasses) {
723 m_deobfClasses.setClasses(deobfClasses);
724 }
725
726 public void setMappingsFile(File file) {
727 m_mappingsFileChooser.setSelectedFile(file);
728 m_saveMappingsMenu.setEnabled(file != null);
729 }
730
731 public void setSource(String source) {
732 m_editor.getHighlighter().removeAllHighlights();
733 m_editor.setText(source);
734 }
735
736 public void showToken(final Token token) {
737 if (token == null) {
738 throw new IllegalArgumentException("Token cannot be null!");
739 }
740 CodeReader.navigateToToken(m_editor, token, m_selectionHighlightPainter);
741 redraw();
742 }
743
744 public void showTokens(Collection<Token> tokens) {
745 Vector<Token> sortedTokens = new Vector<Token>(tokens);
746 Collections.sort(sortedTokens);
747 if (sortedTokens.size() > 1) {
748 // sort the tokens and update the tokens panel
749 m_tokens.setListData(sortedTokens);
750 m_tokens.setSelectedIndex(0);
751 } else {
752 m_tokens.setListData(new Vector<Token>());
753 }
754
755 // show the first token
756 showToken(sortedTokens.get(0));
757 }
758
759 public void setHighlightedTokens(Iterable<Token> obfuscatedTokens, Iterable<Token> deobfuscatedTokens, Iterable<Token> otherTokens) {
760
761 // remove any old highlighters
762 m_editor.getHighlighter().removeAllHighlights();
763
764 // color things based on the index
765 if (obfuscatedTokens != null) {
766 setHighlightedTokens(obfuscatedTokens, m_obfuscatedHighlightPainter);
767 }
768 if (deobfuscatedTokens != null) {
769 setHighlightedTokens(deobfuscatedTokens, m_deobfuscatedHighlightPainter);
770 }
771 if (otherTokens != null) {
772 setHighlightedTokens(otherTokens, m_otherHighlightPainter);
773 }
774
775 redraw();
776 }
777
778 private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) {
779 for (Token token : tokens) {
780 try {
781 m_editor.getHighlighter().addHighlight(token.start, token.end, painter);
782 } catch (BadLocationException ex) {
783 throw new IllegalArgumentException(ex);
784 }
785 }
786 }
787
788 private void clearReference() {
789 m_infoPanel.removeAll();
790 JLabel label = new JLabel("No identifier selected");
791 GuiTricks.unboldLabel(label);
792 label.setHorizontalAlignment(JLabel.CENTER);
793 m_infoPanel.add(label);
794
795 redraw();
796 }
797
798 private void showReference(EntryReference<Entry,Entry> reference) {
799 if (reference == null) {
800 clearReference();
801 return;
802 }
803
804 m_reference = reference;
805
806 m_infoPanel.removeAll();
807 if (reference.entry instanceof ClassEntry) {
808 showClassEntry((ClassEntry)m_reference.entry);
809 } else if (m_reference.entry instanceof FieldEntry) {
810 showFieldEntry((FieldEntry)m_reference.entry);
811 } else if (m_reference.entry instanceof MethodEntry) {
812 showMethodEntry((MethodEntry)m_reference.entry);
813 } else if (m_reference.entry instanceof ConstructorEntry) {
814 showConstructorEntry((ConstructorEntry)m_reference.entry);
815 } else if (m_reference.entry instanceof ArgumentEntry) {
816 showArgumentEntry((ArgumentEntry)m_reference.entry);
817 } else {
818 throw new Error("Unknown entry type: " + m_reference.entry.getClass().getName());
819 }
820
821 redraw();
822 }
823
824 private void showClassEntry(ClassEntry entry) {
825 addNameValue(m_infoPanel, "Class", entry.getName());
826 }
827
828 private void showFieldEntry(FieldEntry entry) {
829 addNameValue(m_infoPanel, "Field", entry.getName());
830 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
831 addNameValue(m_infoPanel, "Type", entry.getType().toString());
832 }
833
834 private void showMethodEntry(MethodEntry entry) {
835 addNameValue(m_infoPanel, "Method", entry.getName());
836 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
837 addNameValue(m_infoPanel, "Signature", entry.getSignature().toString());
838 }
839
840 private void showConstructorEntry(ConstructorEntry entry) {
841 addNameValue(m_infoPanel, "Constructor", entry.getClassEntry().getName());
842 if (!entry.isStatic()) {
843 addNameValue(m_infoPanel, "Signature", entry.getSignature().toString());
844 }
845 }
846
847 private void showArgumentEntry(ArgumentEntry entry) {
848 addNameValue(m_infoPanel, "Argument", entry.getName());
849 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
850 addNameValue(m_infoPanel, "Method", entry.getBehaviorEntry().getName());
851 addNameValue(m_infoPanel, "Index", Integer.toString(entry.getIndex()));
852 }
853
854 private void addNameValue(JPanel container, String name, String value) {
855 JPanel panel = new JPanel();
856 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
857 container.add(panel);
858
859 JLabel label = new JLabel(name + ":", JLabel.RIGHT);
860 label.setPreferredSize(new Dimension(100, label.getPreferredSize().height));
861 panel.add(label);
862
863 panel.add(GuiTricks.unboldLabel(new JLabel(value, JLabel.LEFT)));
864 }
865
866 private void onCaretMove(int pos) {
867
868 Token token = m_controller.getToken(pos);
869 boolean isToken = token != null;
870
871 m_reference = m_controller.getDeobfReference(token);
872 boolean isClassEntry = isToken && m_reference.entry instanceof ClassEntry;
873 boolean isFieldEntry = isToken && m_reference.entry instanceof FieldEntry;
874 boolean isMethodEntry = isToken && m_reference.entry instanceof MethodEntry;
875 boolean isConstructorEntry = isToken && m_reference.entry instanceof ConstructorEntry;
876 boolean isInJar = isToken && m_controller.entryIsInJar(m_reference.entry);
877 boolean isRenameable = isToken && m_controller.referenceIsRenameable(m_reference);
878
879 if (isToken) {
880 showReference(m_reference);
881 } else {
882 clearReference();
883 }
884
885 m_renameMenu.setEnabled(isRenameable && isToken);
886 m_showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry);
887 m_showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry);
888 m_showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry);
889 m_openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry));
890 m_openPreviousMenu.setEnabled(m_controller.hasPreviousLocation());
891 m_toggleMappingMenu.setEnabled(isRenameable && isToken);
892
893 if (isToken && m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
894 m_toggleMappingMenu.setText("Reset to obfuscated");
895 } else {
896 m_toggleMappingMenu.setText("Mark as deobfuscated");
897 }
898 }
899
900 private void navigateTo(Entry entry) {
901 if (!m_controller.entryIsInJar(entry)) {
902 // entry is not in the jar. Ignore it
903 return;
904 }
905 if (m_reference != null) {
906 m_controller.savePreviousReference(m_reference);
907 }
908 m_controller.openDeclaration(entry);
909 }
910
911 private void navigateTo(EntryReference<Entry,Entry> reference) {
912 if (!m_controller.entryIsInJar(reference.getLocationClassEntry())) {
913 // reference is not in the jar. Ignore it
914 return;
915 }
916 if (m_reference != null) {
917 m_controller.savePreviousReference(m_reference);
918 }
919 m_controller.openReference(reference);
920 }
921
922 private void startRename() {
923
924 // init the text box
925 final JTextField text = new JTextField();
926 text.setText(m_reference.getNamableName());
927 text.setPreferredSize(new Dimension(360, text.getPreferredSize().height));
928 text.addKeyListener(new KeyAdapter() {
929 @Override
930 public void keyPressed(KeyEvent event) {
931 switch (event.getKeyCode()) {
932 case KeyEvent.VK_ENTER:
933 finishRename(text, true);
934 break;
935
936 case KeyEvent.VK_ESCAPE:
937 finishRename(text, false);
938 break;
939 }
940 }
941 });
942
943 // find the label with the name and replace it with the text box
944 JPanel panel = (JPanel)m_infoPanel.getComponent(0);
945 panel.remove(panel.getComponentCount() - 1);
946 panel.add(text);
947 text.grabFocus();
948 text.selectAll();
949
950 redraw();
951 }
952
953 private void finishRename(JTextField text, boolean saveName) {
954 String newName = text.getText();
955 if (saveName && newName != null && newName.length() > 0) {
956 try {
957 m_controller.rename(m_reference, newName);
958 } catch (IllegalNameException ex) {
959 text.setBorder(BorderFactory.createLineBorder(Color.red, 1));
960 text.setToolTipText(ex.getReason());
961 GuiTricks.showToolTipNow(text);
962 }
963 return;
964 }
965
966 // abort the rename
967 JPanel panel = (JPanel)m_infoPanel.getComponent(0);
968 panel.remove(panel.getComponentCount() - 1);
969 panel.add(GuiTricks.unboldLabel(new JLabel(m_reference.getNamableName(), JLabel.LEFT)));
970
971 m_editor.grabFocus();
972
973 redraw();
974 }
975
976 private void showInheritance() {
977
978 if (m_reference == null) {
979 return;
980 }
981
982 m_inheritanceTree.setModel(null);
983
984 if (m_reference.entry instanceof ClassEntry) {
985 // get the class inheritance
986 ClassInheritanceTreeNode classNode = m_controller.getClassInheritance((ClassEntry)m_reference.entry);
987
988 // show the tree at the root
989 TreePath path = getPathToRoot(classNode);
990 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
991 m_inheritanceTree.expandPath(path);
992 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
993 } else if (m_reference.entry instanceof MethodEntry) {
994 // get the method inheritance
995 MethodInheritanceTreeNode classNode = m_controller.getMethodInheritance((MethodEntry)m_reference.entry);
996
997 // show the tree at the root
998 TreePath path = getPathToRoot(classNode);
999 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1000 m_inheritanceTree.expandPath(path);
1001 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
1002 }
1003
1004 m_tabs.setSelectedIndex(0);
1005 redraw();
1006 }
1007
1008 private void showImplementations() {
1009
1010 if (m_reference == null) {
1011 return;
1012 }
1013
1014 m_implementationsTree.setModel(null);
1015
1016 if (m_reference.entry instanceof ClassEntry) {
1017 // get the class implementations
1018 ClassImplementationsTreeNode node = m_controller.getClassImplementations((ClassEntry)m_reference.entry);
1019 if (node != null) {
1020 // show the tree at the root
1021 TreePath path = getPathToRoot(node);
1022 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1023 m_implementationsTree.expandPath(path);
1024 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1025 }
1026 } else if (m_reference.entry instanceof MethodEntry) {
1027 // get the method implementations
1028 MethodImplementationsTreeNode node = m_controller.getMethodImplementations((MethodEntry)m_reference.entry);
1029 if (node != null) {
1030 // show the tree at the root
1031 TreePath path = getPathToRoot(node);
1032 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1033 m_implementationsTree.expandPath(path);
1034 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1035 }
1036 }
1037
1038 m_tabs.setSelectedIndex(1);
1039 redraw();
1040 }
1041
1042 private void showCalls() {
1043
1044 if (m_reference == null) {
1045 return;
1046 }
1047
1048 if (m_reference.entry instanceof ClassEntry) {
1049 // look for calls to the default constructor
1050 // TODO: get a list of all the constructors and find calls to all of them
1051 BehaviorReferenceTreeNode node = m_controller.getMethodReferences(new ConstructorEntry((ClassEntry)m_reference.entry, new Signature("()V")));
1052 m_callsTree.setModel(new DefaultTreeModel(node));
1053 } else if (m_reference.entry instanceof FieldEntry) {
1054 FieldReferenceTreeNode node = m_controller.getFieldReferences((FieldEntry)m_reference.entry);
1055 m_callsTree.setModel(new DefaultTreeModel(node));
1056 } else if (m_reference.entry instanceof MethodEntry) {
1057 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((MethodEntry)m_reference.entry);
1058 m_callsTree.setModel(new DefaultTreeModel(node));
1059 } else if (m_reference.entry instanceof ConstructorEntry) {
1060 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((ConstructorEntry)m_reference.entry);
1061 m_callsTree.setModel(new DefaultTreeModel(node));
1062 }
1063
1064 m_tabs.setSelectedIndex(2);
1065 redraw();
1066 }
1067
1068 private void toggleMapping() {
1069 if (m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
1070 m_controller.removeMapping(m_reference);
1071 } else {
1072 m_controller.markAsDeobfuscated(m_reference);
1073 }
1074 }
1075
1076 private TreePath getPathToRoot(TreeNode node) {
1077 List<TreeNode> nodes = Lists.newArrayList();
1078 TreeNode n = node;
1079 do {
1080 nodes.add(n);
1081 n = n.getParent();
1082 } while (n != null);
1083 Collections.reverse(nodes);
1084 return new TreePath(nodes.toArray());
1085 }
1086
1087 private void close() {
1088 if (!m_controller.isDirty()) {
1089 // everything is saved, we can exit safely
1090 m_frame.dispose();
1091 } else {
1092 // ask to save before closing
1093 String[] options = { "Save and exit", "Discard changes", "Cancel" };
1094 int response = JOptionPane.showOptionDialog(m_frame, "Your mappings have not been saved yet. Do you want to save?", "Save your changes?", JOptionPane.YES_NO_CANCEL_OPTION,
1095 JOptionPane.QUESTION_MESSAGE, null, options, options[2]);
1096 switch (response) {
1097 case JOptionPane.YES_OPTION: // save and exit
1098 if (m_mappingsFileChooser.getSelectedFile() != null || m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
1099 try {
1100 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
1101 m_frame.dispose();
1102 } catch (IOException ex) {
1103 throw new Error(ex);
1104 }
1105 }
1106 break;
1107
1108 case JOptionPane.NO_OPTION:
1109 // don't save, exit
1110 m_frame.dispose();
1111 break;
1112
1113 // cancel means do nothing
1114 }
1115 }
1116 }
1117
1118 private void redraw() {
1119 m_frame.validate();
1120 m_frame.repaint();
1121 }
1122}
diff --git a/src/cuchaz/enigma/gui/GuiController.java b/src/cuchaz/enigma/gui/GuiController.java
deleted file mode 100644
index 66906227..00000000
--- a/src/cuchaz/enigma/gui/GuiController.java
+++ /dev/null
@@ -1,358 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.io.File;
14import java.io.FileReader;
15import java.io.FileWriter;
16import java.io.IOException;
17import java.util.Collection;
18import java.util.Deque;
19import java.util.List;
20import java.util.jar.JarFile;
21
22import com.google.common.collect.Lists;
23import com.google.common.collect.Queues;
24import com.strobel.decompiler.languages.java.ast.CompilationUnit;
25
26import cuchaz.enigma.Deobfuscator;
27import cuchaz.enigma.Deobfuscator.ProgressListener;
28import cuchaz.enigma.analysis.BehaviorReferenceTreeNode;
29import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
30import cuchaz.enigma.analysis.ClassInheritanceTreeNode;
31import cuchaz.enigma.analysis.EntryReference;
32import cuchaz.enigma.analysis.FieldReferenceTreeNode;
33import cuchaz.enigma.analysis.MethodImplementationsTreeNode;
34import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
35import cuchaz.enigma.analysis.SourceIndex;
36import cuchaz.enigma.analysis.Token;
37import cuchaz.enigma.gui.ProgressDialog.ProgressRunnable;
38import cuchaz.enigma.mapping.BehaviorEntry;
39import cuchaz.enigma.mapping.ClassEntry;
40import cuchaz.enigma.mapping.Entry;
41import cuchaz.enigma.mapping.FieldEntry;
42import cuchaz.enigma.mapping.MappingParseException;
43import cuchaz.enigma.mapping.MappingsReader;
44import cuchaz.enigma.mapping.MappingsWriter;
45import cuchaz.enigma.mapping.MethodEntry;
46import cuchaz.enigma.mapping.TranslationDirection;
47
48public class GuiController {
49
50 private Deobfuscator m_deobfuscator;
51 private Gui m_gui;
52 private SourceIndex m_index;
53 private ClassEntry m_currentObfClass;
54 private boolean m_isDirty;
55 private Deque<EntryReference<Entry,Entry>> m_referenceStack;
56
57 public GuiController(Gui gui) {
58 m_gui = gui;
59 m_deobfuscator = null;
60 m_index = null;
61 m_currentObfClass = null;
62 m_isDirty = false;
63 m_referenceStack = Queues.newArrayDeque();
64 }
65
66 public boolean isDirty() {
67 return m_isDirty;
68 }
69
70 public void openJar(final JarFile jar) throws IOException {
71 m_gui.onStartOpenJar();
72 m_deobfuscator = new Deobfuscator(jar);
73 m_gui.onFinishOpenJar(m_deobfuscator.getJarName());
74 refreshClasses();
75 }
76
77 public void closeJar() {
78 m_deobfuscator = null;
79 m_gui.onCloseJar();
80 }
81
82 public void openMappings(File file) throws IOException, MappingParseException {
83 FileReader in = new FileReader(file);
84 m_deobfuscator.setMappings(new MappingsReader().read(in));
85 in.close();
86 m_isDirty = false;
87 m_gui.setMappingsFile(file);
88 refreshClasses();
89 refreshCurrentClass();
90 }
91
92 public void saveMappings(File file) throws IOException {
93 FileWriter out = new FileWriter(file);
94 new MappingsWriter().write(out, m_deobfuscator.getMappings());
95 out.close();
96 m_isDirty = false;
97 }
98
99 public void closeMappings() {
100 m_deobfuscator.setMappings(null);
101 m_gui.setMappingsFile(null);
102 refreshClasses();
103 refreshCurrentClass();
104 }
105
106 public void exportSource(final File dirOut) {
107 ProgressDialog.runInThread(m_gui.getFrame(), new ProgressRunnable() {
108 @Override
109 public void run(ProgressListener progress) throws Exception {
110 m_deobfuscator.writeSources(dirOut, progress);
111 }
112 });
113 }
114
115 public void exportJar(final File fileOut) {
116 ProgressDialog.runInThread(m_gui.getFrame(), new ProgressRunnable() {
117 @Override
118 public void run(ProgressListener progress) {
119 m_deobfuscator.writeJar(fileOut, progress);
120 }
121 });
122 }
123
124 public Token getToken(int pos) {
125 if (m_index == null) {
126 return null;
127 }
128 return m_index.getReferenceToken(pos);
129 }
130
131 public EntryReference<Entry,Entry> getDeobfReference(Token token) {
132 if (m_index == null) {
133 return null;
134 }
135 return m_index.getDeobfReference(token);
136 }
137
138 public ReadableToken getReadableToken(Token token) {
139 if (m_index == null) {
140 return null;
141 }
142 return new ReadableToken(
143 m_index.getLineNumber(token.start),
144 m_index.getColumnNumber(token.start),
145 m_index.getColumnNumber(token.end)
146 );
147 }
148
149 public boolean entryHasDeobfuscatedName(Entry deobfEntry) {
150 return m_deobfuscator.hasDeobfuscatedName(m_deobfuscator.obfuscateEntry(deobfEntry));
151 }
152
153 public boolean entryIsInJar(Entry deobfEntry) {
154 return m_deobfuscator.isObfuscatedIdentifier(m_deobfuscator.obfuscateEntry(deobfEntry));
155 }
156
157 public boolean referenceIsRenameable(EntryReference<Entry,Entry> deobfReference) {
158 return m_deobfuscator.isRenameable(m_deobfuscator.obfuscateReference(deobfReference));
159 }
160
161 public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) {
162 ClassEntry obfClassEntry = m_deobfuscator.obfuscateEntry(deobfClassEntry);
163 ClassInheritanceTreeNode rootNode = m_deobfuscator.getJarIndex().getClassInheritance(
164 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
165 obfClassEntry
166 );
167 return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry);
168 }
169
170 public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) {
171 ClassEntry obfClassEntry = m_deobfuscator.obfuscateEntry(deobfClassEntry);
172 return m_deobfuscator.getJarIndex().getClassImplementations(
173 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
174 obfClassEntry
175 );
176 }
177
178 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) {
179 MethodEntry obfMethodEntry = m_deobfuscator.obfuscateEntry(deobfMethodEntry);
180 MethodInheritanceTreeNode rootNode = m_deobfuscator.getJarIndex().getMethodInheritance(
181 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
182 obfMethodEntry
183 );
184 return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry);
185 }
186
187 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) {
188 MethodEntry obfMethodEntry = m_deobfuscator.obfuscateEntry(deobfMethodEntry);
189 List<MethodImplementationsTreeNode> rootNodes = m_deobfuscator.getJarIndex().getMethodImplementations(
190 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
191 obfMethodEntry
192 );
193 if (rootNodes.isEmpty()) {
194 return null;
195 }
196 if (rootNodes.size() > 1) {
197 System.err.println("WARNING: Method " + deobfMethodEntry + " implements multiple interfaces. Only showing first one.");
198 }
199 return MethodImplementationsTreeNode.findNode(rootNodes.get(0), obfMethodEntry);
200 }
201
202 public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) {
203 FieldEntry obfFieldEntry = m_deobfuscator.obfuscateEntry(deobfFieldEntry);
204 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(
205 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
206 obfFieldEntry
207 );
208 rootNode.load(m_deobfuscator.getJarIndex(), true);
209 return rootNode;
210 }
211
212 public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) {
213 BehaviorEntry obfBehaviorEntry = m_deobfuscator.obfuscateEntry(deobfBehaviorEntry);
214 BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode(
215 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
216 obfBehaviorEntry
217 );
218 rootNode.load(m_deobfuscator.getJarIndex(), true);
219 return rootNode;
220 }
221
222 public void rename(EntryReference<Entry,Entry> deobfReference, String newName) {
223 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
224 m_deobfuscator.rename(obfReference.getNameableEntry(), newName);
225 m_isDirty = true;
226 refreshClasses();
227 refreshCurrentClass(obfReference);
228 }
229
230 public void removeMapping(EntryReference<Entry,Entry> deobfReference) {
231 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
232 m_deobfuscator.removeMapping(obfReference.getNameableEntry());
233 m_isDirty = true;
234 refreshClasses();
235 refreshCurrentClass(obfReference);
236 }
237
238 public void markAsDeobfuscated(EntryReference<Entry,Entry> deobfReference) {
239 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
240 m_deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry());
241 m_isDirty = true;
242 refreshClasses();
243 refreshCurrentClass(obfReference);
244 }
245
246 public void openDeclaration(Entry deobfEntry) {
247 if (deobfEntry == null) {
248 throw new IllegalArgumentException("Entry cannot be null!");
249 }
250 openReference(new EntryReference<Entry,Entry>(deobfEntry, deobfEntry.getName()));
251 }
252
253 public void openReference(EntryReference<Entry,Entry> deobfReference) {
254 if (deobfReference == null) {
255 throw new IllegalArgumentException("Reference cannot be null!");
256 }
257
258 // get the reference target class
259 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
260 ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOutermostClassEntry();
261 if (!m_deobfuscator.isObfuscatedIdentifier(obfClassEntry)) {
262 throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!");
263 }
264 if (m_currentObfClass == null || !m_currentObfClass.equals(obfClassEntry)) {
265 // deobfuscate the class, then navigate to the reference
266 m_currentObfClass = obfClassEntry;
267 deobfuscate(m_currentObfClass, obfReference);
268 } else {
269 showReference(obfReference);
270 }
271 }
272
273 private void showReference(EntryReference<Entry,Entry> obfReference) {
274 EntryReference<Entry,Entry> deobfReference = m_deobfuscator.deobfuscateReference(obfReference);
275 Collection<Token> tokens = m_index.getReferenceTokens(deobfReference);
276 if (tokens.isEmpty()) {
277 // DEBUG
278 System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, m_currentObfClass));
279 } else {
280 m_gui.showTokens(tokens);
281 }
282 }
283
284 public void savePreviousReference(EntryReference<Entry,Entry> deobfReference) {
285 m_referenceStack.push(m_deobfuscator.obfuscateReference(deobfReference));
286 }
287
288 public void openPreviousReference() {
289 if (hasPreviousLocation()) {
290 openReference(m_deobfuscator.deobfuscateReference(m_referenceStack.pop()));
291 }
292 }
293
294 public boolean hasPreviousLocation() {
295 return !m_referenceStack.isEmpty();
296 }
297
298 private void refreshClasses() {
299 List<ClassEntry> obfClasses = Lists.newArrayList();
300 List<ClassEntry> deobfClasses = Lists.newArrayList();
301 m_deobfuscator.getSeparatedClasses(obfClasses, deobfClasses);
302 m_gui.setObfClasses(obfClasses);
303 m_gui.setDeobfClasses(deobfClasses);
304 }
305
306 private void refreshCurrentClass() {
307 refreshCurrentClass(null);
308 }
309
310 private void refreshCurrentClass(EntryReference<Entry,Entry> obfReference) {
311 if (m_currentObfClass != null) {
312 deobfuscate(m_currentObfClass, obfReference);
313 }
314 }
315
316 private void deobfuscate(final ClassEntry classEntry, final EntryReference<Entry,Entry> obfReference) {
317
318 m_gui.setSource("(deobfuscating...)");
319
320 // run the deobfuscator in a separate thread so we don't block the GUI event queue
321 new Thread() {
322 @Override
323 public void run() {
324 // decompile,deobfuscate the bytecode
325 CompilationUnit sourceTree = m_deobfuscator.getSourceTree(classEntry.getClassName());
326 if (sourceTree == null) {
327 // decompilation of this class is not supported
328 m_gui.setSource("Unable to find class: " + classEntry);
329 return;
330 }
331 String source = m_deobfuscator.getSource(sourceTree);
332 m_index = m_deobfuscator.getSourceIndex(sourceTree, source);
333 m_gui.setSource(m_index.getSource());
334 if (obfReference != null) {
335 showReference(obfReference);
336 }
337
338 // set the highlighted tokens
339 List<Token> obfuscatedTokens = Lists.newArrayList();
340 List<Token> deobfuscatedTokens = Lists.newArrayList();
341 List<Token> otherTokens = Lists.newArrayList();
342 for (Token token : m_index.referenceTokens()) {
343 EntryReference<Entry,Entry> reference = m_index.getDeobfReference(token);
344 if (referenceIsRenameable(reference)) {
345 if (entryHasDeobfuscatedName(reference.getNameableEntry())) {
346 deobfuscatedTokens.add(token);
347 } else {
348 obfuscatedTokens.add(token);
349 }
350 } else {
351 otherTokens.add(token);
352 }
353 }
354 m_gui.setHighlightedTokens(obfuscatedTokens, deobfuscatedTokens, otherTokens);
355 }
356 }.start();
357 }
358}
diff --git a/src/cuchaz/enigma/gui/GuiTricks.java b/src/cuchaz/enigma/gui/GuiTricks.java
deleted file mode 100644
index 5dc3ffb3..00000000
--- a/src/cuchaz/enigma/gui/GuiTricks.java
+++ /dev/null
@@ -1,56 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Font;
14import java.awt.event.ActionListener;
15import java.awt.event.MouseEvent;
16import java.util.Arrays;
17
18import javax.swing.JButton;
19import javax.swing.JComponent;
20import javax.swing.JLabel;
21import javax.swing.ToolTipManager;
22
23public class GuiTricks {
24
25 public static JLabel unboldLabel(JLabel label) {
26 Font font = label.getFont();
27 label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD));
28 return label;
29 }
30
31 public static void showToolTipNow(JComponent component) {
32 // HACKHACK: trick the tooltip manager into showing the tooltip right now
33 ToolTipManager manager = ToolTipManager.sharedInstance();
34 int oldDelay = manager.getInitialDelay();
35 manager.setInitialDelay(0);
36 manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false));
37 manager.setInitialDelay(oldDelay);
38 }
39
40 public static void deactivateButton(JButton button) {
41 button.setEnabled(false);
42 button.setText("");
43 for (ActionListener listener : Arrays.asList(button.getActionListeners())) {
44 button.removeActionListener(listener);
45 }
46 }
47
48 public static void activateButton(JButton button, String text, ActionListener newListener) {
49 button.setText(text);
50 button.setEnabled(true);
51 for (ActionListener listener : Arrays.asList(button.getActionListeners())) {
52 button.removeActionListener(listener);
53 }
54 button.addActionListener(newListener);
55 }
56}
diff --git a/src/cuchaz/enigma/gui/MemberMatchingGui.java b/src/cuchaz/enigma/gui/MemberMatchingGui.java
deleted file mode 100644
index 150eaadb..00000000
--- a/src/cuchaz/enigma/gui/MemberMatchingGui.java
+++ /dev/null
@@ -1,499 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.Dimension;
16import java.awt.FlowLayout;
17import java.awt.event.ActionEvent;
18import java.awt.event.ActionListener;
19import java.awt.event.KeyAdapter;
20import java.awt.event.KeyEvent;
21import java.util.Collection;
22import java.util.List;
23import java.util.Map;
24
25import javax.swing.BoxLayout;
26import javax.swing.ButtonGroup;
27import javax.swing.JButton;
28import javax.swing.JFrame;
29import javax.swing.JLabel;
30import javax.swing.JPanel;
31import javax.swing.JRadioButton;
32import javax.swing.JScrollPane;
33import javax.swing.JSplitPane;
34import javax.swing.WindowConstants;
35import javax.swing.text.Highlighter.HighlightPainter;
36
37import com.google.common.collect.Lists;
38import com.google.common.collect.Maps;
39
40import cuchaz.enigma.Constants;
41import cuchaz.enigma.Deobfuscator;
42import cuchaz.enigma.analysis.EntryReference;
43import cuchaz.enigma.analysis.SourceIndex;
44import cuchaz.enigma.analysis.Token;
45import cuchaz.enigma.convert.ClassMatches;
46import cuchaz.enigma.convert.MemberMatches;
47import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
48import cuchaz.enigma.mapping.ClassEntry;
49import cuchaz.enigma.mapping.Entry;
50import de.sciss.syntaxpane.DefaultSyntaxKit;
51
52
53public class MemberMatchingGui<T extends Entry> {
54
55 private static enum SourceType {
56 Matched {
57
58 @Override
59 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
60 return matches.getSourceClassesWithoutUnmatchedEntries();
61 }
62 },
63 Unmatched {
64
65 @Override
66 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
67 return matches.getSourceClassesWithUnmatchedEntries();
68 }
69 };
70
71 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
72 JRadioButton button = new JRadioButton(name(), this == getDefault());
73 button.setActionCommand(name());
74 button.addActionListener(listener);
75 group.add(button);
76 return button;
77 }
78
79 public abstract <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches);
80
81 public static SourceType getDefault() {
82 return values()[0];
83 }
84 }
85
86 public static interface SaveListener<T extends Entry> {
87 public void save(MemberMatches<T> matches);
88 }
89
90 // controls
91 private JFrame m_frame;
92 private Map<SourceType,JRadioButton> m_sourceTypeButtons;
93 private ClassSelector m_sourceClasses;
94 private CodeReader m_sourceReader;
95 private CodeReader m_destReader;
96 private JButton m_matchButton;
97 private JButton m_unmatchableButton;
98 private JLabel m_sourceLabel;
99 private JLabel m_destLabel;
100 private HighlightPainter m_unmatchedHighlightPainter;
101 private HighlightPainter m_matchedHighlightPainter;
102
103 private ClassMatches m_classMatches;
104 private MemberMatches<T> m_memberMatches;
105 private Deobfuscator m_sourceDeobfuscator;
106 private Deobfuscator m_destDeobfuscator;
107 private SaveListener<T> m_saveListener;
108 private SourceType m_sourceType;
109 private ClassEntry m_obfSourceClass;
110 private ClassEntry m_obfDestClass;
111 private T m_obfSourceEntry;
112 private T m_obfDestEntry;
113
114 public MemberMatchingGui(ClassMatches classMatches, MemberMatches<T> fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
115
116 m_classMatches = classMatches;
117 m_memberMatches = fieldMatches;
118 m_sourceDeobfuscator = sourceDeobfuscator;
119 m_destDeobfuscator = destDeobfuscator;
120
121 // init frame
122 m_frame = new JFrame(Constants.Name + " - Member Matcher");
123 final Container pane = m_frame.getContentPane();
124 pane.setLayout(new BorderLayout());
125
126 // init classes side
127 JPanel classesPanel = new JPanel();
128 classesPanel.setLayout(new BoxLayout(classesPanel, BoxLayout.PAGE_AXIS));
129 classesPanel.setPreferredSize(new Dimension(200, 0));
130 pane.add(classesPanel, BorderLayout.WEST);
131 classesPanel.add(new JLabel("Classes"));
132
133 // init source type radios
134 JPanel sourceTypePanel = new JPanel();
135 classesPanel.add(sourceTypePanel);
136 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
137 ActionListener sourceTypeListener = new ActionListener() {
138 @Override
139 public void actionPerformed(ActionEvent event) {
140 setSourceType(SourceType.valueOf(event.getActionCommand()));
141 }
142 };
143 ButtonGroup sourceTypeButtons = new ButtonGroup();
144 m_sourceTypeButtons = Maps.newHashMap();
145 for (SourceType sourceType : SourceType.values()) {
146 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
147 m_sourceTypeButtons.put(sourceType, button);
148 sourceTypePanel.add(button);
149 }
150
151 m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
152 m_sourceClasses.setListener(new ClassSelectionListener() {
153 @Override
154 public void onSelectClass(ClassEntry classEntry) {
155 setSourceClass(classEntry);
156 }
157 });
158 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
159 classesPanel.add(sourceScroller);
160
161 // init readers
162 DefaultSyntaxKit.initKit();
163 m_sourceReader = new CodeReader();
164 m_sourceReader.setSelectionListener(new CodeReader.SelectionListener() {
165 @Override
166 public void onSelect(EntryReference<Entry,Entry> reference) {
167 if (reference != null) {
168 onSelectSource(reference.entry);
169 } else {
170 onSelectSource(null);
171 }
172 }
173 });
174 m_destReader = new CodeReader();
175 m_destReader.setSelectionListener(new CodeReader.SelectionListener() {
176 @Override
177 public void onSelect(EntryReference<Entry,Entry> reference) {
178 if (reference != null) {
179 onSelectDest(reference.entry);
180 } else {
181 onSelectDest(null);
182 }
183 }
184 });
185
186 // add key bindings
187 KeyAdapter keyListener = new KeyAdapter() {
188 @Override
189 public void keyPressed(KeyEvent event) {
190 switch (event.getKeyCode()) {
191 case KeyEvent.VK_M:
192 m_matchButton.doClick();
193 break;
194 }
195 }
196 };
197 m_sourceReader.addKeyListener(keyListener);
198 m_destReader.addKeyListener(keyListener);
199
200 // init all the splits
201 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_sourceReader), new JScrollPane(m_destReader));
202 splitRight.setResizeWeight(0.5); // resize 50:50
203 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, splitRight);
204 splitLeft.setResizeWeight(0); // let the right side take all the slack
205 pane.add(splitLeft, BorderLayout.CENTER);
206 splitLeft.resetToPreferredSizes();
207
208 // init bottom panel
209 JPanel bottomPanel = new JPanel();
210 bottomPanel.setLayout(new FlowLayout());
211 pane.add(bottomPanel, BorderLayout.SOUTH);
212
213 m_matchButton = new JButton();
214 m_unmatchableButton = new JButton();
215
216 m_sourceLabel = new JLabel();
217 bottomPanel.add(m_sourceLabel);
218 bottomPanel.add(m_matchButton);
219 bottomPanel.add(m_unmatchableButton);
220 m_destLabel = new JLabel();
221 bottomPanel.add(m_destLabel);
222
223 // show the frame
224 pane.doLayout();
225 m_frame.setSize(1024, 576);
226 m_frame.setMinimumSize(new Dimension(640, 480));
227 m_frame.setVisible(true);
228 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
229
230 m_unmatchedHighlightPainter = new ObfuscatedHighlightPainter();
231 m_matchedHighlightPainter = new DeobfuscatedHighlightPainter();
232
233 // init state
234 m_saveListener = null;
235 m_obfSourceClass = null;
236 m_obfDestClass = null;
237 m_obfSourceEntry = null;
238 m_obfDestEntry = null;
239 setSourceType(SourceType.getDefault());
240 updateButtons();
241 }
242
243 protected void setSourceType(SourceType val) {
244 m_sourceType = val;
245 updateSourceClasses();
246 }
247
248 public void setSaveListener(SaveListener<T> val) {
249 m_saveListener = val;
250 }
251
252 private void updateSourceClasses() {
253
254 String selectedPackage = m_sourceClasses.getSelectedPackage();
255
256 List<ClassEntry> deobfClassEntries = Lists.newArrayList();
257 for (ClassEntry entry : m_sourceType.getObfSourceClasses(m_memberMatches)) {
258 deobfClassEntries.add(m_sourceDeobfuscator.deobfuscateEntry(entry));
259 }
260 m_sourceClasses.setClasses(deobfClassEntries);
261
262 if (selectedPackage != null) {
263 m_sourceClasses.expandPackage(selectedPackage);
264 }
265
266 for (SourceType sourceType : SourceType.values()) {
267 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
268 sourceType.name(), sourceType.getObfSourceClasses(m_memberMatches).size()
269 ));
270 }
271 }
272
273 protected void setSourceClass(ClassEntry sourceClass) {
274
275 m_obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
276 m_obfDestClass = m_classMatches.getUniqueMatches().get(m_obfSourceClass);
277 if (m_obfDestClass == null) {
278 throw new Error("No matching dest class for source class: " + m_obfSourceClass);
279 }
280
281 m_sourceReader.decompileClass(m_obfSourceClass, m_sourceDeobfuscator, false, new Runnable() {
282 @Override
283 public void run() {
284 updateSourceHighlights();
285 }
286 });
287 m_destReader.decompileClass(m_obfDestClass, m_destDeobfuscator, false, new Runnable() {
288 @Override
289 public void run() {
290 updateDestHighlights();
291 }
292 });
293 }
294
295 protected void updateSourceHighlights() {
296 highlightEntries(m_sourceReader, m_sourceDeobfuscator, m_memberMatches.matches().keySet(), m_memberMatches.getUnmatchedSourceEntries());
297 }
298
299 protected void updateDestHighlights() {
300 highlightEntries(m_destReader, m_destDeobfuscator, m_memberMatches.matches().values(), m_memberMatches.getUnmatchedDestEntries());
301 }
302
303 private void highlightEntries(CodeReader reader, Deobfuscator deobfuscator, Collection<T> obfMatchedEntries, Collection<T> obfUnmatchedEntries) {
304 reader.clearHighlights();
305 SourceIndex index = reader.getSourceIndex();
306
307 // matched fields
308 for (T obfT : obfMatchedEntries) {
309 T deobfT = deobfuscator.deobfuscateEntry(obfT);
310 Token token = index.getDeclarationToken(deobfT);
311 if (token != null) {
312 reader.setHighlightedToken(token, m_matchedHighlightPainter);
313 }
314 }
315
316 // unmatched fields
317 for (T obfT : obfUnmatchedEntries) {
318 T deobfT = deobfuscator.deobfuscateEntry(obfT);
319 Token token = index.getDeclarationToken(deobfT);
320 if (token != null) {
321 reader.setHighlightedToken(token, m_unmatchedHighlightPainter);
322 }
323 }
324 }
325
326 private boolean isSelectionMatched() {
327 return m_obfSourceEntry != null && m_obfDestEntry != null
328 && m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry);
329 }
330
331 protected void onSelectSource(Entry source) {
332
333 // start with no selection
334 if (isSelectionMatched()) {
335 setDest(null);
336 }
337 setSource(null);
338
339 // then look for a valid source selection
340 if (source != null) {
341
342 // this looks really scary, but it's actually ok
343 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
344 // and MemberMatches.hasSource() will only pass entries that actually match T
345 @SuppressWarnings("unchecked")
346 T sourceEntry = (T)source;
347
348 T obfSourceEntry = m_sourceDeobfuscator.obfuscateEntry(sourceEntry);
349 if (m_memberMatches.hasSource(obfSourceEntry)) {
350 setSource(obfSourceEntry);
351
352 // look for a matched dest too
353 T obfDestEntry = m_memberMatches.matches().get(obfSourceEntry);
354 if (obfDestEntry != null) {
355 setDest(obfDestEntry);
356 }
357 }
358 }
359
360 updateButtons();
361 }
362
363 protected void onSelectDest(Entry dest) {
364
365 // start with no selection
366 if (isSelectionMatched()) {
367 setSource(null);
368 }
369 setDest(null);
370
371 // then look for a valid dest selection
372 if (dest != null) {
373
374 // this looks really scary, but it's actually ok
375 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
376 // and MemberMatches.hasSource() will only pass entries that actually match T
377 @SuppressWarnings("unchecked")
378 T destEntry = (T)dest;
379
380 T obfDestEntry = m_destDeobfuscator.obfuscateEntry(destEntry);
381 if (m_memberMatches.hasDest(obfDestEntry)) {
382 setDest(obfDestEntry);
383
384 // look for a matched source too
385 T obfSourceEntry = m_memberMatches.matches().inverse().get(obfDestEntry);
386 if (obfSourceEntry != null) {
387 setSource(obfSourceEntry);
388 }
389 }
390 }
391
392 updateButtons();
393 }
394
395 private void setSource(T obfEntry) {
396 if (obfEntry == null) {
397 m_obfSourceEntry = obfEntry;
398 m_sourceLabel.setText("");
399 } else {
400 m_obfSourceEntry = obfEntry;
401 m_sourceLabel.setText(getEntryLabel(obfEntry, m_sourceDeobfuscator));
402 }
403 }
404
405 private void setDest(T obfEntry) {
406 if (obfEntry == null) {
407 m_obfDestEntry = obfEntry;
408 m_destLabel.setText("");
409 } else {
410 m_obfDestEntry = obfEntry;
411 m_destLabel.setText(getEntryLabel(obfEntry, m_destDeobfuscator));
412 }
413 }
414
415 private String getEntryLabel(T obfEntry, Deobfuscator deobfuscator) {
416 // show obfuscated and deobfuscated names, but no types/signatures
417 T deobfEntry = deobfuscator.deobfuscateEntry(obfEntry);
418 return String.format("%s (%s)", deobfEntry.getName(), obfEntry.getName());
419 }
420
421 private void updateButtons() {
422
423 GuiTricks.deactivateButton(m_matchButton);
424 GuiTricks.deactivateButton(m_unmatchableButton);
425
426 if (m_obfSourceEntry != null && m_obfDestEntry != null) {
427 if (m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry)) {
428 GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() {
429 @Override
430 public void actionPerformed(ActionEvent event) {
431 unmatch();
432 }
433 });
434 } else if (!m_memberMatches.isMatchedSourceEntry(m_obfSourceEntry) && !m_memberMatches.isMatchedDestEntry(m_obfDestEntry)) {
435 GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() {
436 @Override
437 public void actionPerformed(ActionEvent event) {
438 match();
439 }
440 });
441 }
442 } else if (m_obfSourceEntry != null) {
443 GuiTricks.activateButton(m_unmatchableButton, "Set Unmatchable", new ActionListener() {
444 @Override
445 public void actionPerformed(ActionEvent event) {
446 unmatchable();
447 }
448 });
449 }
450 }
451
452 protected void match() {
453
454 // update the field matches
455 m_memberMatches.makeMatch(m_obfSourceEntry, m_obfDestEntry);
456 save();
457
458 // update the ui
459 onSelectSource(null);
460 onSelectDest(null);
461 updateSourceHighlights();
462 updateDestHighlights();
463 updateSourceClasses();
464 }
465
466 protected void unmatch() {
467
468 // update the field matches
469 m_memberMatches.unmakeMatch(m_obfSourceEntry, m_obfDestEntry);
470 save();
471
472 // update the ui
473 onSelectSource(null);
474 onSelectDest(null);
475 updateSourceHighlights();
476 updateDestHighlights();
477 updateSourceClasses();
478 }
479
480 protected void unmatchable() {
481
482 // update the field matches
483 m_memberMatches.makeSourceUnmatchable(m_obfSourceEntry);
484 save();
485
486 // update the ui
487 onSelectSource(null);
488 onSelectDest(null);
489 updateSourceHighlights();
490 updateDestHighlights();
491 updateSourceClasses();
492 }
493
494 private void save() {
495 if (m_saveListener != null) {
496 m_saveListener.save(m_memberMatches);
497 }
498 }
499}
diff --git a/src/cuchaz/enigma/gui/ProgressDialog.java b/src/cuchaz/enigma/gui/ProgressDialog.java
deleted file mode 100644
index 1c20f10b..00000000
--- a/src/cuchaz/enigma/gui/ProgressDialog.java
+++ /dev/null
@@ -1,105 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.Dimension;
16import java.awt.FlowLayout;
17
18import javax.swing.BorderFactory;
19import javax.swing.JFrame;
20import javax.swing.JLabel;
21import javax.swing.JPanel;
22import javax.swing.JProgressBar;
23import javax.swing.WindowConstants;
24
25import cuchaz.enigma.Constants;
26import cuchaz.enigma.Deobfuscator.ProgressListener;
27
28public class ProgressDialog implements ProgressListener, AutoCloseable {
29
30 private JFrame m_frame;
31 private JLabel m_title;
32 private JLabel m_text;
33 private JProgressBar m_progress;
34
35 public ProgressDialog(JFrame parent) {
36
37 // init frame
38 m_frame = new JFrame(Constants.Name + " - Operation in progress");
39 final Container pane = m_frame.getContentPane();
40 FlowLayout layout = new FlowLayout();
41 layout.setAlignment(FlowLayout.LEFT);
42 pane.setLayout(layout);
43
44 m_title = new JLabel();
45 pane.add(m_title);
46
47 // set up the progress bar
48 JPanel panel = new JPanel();
49 pane.add(panel);
50 panel.setLayout(new BorderLayout());
51 m_text = GuiTricks.unboldLabel(new JLabel());
52 m_progress = new JProgressBar();
53 m_text.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
54 panel.add(m_text, BorderLayout.NORTH);
55 panel.add(m_progress, BorderLayout.CENTER);
56 panel.setPreferredSize(new Dimension(360, 50));
57
58 // show the frame
59 pane.doLayout();
60 m_frame.setSize(400, 120);
61 m_frame.setResizable(false);
62 m_frame.setLocationRelativeTo(parent);
63 m_frame.setVisible(true);
64 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
65 }
66
67 public void close() {
68 m_frame.dispose();
69 }
70
71 @Override
72 public void init(int totalWork, String title) {
73 m_title.setText(title);
74 m_progress.setMinimum(0);
75 m_progress.setMaximum(totalWork);
76 m_progress.setValue(0);
77 }
78
79 @Override
80 public void onProgress(int numDone, String message) {
81 m_text.setText(message);
82 m_progress.setValue(numDone);
83
84 // update the frame
85 m_frame.validate();
86 m_frame.repaint();
87 }
88
89 public static interface ProgressRunnable {
90 void run(ProgressListener listener) throws Exception;
91 }
92
93 public static void runInThread(final JFrame parent, final ProgressRunnable runnable) {
94 new Thread() {
95 @Override
96 public void run() {
97 try (ProgressDialog progress = new ProgressDialog(parent)) {
98 runnable.run(progress);
99 } catch (Exception ex) {
100 throw new Error(ex);
101 }
102 }
103 }.start();
104 }
105}
diff --git a/src/cuchaz/enigma/mapping/ArgumentEntry.java b/src/cuchaz/enigma/mapping/ArgumentEntry.java
deleted file mode 100644
index 9d99016e..00000000
--- a/src/cuchaz/enigma/mapping/ArgumentEntry.java
+++ /dev/null
@@ -1,116 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class ArgumentEntry implements Entry, Serializable {
18
19 private static final long serialVersionUID = 4472172468162696006L;
20
21 private BehaviorEntry m_behaviorEntry;
22 private int m_index;
23 private String m_name;
24
25 public ArgumentEntry(BehaviorEntry behaviorEntry, int index, String name) {
26 if (behaviorEntry == null) {
27 throw new IllegalArgumentException("Behavior cannot be null!");
28 }
29 if (index < 0) {
30 throw new IllegalArgumentException("Index must be non-negative!");
31 }
32 if (name == null) {
33 throw new IllegalArgumentException("Argument name cannot be null!");
34 }
35
36 m_behaviorEntry = behaviorEntry;
37 m_index = index;
38 m_name = name;
39 }
40
41 public ArgumentEntry(ArgumentEntry other) {
42 m_behaviorEntry = (BehaviorEntry)m_behaviorEntry.cloneToNewClass(getClassEntry());
43 m_index = other.m_index;
44 m_name = other.m_name;
45 }
46
47 public ArgumentEntry(ArgumentEntry other, String newClassName) {
48 m_behaviorEntry = (BehaviorEntry)other.m_behaviorEntry.cloneToNewClass(new ClassEntry(newClassName));
49 m_index = other.m_index;
50 m_name = other.m_name;
51 }
52
53 public BehaviorEntry getBehaviorEntry() {
54 return m_behaviorEntry;
55 }
56
57 public int getIndex() {
58 return m_index;
59 }
60
61 @Override
62 public String getName() {
63 return m_name;
64 }
65
66 @Override
67 public ClassEntry getClassEntry() {
68 return m_behaviorEntry.getClassEntry();
69 }
70
71 @Override
72 public String getClassName() {
73 return m_behaviorEntry.getClassName();
74 }
75
76 @Override
77 public ArgumentEntry cloneToNewClass(ClassEntry classEntry) {
78 return new ArgumentEntry(this, classEntry.getName());
79 }
80
81 public String getMethodName() {
82 return m_behaviorEntry.getName();
83 }
84
85 public Signature getMethodSignature() {
86 return m_behaviorEntry.getSignature();
87 }
88
89 @Override
90 public int hashCode() {
91 return Util.combineHashesOrdered(
92 m_behaviorEntry,
93 Integer.valueOf(m_index).hashCode(),
94 m_name.hashCode()
95 );
96 }
97
98 @Override
99 public boolean equals(Object other) {
100 if (other instanceof ArgumentEntry) {
101 return equals((ArgumentEntry)other);
102 }
103 return false;
104 }
105
106 public boolean equals(ArgumentEntry other) {
107 return m_behaviorEntry.equals(other.m_behaviorEntry)
108 && m_index == other.m_index
109 && m_name.equals(other.m_name);
110 }
111
112 @Override
113 public String toString() {
114 return m_behaviorEntry.toString() + "(" + m_index + ":" + m_name + ")";
115 }
116}
diff --git a/src/cuchaz/enigma/mapping/ArgumentMapping.java b/src/cuchaz/enigma/mapping/ArgumentMapping.java
deleted file mode 100644
index a0055a63..00000000
--- a/src/cuchaz/enigma/mapping/ArgumentMapping.java
+++ /dev/null
@@ -1,49 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15public class ArgumentMapping implements Serializable, Comparable<ArgumentMapping> {
16
17 private static final long serialVersionUID = 8610742471440861315L;
18
19 private int m_index;
20 private String m_name;
21
22 // NOTE: this argument order is important for the MethodReader/MethodWriter
23 public ArgumentMapping(int index, String name) {
24 m_index = index;
25 m_name = NameValidator.validateArgumentName(name);
26 }
27
28 public ArgumentMapping(ArgumentMapping other) {
29 m_index = other.m_index;
30 m_name = other.m_name;
31 }
32
33 public int getIndex() {
34 return m_index;
35 }
36
37 public String getName() {
38 return m_name;
39 }
40
41 public void setName(String val) {
42 m_name = NameValidator.validateArgumentName(val);
43 }
44
45 @Override
46 public int compareTo(ArgumentMapping other) {
47 return Integer.compare(m_index, other.m_index);
48 }
49}
diff --git a/src/cuchaz/enigma/mapping/ClassEntry.java b/src/cuchaz/enigma/mapping/ClassEntry.java
deleted file mode 100644
index 373203f0..00000000
--- a/src/cuchaz/enigma/mapping/ClassEntry.java
+++ /dev/null
@@ -1,172 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.List;
15
16import com.google.common.collect.Lists;
17
18public class ClassEntry implements Entry, Serializable {
19
20 private static final long serialVersionUID = 4235460580973955811L;
21
22 private String m_name;
23
24 public ClassEntry(String className) {
25 if (className == null) {
26 throw new IllegalArgumentException("Class name cannot be null!");
27 }
28 if (className.indexOf('.') >= 0) {
29 throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className);
30 }
31
32 m_name = className;
33
34 if (isInnerClass() && getInnermostClassName().indexOf('/') >= 0) {
35 throw new IllegalArgumentException("Inner class must not have a package: " + className);
36 }
37 }
38
39 public ClassEntry(ClassEntry other) {
40 m_name = other.m_name;
41 }
42
43 @Override
44 public String getName() {
45 return m_name;
46 }
47
48 @Override
49 public String getClassName() {
50 return m_name;
51 }
52
53 @Override
54 public ClassEntry getClassEntry() {
55 return this;
56 }
57
58 @Override
59 public ClassEntry cloneToNewClass(ClassEntry classEntry) {
60 return classEntry;
61 }
62
63 @Override
64 public int hashCode() {
65 return m_name.hashCode();
66 }
67
68 @Override
69 public boolean equals(Object other) {
70 if (other instanceof ClassEntry) {
71 return equals((ClassEntry)other);
72 }
73 return false;
74 }
75
76 public boolean equals(ClassEntry other) {
77 return m_name.equals(other.m_name);
78 }
79
80 @Override
81 public String toString() {
82 return m_name;
83 }
84
85 public boolean isInnerClass() {
86 return m_name.lastIndexOf('$') >= 0;
87 }
88
89 public List<String> getClassChainNames() {
90 return Lists.newArrayList(m_name.split("\\$"));
91 }
92
93 public List<ClassEntry> getClassChain() {
94 List<ClassEntry> entries = Lists.newArrayList();
95 StringBuilder buf = new StringBuilder();
96 for (String name : getClassChainNames()) {
97 if (buf.length() > 0) {
98 buf.append("$");
99 }
100 buf.append(name);
101 entries.add(new ClassEntry(buf.toString()));
102 }
103 return entries;
104 }
105
106 public String getOutermostClassName() {
107 if (isInnerClass()) {
108 return m_name.substring(0, m_name.indexOf('$'));
109 }
110 return m_name;
111 }
112
113 public ClassEntry getOutermostClassEntry() {
114 return new ClassEntry(getOutermostClassName());
115 }
116
117 public String getOuterClassName() {
118 if (!isInnerClass()) {
119 throw new Error("This is not an inner class!");
120 }
121 return m_name.substring(0, m_name.lastIndexOf('$'));
122 }
123
124 public ClassEntry getOuterClassEntry() {
125 return new ClassEntry(getOuterClassName());
126 }
127
128 public String getInnermostClassName() {
129 if (!isInnerClass()) {
130 throw new Error("This is not an inner class!");
131 }
132 return m_name.substring(m_name.lastIndexOf('$') + 1);
133 }
134
135 public boolean isInDefaultPackage() {
136 return m_name.indexOf('/') < 0;
137 }
138
139 public String getPackageName() {
140 int pos = m_name.lastIndexOf('/');
141 if (pos > 0) {
142 return m_name.substring(0, pos);
143 }
144 return null;
145 }
146
147 public String getSimpleName() {
148 int pos = m_name.lastIndexOf('/');
149 if (pos > 0) {
150 return m_name.substring(pos + 1);
151 }
152 return m_name;
153 }
154
155 public ClassEntry buildClassEntry(List<ClassEntry> classChain) {
156 assert(classChain.contains(this));
157 StringBuilder buf = new StringBuilder();
158 for (ClassEntry chainEntry : classChain) {
159 if (buf.length() == 0) {
160 buf.append(chainEntry.getName());
161 } else {
162 buf.append("$");
163 buf.append(chainEntry.isInnerClass() ? chainEntry.getInnermostClassName() : chainEntry.getSimpleName());
164 }
165
166 if (chainEntry == this) {
167 break;
168 }
169 }
170 return new ClassEntry(buf.toString());
171 }
172}
diff --git a/src/cuchaz/enigma/mapping/ClassMapping.java b/src/cuchaz/enigma/mapping/ClassMapping.java
deleted file mode 100644
index 0b0105ec..00000000
--- a/src/cuchaz/enigma/mapping/ClassMapping.java
+++ /dev/null
@@ -1,460 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.ArrayList;
15import java.util.Map;
16
17import com.google.common.collect.Maps;
18
19public class ClassMapping implements Serializable, Comparable<ClassMapping> {
20
21 private static final long serialVersionUID = -5148491146902340107L;
22
23 private String m_obfFullName;
24 private String m_obfSimpleName;
25 private String m_deobfName;
26 private Map<String,ClassMapping> m_innerClassesByObfSimple;
27 private Map<String,ClassMapping> m_innerClassesByDeobf;
28 private Map<String,FieldMapping> m_fieldsByObf;
29 private Map<String,FieldMapping> m_fieldsByDeobf;
30 private Map<String,MethodMapping> m_methodsByObf;
31 private Map<String,MethodMapping> m_methodsByDeobf;
32
33 public ClassMapping(String obfFullName) {
34 this(obfFullName, null);
35 }
36
37 public ClassMapping(String obfFullName, String deobfName) {
38 m_obfFullName = obfFullName;
39 ClassEntry classEntry = new ClassEntry(obfFullName);
40 m_obfSimpleName = classEntry.isInnerClass() ? classEntry.getInnermostClassName() : classEntry.getSimpleName();
41 m_deobfName = NameValidator.validateClassName(deobfName, false);
42 m_innerClassesByObfSimple = Maps.newHashMap();
43 m_innerClassesByDeobf = Maps.newHashMap();
44 m_fieldsByObf = Maps.newHashMap();
45 m_fieldsByDeobf = Maps.newHashMap();
46 m_methodsByObf = Maps.newHashMap();
47 m_methodsByDeobf = Maps.newHashMap();
48 }
49
50 public String getObfFullName() {
51 return m_obfFullName;
52 }
53
54 public String getObfSimpleName() {
55 return m_obfSimpleName;
56 }
57
58 public String getDeobfName() {
59 return m_deobfName;
60 }
61
62 public void setDeobfName(String val) {
63 m_deobfName = NameValidator.validateClassName(val, false);
64 }
65
66 //// INNER CLASSES ////////
67
68 public Iterable<ClassMapping> innerClasses() {
69 assert (m_innerClassesByObfSimple.size() >= m_innerClassesByDeobf.size());
70 return m_innerClassesByObfSimple.values();
71 }
72
73 public void addInnerClassMapping(ClassMapping classMapping) {
74 boolean obfWasAdded = m_innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping) == null;
75 assert (obfWasAdded);
76 if (classMapping.getDeobfName() != null) {
77 assert (isSimpleClassName(classMapping.getDeobfName()));
78 boolean deobfWasAdded = m_innerClassesByDeobf.put(classMapping.getDeobfName(), classMapping) == null;
79 assert (deobfWasAdded);
80 }
81 }
82
83 public void removeInnerClassMapping(ClassMapping classMapping) {
84 boolean obfWasRemoved = m_innerClassesByObfSimple.remove(classMapping.getObfSimpleName()) != null;
85 assert (obfWasRemoved);
86 if (classMapping.getDeobfName() != null) {
87 boolean deobfWasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null;
88 assert (deobfWasRemoved);
89 }
90 }
91
92 public ClassMapping getOrCreateInnerClass(ClassEntry obfInnerClass) {
93 ClassMapping classMapping = m_innerClassesByObfSimple.get(obfInnerClass.getInnermostClassName());
94 if (classMapping == null) {
95 classMapping = new ClassMapping(obfInnerClass.getName());
96 boolean wasAdded = m_innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping) == null;
97 assert (wasAdded);
98 }
99 return classMapping;
100 }
101
102 public ClassMapping getInnerClassByObfSimple(String obfSimpleName) {
103 assert (isSimpleClassName(obfSimpleName));
104 return m_innerClassesByObfSimple.get(obfSimpleName);
105 }
106
107 public ClassMapping getInnerClassByDeobf(String deobfName) {
108 assert (isSimpleClassName(deobfName));
109 return m_innerClassesByDeobf.get(deobfName);
110 }
111
112 public ClassMapping getInnerClassByDeobfThenObfSimple(String name) {
113 ClassMapping classMapping = getInnerClassByDeobf(name);
114 if (classMapping == null) {
115 classMapping = getInnerClassByObfSimple(name);
116 }
117 return classMapping;
118 }
119
120 public String getDeobfInnerClassName(String obfSimpleName) {
121 assert (isSimpleClassName(obfSimpleName));
122 ClassMapping classMapping = m_innerClassesByObfSimple.get(obfSimpleName);
123 if (classMapping != null) {
124 return classMapping.getDeobfName();
125 }
126 return null;
127 }
128
129 public void setInnerClassName(ClassEntry obfInnerClass, String deobfName) {
130 ClassMapping classMapping = getOrCreateInnerClass(obfInnerClass);
131 if (classMapping.getDeobfName() != null) {
132 boolean wasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null;
133 assert (wasRemoved);
134 }
135 classMapping.setDeobfName(deobfName);
136 if (deobfName != null) {
137 assert (isSimpleClassName(deobfName));
138 boolean wasAdded = m_innerClassesByDeobf.put(deobfName, classMapping) == null;
139 assert (wasAdded);
140 }
141 }
142
143 public boolean hasInnerClassByObfSimple(String obfSimpleName) {
144 return m_innerClassesByObfSimple.containsKey(obfSimpleName);
145 }
146
147 public boolean hasInnerClassByDeobf(String deobfName) {
148 return m_innerClassesByDeobf.containsKey(deobfName);
149 }
150
151
152 //// FIELDS ////////
153
154 public Iterable<FieldMapping> fields() {
155 assert (m_fieldsByObf.size() == m_fieldsByDeobf.size());
156 return m_fieldsByObf.values();
157 }
158
159 public boolean containsObfField(String obfName, Type obfType) {
160 return m_fieldsByObf.containsKey(getFieldKey(obfName, obfType));
161 }
162
163 public boolean containsDeobfField(String deobfName, Type deobfType) {
164 return m_fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfType));
165 }
166
167 public void addFieldMapping(FieldMapping fieldMapping) {
168 String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType());
169 if (m_fieldsByObf.containsKey(obfKey)) {
170 throw new Error("Already have mapping for " + m_obfFullName + "." + obfKey);
171 }
172 String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType());
173 if (m_fieldsByDeobf.containsKey(deobfKey)) {
174 throw new Error("Already have mapping for " + m_deobfName + "." + deobfKey);
175 }
176 boolean obfWasAdded = m_fieldsByObf.put(obfKey, fieldMapping) == null;
177 assert (obfWasAdded);
178 boolean deobfWasAdded = m_fieldsByDeobf.put(deobfKey, fieldMapping) == null;
179 assert (deobfWasAdded);
180 assert (m_fieldsByObf.size() == m_fieldsByDeobf.size());
181 }
182
183 public void removeFieldMapping(FieldMapping fieldMapping) {
184 boolean obfWasRemoved = m_fieldsByObf.remove(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType())) != null;
185 assert (obfWasRemoved);
186 if (fieldMapping.getDeobfName() != null) {
187 boolean deobfWasRemoved = m_fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType())) != null;
188 assert (deobfWasRemoved);
189 }
190 }
191
192 public FieldMapping getFieldByObf(String obfName, Type obfType) {
193 return m_fieldsByObf.get(getFieldKey(obfName, obfType));
194 }
195
196 public FieldMapping getFieldByDeobf(String deobfName, Type obfType) {
197 return m_fieldsByDeobf.get(getFieldKey(deobfName, obfType));
198 }
199
200 public String getObfFieldName(String deobfName, Type obfType) {
201 FieldMapping fieldMapping = m_fieldsByDeobf.get(getFieldKey(deobfName, obfType));
202 if (fieldMapping != null) {
203 return fieldMapping.getObfName();
204 }
205 return null;
206 }
207
208 public String getDeobfFieldName(String obfName, Type obfType) {
209 FieldMapping fieldMapping = m_fieldsByObf.get(getFieldKey(obfName, obfType));
210 if (fieldMapping != null) {
211 return fieldMapping.getDeobfName();
212 }
213 return null;
214 }
215
216 private String getFieldKey(String name, Type type) {
217 if (name == null) {
218 throw new IllegalArgumentException("name cannot be null!");
219 }
220 if (type == null) {
221 throw new IllegalArgumentException("type cannot be null!");
222 }
223 return name + ":" + type;
224 }
225
226
227 public void setFieldName(String obfName, Type obfType, String deobfName) {
228 assert(deobfName != null);
229 FieldMapping fieldMapping = m_fieldsByObf.get(getFieldKey(obfName, obfType));
230 if (fieldMapping == null) {
231 fieldMapping = new FieldMapping(obfName, obfType, deobfName);
232 boolean obfWasAdded = m_fieldsByObf.put(getFieldKey(obfName, obfType), fieldMapping) == null;
233 assert (obfWasAdded);
234 } else {
235 boolean wasRemoved = m_fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), obfType)) != null;
236 assert (wasRemoved);
237 }
238 fieldMapping.setDeobfName(deobfName);
239 if (deobfName != null) {
240 boolean wasAdded = m_fieldsByDeobf.put(getFieldKey(deobfName, obfType), fieldMapping) == null;
241 assert (wasAdded);
242 }
243 }
244
245 public void setFieldObfNameAndType(String oldObfName, Type obfType, String newObfName, Type newObfType) {
246 assert(newObfName != null);
247 FieldMapping fieldMapping = m_fieldsByObf.remove(getFieldKey(oldObfName, obfType));
248 assert(fieldMapping != null);
249 fieldMapping.setObfName(newObfName);
250 fieldMapping.setObfType(newObfType);
251 boolean obfWasAdded = m_fieldsByObf.put(getFieldKey(newObfName, newObfType), fieldMapping) == null;
252 assert(obfWasAdded);
253 }
254
255
256 //// METHODS ////////
257
258 public Iterable<MethodMapping> methods() {
259 assert (m_methodsByObf.size() >= m_methodsByDeobf.size());
260 return m_methodsByObf.values();
261 }
262
263 public boolean containsObfMethod(String obfName, Signature obfSignature) {
264 return m_methodsByObf.containsKey(getMethodKey(obfName, obfSignature));
265 }
266
267 public boolean containsDeobfMethod(String deobfName, Signature obfSignature) {
268 return m_methodsByDeobf.containsKey(getMethodKey(deobfName, obfSignature));
269 }
270
271 public void addMethodMapping(MethodMapping methodMapping) {
272 String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature());
273 if (m_methodsByObf.containsKey(obfKey)) {
274 throw new Error("Already have mapping for " + m_obfFullName + "." + obfKey);
275 }
276 boolean wasAdded = m_methodsByObf.put(obfKey, methodMapping) == null;
277 assert (wasAdded);
278 if (methodMapping.getDeobfName() != null) {
279 String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature());
280 if (m_methodsByDeobf.containsKey(deobfKey)) {
281 throw new Error("Already have mapping for " + m_deobfName + "." + deobfKey);
282 }
283 boolean deobfWasAdded = m_methodsByDeobf.put(deobfKey, methodMapping) == null;
284 assert (deobfWasAdded);
285 }
286 assert (m_methodsByObf.size() >= m_methodsByDeobf.size());
287 }
288
289 public void removeMethodMapping(MethodMapping methodMapping) {
290 boolean obfWasRemoved = m_methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature())) != null;
291 assert (obfWasRemoved);
292 if (methodMapping.getDeobfName() != null) {
293 boolean deobfWasRemoved = m_methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null;
294 assert (deobfWasRemoved);
295 }
296 }
297
298 public MethodMapping getMethodByObf(String obfName, Signature obfSignature) {
299 return m_methodsByObf.get(getMethodKey(obfName, obfSignature));
300 }
301
302 public MethodMapping getMethodByDeobf(String deobfName, Signature obfSignature) {
303 return m_methodsByDeobf.get(getMethodKey(deobfName, obfSignature));
304 }
305
306 private String getMethodKey(String name, Signature signature) {
307 if (name == null) {
308 throw new IllegalArgumentException("name cannot be null!");
309 }
310 if (signature == null) {
311 throw new IllegalArgumentException("signature cannot be null!");
312 }
313 return name + signature;
314 }
315
316 public void setMethodName(String obfName, Signature obfSignature, String deobfName) {
317 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfName, obfSignature));
318 if (methodMapping == null) {
319 methodMapping = createMethodMapping(obfName, obfSignature);
320 } else if (methodMapping.getDeobfName() != null) {
321 boolean wasRemoved = m_methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null;
322 assert (wasRemoved);
323 }
324 methodMapping.setDeobfName(deobfName);
325 if (deobfName != null) {
326 boolean wasAdded = m_methodsByDeobf.put(getMethodKey(deobfName, obfSignature), methodMapping) == null;
327 assert (wasAdded);
328 }
329 }
330
331 public void setMethodObfNameAndSignature(String oldObfName, Signature obfSignature, String newObfName, Signature newObfSignature) {
332 assert(newObfName != null);
333 MethodMapping methodMapping = m_methodsByObf.remove(getMethodKey(oldObfName, obfSignature));
334 assert(methodMapping != null);
335 methodMapping.setObfName(newObfName);
336 methodMapping.setObfSignature(newObfSignature);
337 boolean obfWasAdded = m_methodsByObf.put(getMethodKey(newObfName, newObfSignature), methodMapping) == null;
338 assert(obfWasAdded);
339 }
340
341 //// ARGUMENTS ////////
342
343 public void setArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex, String argumentName) {
344 assert(argumentName != null);
345 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature));
346 if (methodMapping == null) {
347 methodMapping = createMethodMapping(obfMethodName, obfMethodSignature);
348 }
349 methodMapping.setArgumentName(argumentIndex, argumentName);
350 }
351
352 public void removeArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex) {
353 m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)).removeArgumentName(argumentIndex);
354 }
355
356 private MethodMapping createMethodMapping(String obfName, Signature obfSignature) {
357 MethodMapping methodMapping = new MethodMapping(obfName, obfSignature);
358 boolean wasAdded = m_methodsByObf.put(getMethodKey(obfName, obfSignature), methodMapping) == null;
359 assert (wasAdded);
360 return methodMapping;
361 }
362
363 @Override
364 public String toString() {
365 StringBuilder buf = new StringBuilder();
366 buf.append(m_obfFullName);
367 buf.append(" <-> ");
368 buf.append(m_deobfName);
369 buf.append("\n");
370 buf.append("Fields:\n");
371 for (FieldMapping fieldMapping : fields()) {
372 buf.append("\t");
373 buf.append(fieldMapping.getObfName());
374 buf.append(" <-> ");
375 buf.append(fieldMapping.getDeobfName());
376 buf.append("\n");
377 }
378 buf.append("Methods:\n");
379 for (MethodMapping methodMapping : m_methodsByObf.values()) {
380 buf.append(methodMapping.toString());
381 buf.append("\n");
382 }
383 buf.append("Inner Classes:\n");
384 for (ClassMapping classMapping : m_innerClassesByObfSimple.values()) {
385 buf.append("\t");
386 buf.append(classMapping.getObfSimpleName());
387 buf.append(" <-> ");
388 buf.append(classMapping.getDeobfName());
389 buf.append("\n");
390 }
391 return buf.toString();
392 }
393
394 @Override
395 public int compareTo(ClassMapping other) {
396 // sort by a, b, c, ... aa, ab, etc
397 if (m_obfFullName.length() != other.m_obfFullName.length()) {
398 return m_obfFullName.length() - other.m_obfFullName.length();
399 }
400 return m_obfFullName.compareTo(other.m_obfFullName);
401 }
402
403 public boolean renameObfClass(String oldObfClassName, String newObfClassName) {
404
405 // rename inner classes
406 for (ClassMapping innerClassMapping : new ArrayList<ClassMapping>(m_innerClassesByObfSimple.values())) {
407 if (innerClassMapping.renameObfClass(oldObfClassName, newObfClassName)) {
408 boolean wasRemoved = m_innerClassesByObfSimple.remove(oldObfClassName) != null;
409 assert (wasRemoved);
410 boolean wasAdded = m_innerClassesByObfSimple.put(newObfClassName, innerClassMapping) == null;
411 assert (wasAdded);
412 }
413 }
414
415 // rename field types
416 for (FieldMapping fieldMapping : new ArrayList<FieldMapping>(m_fieldsByObf.values())) {
417 String oldFieldKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType());
418 if (fieldMapping.renameObfClass(oldObfClassName, newObfClassName)) {
419 boolean wasRemoved = m_fieldsByObf.remove(oldFieldKey) != null;
420 assert (wasRemoved);
421 boolean wasAdded = m_fieldsByObf.put(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()), fieldMapping) == null;
422 assert (wasAdded);
423 }
424 }
425
426 // rename method signatures
427 for (MethodMapping methodMapping : new ArrayList<MethodMapping>(m_methodsByObf.values())) {
428 String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature());
429 if (methodMapping.renameObfClass(oldObfClassName, newObfClassName)) {
430 boolean wasRemoved = m_methodsByObf.remove(oldMethodKey) != null;
431 assert (wasRemoved);
432 boolean wasAdded = m_methodsByObf.put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()), methodMapping) == null;
433 assert (wasAdded);
434 }
435 }
436
437 if (m_obfFullName.equals(oldObfClassName)) {
438 // rename this class
439 m_obfFullName = newObfClassName;
440 return true;
441 }
442 return false;
443 }
444
445 public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) {
446 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfBehaviorEntry.getName(), obfBehaviorEntry.getSignature()));
447 if (methodMapping != null) {
448 return methodMapping.containsArgument(name);
449 }
450 return false;
451 }
452
453 public static boolean isSimpleClassName(String name) {
454 return name.indexOf('/') < 0 && name.indexOf('$') < 0;
455 }
456
457 public ClassEntry getObfEntry() {
458 return new ClassEntry(m_obfFullName);
459 }
460}
diff --git a/src/cuchaz/enigma/mapping/ConstructorEntry.java b/src/cuchaz/enigma/mapping/ConstructorEntry.java
deleted file mode 100644
index 7cde8f65..00000000
--- a/src/cuchaz/enigma/mapping/ConstructorEntry.java
+++ /dev/null
@@ -1,116 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class ConstructorEntry implements BehaviorEntry, Serializable {
18
19 private static final long serialVersionUID = -868346075317366758L;
20
21 private ClassEntry m_classEntry;
22 private Signature m_signature;
23
24 public ConstructorEntry(ClassEntry classEntry) {
25 this(classEntry, null);
26 }
27
28 public ConstructorEntry(ClassEntry classEntry, Signature signature) {
29 if (classEntry == null) {
30 throw new IllegalArgumentException("Class cannot be null!");
31 }
32
33 m_classEntry = classEntry;
34 m_signature = signature;
35 }
36
37 public ConstructorEntry(ConstructorEntry other) {
38 m_classEntry = new ClassEntry(other.m_classEntry);
39 m_signature = other.m_signature;
40 }
41
42 public ConstructorEntry(ConstructorEntry other, String newClassName) {
43 m_classEntry = new ClassEntry(newClassName);
44 m_signature = other.m_signature;
45 }
46
47 @Override
48 public ClassEntry getClassEntry() {
49 return m_classEntry;
50 }
51
52 @Override
53 public String getName() {
54 if (isStatic()) {
55 return "<clinit>";
56 }
57 return "<init>";
58 }
59
60 public boolean isStatic() {
61 return m_signature == null;
62 }
63
64 @Override
65 public Signature getSignature() {
66 return m_signature;
67 }
68
69 @Override
70 public String getClassName() {
71 return m_classEntry.getName();
72 }
73
74 @Override
75 public ConstructorEntry cloneToNewClass(ClassEntry classEntry) {
76 return new ConstructorEntry(this, classEntry.getName());
77 }
78
79 @Override
80 public int hashCode() {
81 if (isStatic()) {
82 return Util.combineHashesOrdered(m_classEntry);
83 } else {
84 return Util.combineHashesOrdered(m_classEntry, m_signature);
85 }
86 }
87
88 @Override
89 public boolean equals(Object other) {
90 if (other instanceof ConstructorEntry) {
91 return equals((ConstructorEntry)other);
92 }
93 return false;
94 }
95
96 public boolean equals(ConstructorEntry other) {
97 if (isStatic() != other.isStatic()) {
98 return false;
99 }
100
101 if (isStatic()) {
102 return m_classEntry.equals(other.m_classEntry);
103 } else {
104 return m_classEntry.equals(other.m_classEntry) && m_signature.equals(other.m_signature);
105 }
106 }
107
108 @Override
109 public String toString() {
110 if (isStatic()) {
111 return m_classEntry.getName() + "." + getName();
112 } else {
113 return m_classEntry.getName() + "." + getName() + m_signature;
114 }
115 }
116}
diff --git a/src/cuchaz/enigma/mapping/EntryFactory.java b/src/cuchaz/enigma/mapping/EntryFactory.java
deleted file mode 100644
index 03d97ba1..00000000
--- a/src/cuchaz/enigma/mapping/EntryFactory.java
+++ /dev/null
@@ -1,166 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.CtConstructor;
16import javassist.CtField;
17import javassist.CtMethod;
18import javassist.bytecode.Descriptor;
19import javassist.expr.ConstructorCall;
20import javassist.expr.FieldAccess;
21import javassist.expr.MethodCall;
22import javassist.expr.NewExpr;
23
24import cuchaz.enigma.analysis.JarIndex;
25
26public class EntryFactory {
27
28 public static ClassEntry getClassEntry(CtClass c) {
29 return new ClassEntry(Descriptor.toJvmName(c.getName()));
30 }
31
32 public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) {
33 ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfFullName());
34 return obfClassEntry.buildClassEntry(jarIndex.getObfClassChain(obfClassEntry));
35 }
36
37 private static ClassEntry getObfClassEntry(ClassMapping classMapping) {
38 return new ClassEntry(classMapping.getObfFullName());
39 }
40
41 public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) {
42 return new ClassEntry(classMapping.getDeobfName());
43 }
44
45 public static ClassEntry getSuperclassEntry(CtClass c) {
46 return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
47 }
48
49 public static FieldEntry getFieldEntry(CtField field) {
50 return new FieldEntry(
51 getClassEntry(field.getDeclaringClass()),
52 field.getName(),
53 new Type(field.getFieldInfo().getDescriptor())
54 );
55 }
56
57 public static FieldEntry getFieldEntry(FieldAccess call) {
58 return new FieldEntry(
59 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
60 call.getFieldName(),
61 new Type(call.getSignature())
62 );
63 }
64
65 public static FieldEntry getFieldEntry(String className, String name, String type) {
66 return new FieldEntry(new ClassEntry(className), name, new Type(type));
67 }
68
69 public static FieldEntry getObfFieldEntry(ClassMapping classMapping, FieldMapping fieldMapping) {
70 return new FieldEntry(
71 getObfClassEntry(classMapping),
72 fieldMapping.getObfName(),
73 fieldMapping.getObfType()
74 );
75 }
76
77 public static MethodEntry getMethodEntry(CtMethod method) {
78 return new MethodEntry(
79 getClassEntry(method.getDeclaringClass()),
80 method.getName(),
81 new Signature(method.getMethodInfo().getDescriptor())
82 );
83 }
84
85 public static MethodEntry getMethodEntry(MethodCall call) {
86 return new MethodEntry(
87 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
88 call.getMethodName(),
89 new Signature(call.getSignature())
90 );
91 }
92
93 public static ConstructorEntry getConstructorEntry(CtConstructor constructor) {
94 if (constructor.isClassInitializer()) {
95 return new ConstructorEntry(
96 getClassEntry(constructor.getDeclaringClass())
97 );
98 } else {
99 return new ConstructorEntry(
100 getClassEntry(constructor.getDeclaringClass()),
101 new Signature(constructor.getMethodInfo().getDescriptor())
102 );
103 }
104 }
105
106 public static ConstructorEntry getConstructorEntry(ConstructorCall call) {
107 return new ConstructorEntry(
108 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
109 new Signature(call.getSignature())
110 );
111 }
112
113 public static ConstructorEntry getConstructorEntry(NewExpr call) {
114 return new ConstructorEntry(
115 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
116 new Signature(call.getSignature())
117 );
118 }
119
120 public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) {
121 if (behavior instanceof CtMethod) {
122 return getMethodEntry((CtMethod)behavior);
123 } else if (behavior instanceof CtConstructor) {
124 return getConstructorEntry((CtConstructor)behavior);
125 }
126 throw new Error("behavior is neither Method nor Constructor!");
127 }
128
129 public static BehaviorEntry getBehaviorEntry(String className, String behaviorName, String behaviorSignature) {
130 return getBehaviorEntry(new ClassEntry(className), behaviorName, new Signature(behaviorSignature));
131 }
132
133 public static BehaviorEntry getBehaviorEntry(String className, String behaviorName) {
134 return getBehaviorEntry(new ClassEntry(className), behaviorName);
135 }
136
137 public static BehaviorEntry getBehaviorEntry(String className) {
138 return new ConstructorEntry(new ClassEntry(className));
139 }
140
141 public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName, Signature behaviorSignature) {
142 if (behaviorName.equals("<init>")) {
143 return new ConstructorEntry(classEntry, behaviorSignature);
144 } else if(behaviorName.equals("<clinit>")) {
145 return new ConstructorEntry(classEntry);
146 } else {
147 return new MethodEntry(classEntry, behaviorName, behaviorSignature);
148 }
149 }
150
151 public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName) {
152 if(behaviorName.equals("<clinit>")) {
153 return new ConstructorEntry(classEntry);
154 } else {
155 throw new IllegalArgumentException("Only class initializers don't have signatures");
156 }
157 }
158
159 public static BehaviorEntry getObfBehaviorEntry(ClassEntry classEntry, MethodMapping methodMapping) {
160 return getBehaviorEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature());
161 }
162
163 public static BehaviorEntry getObfBehaviorEntry(ClassMapping classMapping, MethodMapping methodMapping) {
164 return getObfBehaviorEntry(getObfClassEntry(classMapping), methodMapping);
165 }
166}
diff --git a/src/cuchaz/enigma/mapping/FieldEntry.java b/src/cuchaz/enigma/mapping/FieldEntry.java
deleted file mode 100644
index e4a74f4f..00000000
--- a/src/cuchaz/enigma/mapping/FieldEntry.java
+++ /dev/null
@@ -1,99 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class FieldEntry implements Entry, Serializable {
18
19 private static final long serialVersionUID = 3004663582802885451L;
20
21 private ClassEntry m_classEntry;
22 private String m_name;
23 private Type m_type;
24
25 // NOTE: this argument order is important for the MethodReader/MethodWriter
26 public FieldEntry(ClassEntry classEntry, String name, Type type) {
27 if (classEntry == null) {
28 throw new IllegalArgumentException("Class cannot be null!");
29 }
30 if (name == null) {
31 throw new IllegalArgumentException("Field name cannot be null!");
32 }
33 if (type == null) {
34 throw new IllegalArgumentException("Field type cannot be null!");
35 }
36
37 m_classEntry = classEntry;
38 m_name = name;
39 m_type = type;
40 }
41
42 public FieldEntry(FieldEntry other) {
43 this(other, new ClassEntry(other.m_classEntry));
44 }
45
46 public FieldEntry(FieldEntry other, ClassEntry newClassEntry) {
47 m_classEntry = newClassEntry;
48 m_name = other.m_name;
49 m_type = other.m_type;
50 }
51
52 @Override
53 public ClassEntry getClassEntry() {
54 return m_classEntry;
55 }
56
57 @Override
58 public String getName() {
59 return m_name;
60 }
61
62 @Override
63 public String getClassName() {
64 return m_classEntry.getName();
65 }
66
67 public Type getType() {
68 return m_type;
69 }
70
71 @Override
72 public FieldEntry cloneToNewClass(ClassEntry classEntry) {
73 return new FieldEntry(this, classEntry);
74 }
75
76 @Override
77 public int hashCode() {
78 return Util.combineHashesOrdered(m_classEntry, m_name, m_type);
79 }
80
81 @Override
82 public boolean equals(Object other) {
83 if (other instanceof FieldEntry) {
84 return equals((FieldEntry)other);
85 }
86 return false;
87 }
88
89 public boolean equals(FieldEntry other) {
90 return m_classEntry.equals(other.m_classEntry)
91 && m_name.equals(other.m_name)
92 && m_type.equals(other.m_type);
93 }
94
95 @Override
96 public String toString() {
97 return m_classEntry.getName() + "." + m_name + ":" + m_type;
98 }
99}
diff --git a/src/cuchaz/enigma/mapping/FieldMapping.java b/src/cuchaz/enigma/mapping/FieldMapping.java
deleted file mode 100644
index 28557406..00000000
--- a/src/cuchaz/enigma/mapping/FieldMapping.java
+++ /dev/null
@@ -1,89 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15public class FieldMapping implements Serializable, Comparable<FieldMapping>, MemberMapping<FieldEntry> {
16
17 private static final long serialVersionUID = 8610742471440861315L;
18
19 private String m_obfName;
20 private String m_deobfName;
21 private Type m_obfType;
22
23 public FieldMapping(String obfName, Type obfType, String deobfName) {
24 m_obfName = obfName;
25 m_deobfName = NameValidator.validateFieldName(deobfName);
26 m_obfType = obfType;
27 }
28
29 public FieldMapping(FieldMapping other, ClassNameReplacer obfClassNameReplacer) {
30 m_obfName = other.m_obfName;
31 m_deobfName = other.m_deobfName;
32 m_obfType = new Type(other.m_obfType, obfClassNameReplacer);
33 }
34
35 @Override
36 public String getObfName() {
37 return m_obfName;
38 }
39
40 public void setObfName(String val) {
41 m_obfName = NameValidator.validateFieldName(val);
42 }
43
44 public String getDeobfName() {
45 return m_deobfName;
46 }
47
48 public void setDeobfName(String val) {
49 m_deobfName = NameValidator.validateFieldName(val);
50 }
51
52 public Type getObfType() {
53 return m_obfType;
54 }
55
56 public void setObfType(Type val) {
57 m_obfType = val;
58 }
59
60 @Override
61 public int compareTo(FieldMapping other) {
62 return (m_obfName + m_obfType).compareTo(other.m_obfName + other.m_obfType);
63 }
64
65 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
66
67 // rename obf classes in the type
68 Type newType = new Type(m_obfType, new ClassNameReplacer() {
69 @Override
70 public String replace(String className) {
71 if (className.equals(oldObfClassName)) {
72 return newObfClassName;
73 }
74 return null;
75 }
76 });
77
78 if (!newType.equals(m_obfType)) {
79 m_obfType = newType;
80 return true;
81 }
82 return false;
83 }
84
85 @Override
86 public FieldEntry getObfEntry(ClassEntry classEntry) {
87 return new FieldEntry(classEntry, m_obfName, new Type(m_obfType));
88 }
89}
diff --git a/src/cuchaz/enigma/mapping/IllegalNameException.java b/src/cuchaz/enigma/mapping/IllegalNameException.java
deleted file mode 100644
index f62df7c4..00000000
--- a/src/cuchaz/enigma/mapping/IllegalNameException.java
+++ /dev/null
@@ -1,44 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public class IllegalNameException extends RuntimeException {
14
15 private static final long serialVersionUID = -2279910052561114323L;
16
17 private String m_name;
18 private String m_reason;
19
20 public IllegalNameException(String name) {
21 this(name, null);
22 }
23
24 public IllegalNameException(String name, String reason) {
25 m_name = name;
26 m_reason = reason;
27 }
28
29 public String getReason() {
30 return m_reason;
31 }
32
33 @Override
34 public String getMessage() {
35 StringBuilder buf = new StringBuilder();
36 buf.append("Illegal name: ");
37 buf.append(m_name);
38 if (m_reason != null) {
39 buf.append(" because ");
40 buf.append(m_reason);
41 }
42 return buf.toString();
43 }
44}
diff --git a/src/cuchaz/enigma/mapping/Mappings.java b/src/cuchaz/enigma/mapping/Mappings.java
deleted file mode 100644
index 11ed5d0c..00000000
--- a/src/cuchaz/enigma/mapping/Mappings.java
+++ /dev/null
@@ -1,216 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.ArrayList;
15import java.util.Collection;
16import java.util.List;
17import java.util.Map;
18import java.util.Set;
19
20import com.google.common.collect.Lists;
21import com.google.common.collect.Maps;
22import com.google.common.collect.Sets;
23
24import cuchaz.enigma.analysis.TranslationIndex;
25
26public class Mappings implements Serializable {
27
28 private static final long serialVersionUID = 4649790259460259026L;
29
30 protected Map<String,ClassMapping> m_classesByObf;
31 protected Map<String,ClassMapping> m_classesByDeobf;
32
33 public Mappings() {
34 m_classesByObf = Maps.newHashMap();
35 m_classesByDeobf = Maps.newHashMap();
36 }
37
38 public Mappings(Iterable<ClassMapping> classes) {
39 this();
40
41 for (ClassMapping classMapping : classes) {
42 m_classesByObf.put(classMapping.getObfFullName(), classMapping);
43 if (classMapping.getDeobfName() != null) {
44 m_classesByDeobf.put(classMapping.getDeobfName(), classMapping);
45 }
46 }
47 }
48
49 public Collection<ClassMapping> classes() {
50 assert (m_classesByObf.size() >= m_classesByDeobf.size());
51 return m_classesByObf.values();
52 }
53
54 public void addClassMapping(ClassMapping classMapping) {
55 if (m_classesByObf.containsKey(classMapping.getObfFullName())) {
56 throw new Error("Already have mapping for " + classMapping.getObfFullName());
57 }
58 boolean obfWasAdded = m_classesByObf.put(classMapping.getObfFullName(), classMapping) == null;
59 assert (obfWasAdded);
60 if (classMapping.getDeobfName() != null) {
61 if (m_classesByDeobf.containsKey(classMapping.getDeobfName())) {
62 throw new Error("Already have mapping for " + classMapping.getDeobfName());
63 }
64 boolean deobfWasAdded = m_classesByDeobf.put(classMapping.getDeobfName(), classMapping) == null;
65 assert (deobfWasAdded);
66 }
67 }
68
69 public void removeClassMapping(ClassMapping classMapping) {
70 boolean obfWasRemoved = m_classesByObf.remove(classMapping.getObfFullName()) != null;
71 assert (obfWasRemoved);
72 if (classMapping.getDeobfName() != null) {
73 boolean deobfWasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
74 assert (deobfWasRemoved);
75 }
76 }
77
78 public ClassMapping getClassByObf(ClassEntry entry) {
79 return getClassByObf(entry.getName());
80 }
81
82 public ClassMapping getClassByObf(String obfName) {
83 return m_classesByObf.get(obfName);
84 }
85
86 public ClassMapping getClassByDeobf(ClassEntry entry) {
87 return getClassByDeobf(entry.getName());
88 }
89
90 public ClassMapping getClassByDeobf(String deobfName) {
91 return m_classesByDeobf.get(deobfName);
92 }
93
94 public void setClassDeobfName(ClassMapping classMapping, String deobfName) {
95 if (classMapping.getDeobfName() != null) {
96 boolean wasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
97 assert (wasRemoved);
98 }
99 classMapping.setDeobfName(deobfName);
100 if (deobfName != null) {
101 boolean wasAdded = m_classesByDeobf.put(deobfName, classMapping) == null;
102 assert (wasAdded);
103 }
104 }
105
106 public Translator getTranslator(TranslationDirection direction, TranslationIndex index) {
107 switch (direction) {
108 case Deobfuscating:
109
110 return new Translator(direction, m_classesByObf, index);
111
112 case Obfuscating:
113
114 // fill in the missing deobf class entries with obf entries
115 Map<String,ClassMapping> classes = Maps.newHashMap();
116 for (ClassMapping classMapping : classes()) {
117 if (classMapping.getDeobfName() != null) {
118 classes.put(classMapping.getDeobfName(), classMapping);
119 } else {
120 classes.put(classMapping.getObfFullName(), classMapping);
121 }
122 }
123
124 // translate the translation index
125 // NOTE: this isn't actually recursive
126 TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.Deobfuscating, index));
127
128 return new Translator(direction, classes, deobfIndex);
129
130 default:
131 throw new Error("Invalid translation direction!");
132 }
133 }
134
135 @Override
136 public String toString() {
137 StringBuilder buf = new StringBuilder();
138 for (ClassMapping classMapping : m_classesByObf.values()) {
139 buf.append(classMapping.toString());
140 buf.append("\n");
141 }
142 return buf.toString();
143 }
144
145 public void renameObfClass(String oldObfName, String newObfName) {
146 for (ClassMapping classMapping : new ArrayList<ClassMapping>(classes())) {
147 if (classMapping.renameObfClass(oldObfName, newObfName)) {
148 boolean wasRemoved = m_classesByObf.remove(oldObfName) != null;
149 assert (wasRemoved);
150 boolean wasAdded = m_classesByObf.put(newObfName, classMapping) == null;
151 assert (wasAdded);
152 }
153 }
154 }
155
156 public Set<String> getAllObfClassNames() {
157 final Set<String> classNames = Sets.newHashSet();
158 for (ClassMapping classMapping : classes()) {
159
160 // add the class name
161 classNames.add(classMapping.getObfFullName());
162
163 // add classes from method signatures
164 for (MethodMapping methodMapping : classMapping.methods()) {
165 for (Type type : methodMapping.getObfSignature().types()) {
166 if (type.hasClass()) {
167 classNames.add(type.getClassEntry().getClassName());
168 }
169 }
170 }
171 }
172 return classNames;
173 }
174
175 public boolean containsDeobfClass(String deobfName) {
176 return m_classesByDeobf.containsKey(deobfName);
177 }
178
179 public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName, Type obfType) {
180 ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
181 if (classMapping != null) {
182 return classMapping.containsDeobfField(deobfName, obfType);
183 }
184 return false;
185 }
186
187 public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, Signature deobfSignature) {
188 ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
189 if (classMapping != null) {
190 return classMapping.containsDeobfMethod(deobfName, deobfSignature);
191 }
192 return false;
193 }
194
195 public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) {
196 ClassMapping classMapping = m_classesByObf.get(obfBehaviorEntry.getClassName());
197 if (classMapping != null) {
198 return classMapping.containsArgument(obfBehaviorEntry, name);
199 }
200 return false;
201 }
202
203 public List<ClassMapping> getClassMappingChain(ClassEntry obfClass) {
204 List<ClassMapping> mappingChain = Lists.newArrayList();
205 ClassMapping classMapping = null;
206 for (ClassEntry obfClassEntry : obfClass.getClassChain()) {
207 if (mappingChain.isEmpty()) {
208 classMapping = m_classesByObf.get(obfClassEntry.getName());
209 } else if (classMapping != null) {
210 classMapping = classMapping.getInnerClassByObfSimple(obfClassEntry.getInnermostClassName());
211 }
212 mappingChain.add(classMapping);
213 }
214 return mappingChain;
215 }
216}
diff --git a/src/cuchaz/enigma/mapping/MappingsChecker.java b/src/cuchaz/enigma/mapping/MappingsChecker.java
deleted file mode 100644
index b25ea3cf..00000000
--- a/src/cuchaz/enigma/mapping/MappingsChecker.java
+++ /dev/null
@@ -1,107 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.util.Map;
14
15import com.google.common.collect.Lists;
16import com.google.common.collect.Maps;
17
18import cuchaz.enigma.analysis.JarIndex;
19import cuchaz.enigma.analysis.RelatedMethodChecker;
20
21
22public class MappingsChecker {
23
24 private JarIndex m_index;
25 private RelatedMethodChecker m_relatedMethodChecker;
26 private Map<ClassEntry,ClassMapping> m_droppedClassMappings;
27 private Map<ClassEntry,ClassMapping> m_droppedInnerClassMappings;
28 private Map<FieldEntry,FieldMapping> m_droppedFieldMappings;
29 private Map<BehaviorEntry,MethodMapping> m_droppedMethodMappings;
30
31 public MappingsChecker(JarIndex index) {
32 m_index = index;
33 m_relatedMethodChecker = new RelatedMethodChecker(m_index);
34 m_droppedClassMappings = Maps.newHashMap();
35 m_droppedInnerClassMappings = Maps.newHashMap();
36 m_droppedFieldMappings = Maps.newHashMap();
37 m_droppedMethodMappings = Maps.newHashMap();
38 }
39
40 public RelatedMethodChecker getRelatedMethodChecker() {
41 return m_relatedMethodChecker;
42 }
43
44 public Map<ClassEntry,ClassMapping> getDroppedClassMappings() {
45 return m_droppedClassMappings;
46 }
47
48 public Map<ClassEntry,ClassMapping> getDroppedInnerClassMappings() {
49 return m_droppedInnerClassMappings;
50 }
51
52 public Map<FieldEntry,FieldMapping> getDroppedFieldMappings() {
53 return m_droppedFieldMappings;
54 }
55
56 public Map<BehaviorEntry,MethodMapping> getDroppedMethodMappings() {
57 return m_droppedMethodMappings;
58 }
59
60 public void dropBrokenMappings(Mappings mappings) {
61 for (ClassMapping classMapping : Lists.newArrayList(mappings.classes())) {
62 if (!checkClassMapping(classMapping)) {
63 mappings.removeClassMapping(classMapping);
64 m_droppedClassMappings.put(EntryFactory.getObfClassEntry(m_index, classMapping), classMapping);
65 }
66 }
67 }
68
69 private boolean checkClassMapping(ClassMapping classMapping) {
70
71 // check the class
72 ClassEntry classEntry = EntryFactory.getObfClassEntry(m_index, classMapping);
73 if (!m_index.getObfClassEntries().contains(classEntry)) {
74 return false;
75 }
76
77 // check the fields
78 for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) {
79 FieldEntry obfFieldEntry = EntryFactory.getObfFieldEntry(classMapping, fieldMapping);
80 if (!m_index.containsObfField(obfFieldEntry)) {
81 classMapping.removeFieldMapping(fieldMapping);
82 m_droppedFieldMappings.put(obfFieldEntry, fieldMapping);
83 }
84 }
85
86 // check methods
87 for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) {
88 BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping);
89 if (!m_index.containsObfBehavior(obfBehaviorEntry)) {
90 classMapping.removeMethodMapping(methodMapping);
91 m_droppedMethodMappings.put(obfBehaviorEntry, methodMapping);
92 }
93
94 m_relatedMethodChecker.checkMethod(classEntry, methodMapping);
95 }
96
97 // check inner classes
98 for (ClassMapping innerClassMapping : Lists.newArrayList(classMapping.innerClasses())) {
99 if (!checkClassMapping(innerClassMapping)) {
100 classMapping.removeInnerClassMapping(innerClassMapping);
101 m_droppedInnerClassMappings.put(EntryFactory.getObfClassEntry(m_index, innerClassMapping), innerClassMapping);
102 }
103 }
104
105 return true;
106 }
107}
diff --git a/src/cuchaz/enigma/mapping/MappingsReader.java b/src/cuchaz/enigma/mapping/MappingsReader.java
deleted file mode 100644
index 0a4b117e..00000000
--- a/src/cuchaz/enigma/mapping/MappingsReader.java
+++ /dev/null
@@ -1,134 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.BufferedReader;
14import java.io.IOException;
15import java.io.Reader;
16import java.util.Deque;
17
18import com.google.common.collect.Queues;
19
20public class MappingsReader {
21
22 public Mappings read(Reader in)
23 throws IOException, MappingParseException {
24 return read(new BufferedReader(in));
25 }
26
27 public Mappings read(BufferedReader in)
28 throws IOException, MappingParseException {
29 Mappings mappings = new Mappings();
30 Deque<Object> mappingStack = Queues.newArrayDeque();
31
32 int lineNumber = 0;
33 String line = null;
34 while ( (line = in.readLine()) != null) {
35 lineNumber++;
36
37 // strip comments
38 int commentPos = line.indexOf('#');
39 if (commentPos >= 0) {
40 line = line.substring(0, commentPos);
41 }
42
43 // skip blank lines
44 if (line.trim().length() <= 0) {
45 continue;
46 }
47
48 // get the indent of this line
49 int indent = 0;
50 for (int i = 0; i < line.length(); i++) {
51 if (line.charAt(i) != '\t') {
52 break;
53 }
54 indent++;
55 }
56
57 // handle stack pops
58 while (indent < mappingStack.size()) {
59 mappingStack.pop();
60 }
61
62 String[] parts = line.trim().split("\\s");
63 try {
64 // read the first token
65 String token = parts[0];
66
67 if (token.equalsIgnoreCase("CLASS")) {
68 ClassMapping classMapping;
69 if (indent <= 0) {
70 // outer class
71 classMapping = readClass(parts, false);
72 mappings.addClassMapping(classMapping);
73 } else {
74
75 // inner class
76 if (!(mappingStack.peek() instanceof ClassMapping)) {
77 throw new MappingParseException(lineNumber, "Unexpected CLASS entry here!");
78 }
79
80 classMapping = readClass(parts, true);
81 ((ClassMapping)mappingStack.peek()).addInnerClassMapping(classMapping);
82 }
83 mappingStack.push(classMapping);
84 } else if (token.equalsIgnoreCase("FIELD")) {
85 if (mappingStack.isEmpty() || ! (mappingStack.peek() instanceof ClassMapping)) {
86 throw new MappingParseException(lineNumber, "Unexpected FIELD entry here!");
87 }
88 ((ClassMapping)mappingStack.peek()).addFieldMapping(readField(parts));
89 } else if (token.equalsIgnoreCase("METHOD")) {
90 if (mappingStack.isEmpty() || ! (mappingStack.peek() instanceof ClassMapping)) {
91 throw new MappingParseException(lineNumber, "Unexpected METHOD entry here!");
92 }
93 MethodMapping methodMapping = readMethod(parts);
94 ((ClassMapping)mappingStack.peek()).addMethodMapping(methodMapping);
95 mappingStack.push(methodMapping);
96 } else if (token.equalsIgnoreCase("ARG")) {
97 if (mappingStack.isEmpty() || ! (mappingStack.peek() instanceof MethodMapping)) {
98 throw new MappingParseException(lineNumber, "Unexpected ARG entry here!");
99 }
100 ((MethodMapping)mappingStack.peek()).addArgumentMapping(readArgument(parts));
101 }
102 } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) {
103 throw new MappingParseException(lineNumber, "Malformed line:\n" + line);
104 }
105 }
106
107 return mappings;
108 }
109
110 private ArgumentMapping readArgument(String[] parts) {
111 return new ArgumentMapping(Integer.parseInt(parts[1]), parts[2]);
112 }
113
114 private ClassMapping readClass(String[] parts, boolean makeSimple) {
115 if (parts.length == 2) {
116 return new ClassMapping(parts[1]);
117 } else {
118 return new ClassMapping(parts[1], parts[2]);
119 }
120 }
121
122 /* TEMP */
123 protected FieldMapping readField(String[] parts) {
124 return new FieldMapping(parts[1], new Type(parts[3]), parts[2]);
125 }
126
127 private MethodMapping readMethod(String[] parts) {
128 if (parts.length == 3) {
129 return new MethodMapping(parts[1], new Signature(parts[2]));
130 } else {
131 return new MethodMapping(parts[1], new Signature(parts[3]), parts[2]);
132 }
133 }
134}
diff --git a/src/cuchaz/enigma/mapping/MappingsRenamer.java b/src/cuchaz/enigma/mapping/MappingsRenamer.java
deleted file mode 100644
index 47e5738c..00000000
--- a/src/cuchaz/enigma/mapping/MappingsRenamer.java
+++ /dev/null
@@ -1,237 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.IOException;
14import java.io.ObjectOutputStream;
15import java.io.OutputStream;
16import java.util.List;
17import java.util.Set;
18import java.util.zip.GZIPOutputStream;
19
20import cuchaz.enigma.analysis.JarIndex;
21
22public class MappingsRenamer {
23
24 private JarIndex m_index;
25 private Mappings m_mappings;
26
27 public MappingsRenamer(JarIndex index, Mappings mappings) {
28 m_index = index;
29 m_mappings = mappings;
30 }
31
32 public void setClassName(ClassEntry obf, String deobfName) {
33
34 deobfName = NameValidator.validateClassName(deobfName, !obf.isInnerClass());
35
36 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obf);
37 if (mappingChain.size() == 1) {
38
39 if (deobfName != null) {
40 // make sure we don't rename to an existing obf or deobf class
41 if (m_mappings.containsDeobfClass(deobfName) || m_index.containsObfClass(new ClassEntry(deobfName))) {
42 throw new IllegalNameException(deobfName, "There is already a class with that name");
43 }
44 }
45
46 ClassMapping classMapping = mappingChain.get(0);
47 m_mappings.setClassDeobfName(classMapping, deobfName);
48
49 } else {
50
51 ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2);
52
53 if (deobfName != null) {
54 // make sure we don't rename to an existing obf or deobf inner class
55 if (outerClassMapping.hasInnerClassByDeobf(deobfName) || outerClassMapping.hasInnerClassByObfSimple(deobfName)) {
56 throw new IllegalNameException(deobfName, "There is already a class with that name");
57 }
58 }
59
60 outerClassMapping.setInnerClassName(obf, deobfName);
61 }
62 }
63
64 public void removeClassMapping(ClassEntry obf) {
65 setClassName(obf, null);
66 }
67
68 public void markClassAsDeobfuscated(ClassEntry obf) {
69 String deobfName = obf.isInnerClass() ? obf.getInnermostClassName() : obf.getName();
70 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obf);
71 if (mappingChain.size() == 1) {
72 ClassMapping classMapping = mappingChain.get(0);
73 m_mappings.setClassDeobfName(classMapping, deobfName);
74 } else {
75 ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2);
76 outerClassMapping.setInnerClassName(obf, deobfName);
77 }
78 }
79
80 public void setFieldName(FieldEntry obf, String deobfName) {
81 deobfName = NameValidator.validateFieldName(deobfName);
82 FieldEntry targetEntry = new FieldEntry(obf.getClassEntry(), deobfName, obf.getType());
83 if (m_mappings.containsDeobfField(obf.getClassEntry(), deobfName, obf.getType()) || m_index.containsObfField(targetEntry)) {
84 throw new IllegalNameException(deobfName, "There is already a field with that name");
85 }
86
87 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
88 classMapping.setFieldName(obf.getName(), obf.getType(), deobfName);
89 }
90
91 public void removeFieldMapping(FieldEntry obf) {
92 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
93 classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getType()));
94 }
95
96 public void markFieldAsDeobfuscated(FieldEntry obf) {
97 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
98 classMapping.setFieldName(obf.getName(), obf.getType(), obf.getName());
99 }
100
101 public void setMethodTreeName(MethodEntry obf, String deobfName) {
102 Set<MethodEntry> implementations = m_index.getRelatedMethodImplementations(obf);
103
104 deobfName = NameValidator.validateMethodName(deobfName);
105 for (MethodEntry entry : implementations) {
106 Signature deobfSignature = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateSignature(obf.getSignature());
107 MethodEntry targetEntry = new MethodEntry(entry.getClassEntry(), deobfName, deobfSignature);
108 if (m_mappings.containsDeobfMethod(entry.getClassEntry(), deobfName, entry.getSignature()) || m_index.containsObfBehavior(targetEntry)) {
109 String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(entry.getClassName());
110 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName);
111 }
112 }
113
114 for (MethodEntry entry : implementations) {
115 setMethodName(entry, deobfName);
116 }
117 }
118
119 public void setMethodName(MethodEntry obf, String deobfName) {
120 deobfName = NameValidator.validateMethodName(deobfName);
121 MethodEntry targetEntry = new MethodEntry(obf.getClassEntry(), deobfName, obf.getSignature());
122 if (m_mappings.containsDeobfMethod(obf.getClassEntry(), deobfName, obf.getSignature()) || m_index.containsObfBehavior(targetEntry)) {
123 String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(obf.getClassName());
124 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName);
125 }
126
127 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
128 classMapping.setMethodName(obf.getName(), obf.getSignature(), deobfName);
129 }
130
131 public void removeMethodTreeMapping(MethodEntry obf) {
132 for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) {
133 removeMethodMapping(implementation);
134 }
135 }
136
137 public void removeMethodMapping(MethodEntry obf) {
138 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
139 classMapping.setMethodName(obf.getName(), obf.getSignature(), null);
140 }
141
142 public void markMethodTreeAsDeobfuscated(MethodEntry obf) {
143 for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) {
144 markMethodAsDeobfuscated(implementation);
145 }
146 }
147
148 public void markMethodAsDeobfuscated(MethodEntry obf) {
149 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
150 classMapping.setMethodName(obf.getName(), obf.getSignature(), obf.getName());
151 }
152
153 public void setArgumentName(ArgumentEntry obf, String deobfName) {
154 deobfName = NameValidator.validateArgumentName(deobfName);
155 // NOTE: don't need to check arguments for name collisions with names determined by Procyon
156 if (m_mappings.containsArgument(obf.getBehaviorEntry(), deobfName)) {
157 throw new IllegalNameException(deobfName, "There is already an argument with that name");
158 }
159
160 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
161 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), deobfName);
162 }
163
164 public void removeArgumentMapping(ArgumentEntry obf) {
165 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
166 classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex());
167 }
168
169 public void markArgumentAsDeobfuscated(ArgumentEntry obf) {
170 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
171 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), obf.getName());
172 }
173
174 public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) {
175 classMapping.removeFieldMapping(fieldMapping);
176 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
177 if (!targetClassMapping.containsObfField(fieldMapping.getObfName(), fieldMapping.getObfType())) {
178 if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfType())) {
179 targetClassMapping.addFieldMapping(fieldMapping);
180 return true;
181 } else {
182 System.err.println("WARNING: deobf field was already there: " + obfClass + "." + fieldMapping.getDeobfName());
183 }
184 }
185 return false;
186 }
187
188 public boolean moveMethodToObfClass(ClassMapping classMapping, MethodMapping methodMapping, ClassEntry obfClass) {
189 classMapping.removeMethodMapping(methodMapping);
190 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
191 if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfSignature())) {
192 if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfSignature())) {
193 targetClassMapping.addMethodMapping(methodMapping);
194 return true;
195 } else {
196 System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfSignature());
197 }
198 }
199 return false;
200 }
201
202 public void write(OutputStream out) throws IOException {
203 // TEMP: just use the object output for now. We can find a more efficient storage format later
204 GZIPOutputStream gzipout = new GZIPOutputStream(out);
205 ObjectOutputStream oout = new ObjectOutputStream(gzipout);
206 oout.writeObject(this);
207 gzipout.finish();
208 }
209
210 private ClassMapping getOrCreateClassMapping(ClassEntry obfClassEntry) {
211 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obfClassEntry);
212 return mappingChain.get(mappingChain.size() - 1);
213 }
214
215 private List<ClassMapping> getOrCreateClassMappingChain(ClassEntry obfClassEntry) {
216 List<ClassEntry> classChain = obfClassEntry.getClassChain();
217 List<ClassMapping> mappingChain = m_mappings.getClassMappingChain(obfClassEntry);
218 for (int i=0; i<classChain.size(); i++) {
219 ClassEntry classEntry = classChain.get(i);
220 ClassMapping classMapping = mappingChain.get(i);
221 if (classMapping == null) {
222
223 // create it
224 classMapping = new ClassMapping(classEntry.getName());
225 mappingChain.set(i, classMapping);
226
227 // add it to the right parent
228 if (i == 0) {
229 m_mappings.addClassMapping(classMapping);
230 } else {
231 mappingChain.get(i-1).addInnerClassMapping(classMapping);
232 }
233 }
234 }
235 return mappingChain;
236 }
237}
diff --git a/src/cuchaz/enigma/mapping/MappingsWriter.java b/src/cuchaz/enigma/mapping/MappingsWriter.java
deleted file mode 100644
index 1ebefefa..00000000
--- a/src/cuchaz/enigma/mapping/MappingsWriter.java
+++ /dev/null
@@ -1,88 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.IOException;
14import java.io.PrintWriter;
15import java.io.Writer;
16import java.util.ArrayList;
17import java.util.Collections;
18import java.util.List;
19
20public class MappingsWriter {
21
22 public void write(Writer out, Mappings mappings) throws IOException {
23 write(new PrintWriter(out), mappings);
24 }
25
26 public void write(PrintWriter out, Mappings mappings) throws IOException {
27 for (ClassMapping classMapping : sorted(mappings.classes())) {
28 write(out, classMapping, 0);
29 }
30 }
31
32 private void write(PrintWriter out, ClassMapping classMapping, int depth) throws IOException {
33 if (classMapping.getDeobfName() == null) {
34 out.format("%sCLASS %s\n", getIndent(depth), classMapping.getObfFullName());
35 } else {
36 out.format("%sCLASS %s %s\n", getIndent(depth), classMapping.getObfFullName(), classMapping.getDeobfName());
37 }
38
39 for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) {
40 write(out, innerClassMapping, depth + 1);
41 }
42
43 for (FieldMapping fieldMapping : sorted(classMapping.fields())) {
44 write(out, fieldMapping, depth + 1);
45 }
46
47 for (MethodMapping methodMapping : sorted(classMapping.methods())) {
48 write(out, methodMapping, depth + 1);
49 }
50 }
51
52 private void write(PrintWriter out, FieldMapping fieldMapping, int depth) throws IOException {
53 out.format("%sFIELD %s %s %s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName(), fieldMapping.getObfType().toString());
54 }
55
56 private void write(PrintWriter out, MethodMapping methodMapping, int depth) throws IOException {
57 if (methodMapping.getDeobfName() == null) {
58 out.format("%sMETHOD %s %s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfSignature());
59 } else {
60 out.format("%sMETHOD %s %s %s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfSignature());
61 }
62
63 for (ArgumentMapping argumentMapping : sorted(methodMapping.arguments())) {
64 write(out, argumentMapping, depth + 1);
65 }
66 }
67
68 private void write(PrintWriter out, ArgumentMapping argumentMapping, int depth) throws IOException {
69 out.format("%sARG %d %s\n", getIndent(depth), argumentMapping.getIndex(), argumentMapping.getName());
70 }
71
72 private <T extends Comparable<T>> List<T> sorted(Iterable<T> classes) {
73 List<T> out = new ArrayList<T>();
74 for (T t : classes) {
75 out.add(t);
76 }
77 Collections.sort(out);
78 return out;
79 }
80
81 private String getIndent(int depth) {
82 StringBuilder buf = new StringBuilder();
83 for (int i = 0; i < depth; i++) {
84 buf.append("\t");
85 }
86 return buf.toString();
87 }
88}
diff --git a/src/cuchaz/enigma/mapping/MethodEntry.java b/src/cuchaz/enigma/mapping/MethodEntry.java
deleted file mode 100644
index eb9e2043..00000000
--- a/src/cuchaz/enigma/mapping/MethodEntry.java
+++ /dev/null
@@ -1,104 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class MethodEntry implements BehaviorEntry, Serializable {
18
19 private static final long serialVersionUID = 4770915224467247458L;
20
21 private ClassEntry m_classEntry;
22 private String m_name;
23 private Signature m_signature;
24
25 public MethodEntry(ClassEntry classEntry, String name, Signature signature) {
26 if (classEntry == null) {
27 throw new IllegalArgumentException("Class cannot be null!");
28 }
29 if (name == null) {
30 throw new IllegalArgumentException("Method name cannot be null!");
31 }
32 if (signature == null) {
33 throw new IllegalArgumentException("Method signature cannot be null!");
34 }
35 if (name.startsWith("<")) {
36 throw new IllegalArgumentException("Don't use MethodEntry for a constructor!");
37 }
38
39 m_classEntry = classEntry;
40 m_name = name;
41 m_signature = signature;
42 }
43
44 public MethodEntry(MethodEntry other) {
45 m_classEntry = new ClassEntry(other.m_classEntry);
46 m_name = other.m_name;
47 m_signature = other.m_signature;
48 }
49
50 public MethodEntry(MethodEntry other, String newClassName) {
51 m_classEntry = new ClassEntry(newClassName);
52 m_name = other.m_name;
53 m_signature = other.m_signature;
54 }
55
56 @Override
57 public ClassEntry getClassEntry() {
58 return m_classEntry;
59 }
60
61 @Override
62 public String getName() {
63 return m_name;
64 }
65
66 @Override
67 public Signature getSignature() {
68 return m_signature;
69 }
70
71 @Override
72 public String getClassName() {
73 return m_classEntry.getName();
74 }
75
76 @Override
77 public MethodEntry cloneToNewClass(ClassEntry classEntry) {
78 return new MethodEntry(this, classEntry.getName());
79 }
80
81 @Override
82 public int hashCode() {
83 return Util.combineHashesOrdered(m_classEntry, m_name, m_signature);
84 }
85
86 @Override
87 public boolean equals(Object other) {
88 if (other instanceof MethodEntry) {
89 return equals((MethodEntry)other);
90 }
91 return false;
92 }
93
94 public boolean equals(MethodEntry other) {
95 return m_classEntry.equals(other.m_classEntry)
96 && m_name.equals(other.m_name)
97 && m_signature.equals(other.m_signature);
98 }
99
100 @Override
101 public String toString() {
102 return m_classEntry.getName() + "." + m_name + m_signature;
103 }
104}
diff --git a/src/cuchaz/enigma/mapping/MethodMapping.java b/src/cuchaz/enigma/mapping/MethodMapping.java
deleted file mode 100644
index 055e1fe1..00000000
--- a/src/cuchaz/enigma/mapping/MethodMapping.java
+++ /dev/null
@@ -1,191 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.Map;
15import java.util.Map.Entry;
16
17import com.google.common.collect.Maps;
18
19public class MethodMapping implements Serializable, Comparable<MethodMapping>, MemberMapping<BehaviorEntry> {
20
21 private static final long serialVersionUID = -4409570216084263978L;
22
23 private String m_obfName;
24 private String m_deobfName;
25 private Signature m_obfSignature;
26 private Map<Integer,ArgumentMapping> m_arguments;
27
28 public MethodMapping(String obfName, Signature obfSignature) {
29 this(obfName, obfSignature, null);
30 }
31
32 public MethodMapping(String obfName, Signature obfSignature, String deobfName) {
33 if (obfName == null) {
34 throw new IllegalArgumentException("obf name cannot be null!");
35 }
36 if (obfSignature == null) {
37 throw new IllegalArgumentException("obf signature cannot be null!");
38 }
39 m_obfName = obfName;
40 m_deobfName = NameValidator.validateMethodName(deobfName);
41 m_obfSignature = obfSignature;
42 m_arguments = Maps.newTreeMap();
43 }
44
45 public MethodMapping(MethodMapping other, ClassNameReplacer obfClassNameReplacer) {
46 m_obfName = other.m_obfName;
47 m_deobfName = other.m_deobfName;
48 m_obfSignature = new Signature(other.m_obfSignature, obfClassNameReplacer);
49 m_arguments = Maps.newTreeMap();
50 for (Entry<Integer,ArgumentMapping> entry : other.m_arguments.entrySet()) {
51 m_arguments.put(entry.getKey(), new ArgumentMapping(entry.getValue()));
52 }
53 }
54
55 @Override
56 public String getObfName() {
57 return m_obfName;
58 }
59
60 public void setObfName(String val) {
61 m_obfName = NameValidator.validateMethodName(val);
62 }
63
64 public String getDeobfName() {
65 return m_deobfName;
66 }
67
68 public void setDeobfName(String val) {
69 m_deobfName = NameValidator.validateMethodName(val);
70 }
71
72 public Signature getObfSignature() {
73 return m_obfSignature;
74 }
75
76 public void setObfSignature(Signature val) {
77 m_obfSignature = val;
78 }
79
80 public Iterable<ArgumentMapping> arguments() {
81 return m_arguments.values();
82 }
83
84 public boolean isConstructor() {
85 return m_obfName.startsWith("<");
86 }
87
88 public void addArgumentMapping(ArgumentMapping argumentMapping) {
89 boolean wasAdded = m_arguments.put(argumentMapping.getIndex(), argumentMapping) == null;
90 assert (wasAdded);
91 }
92
93 public String getObfArgumentName(int index) {
94 ArgumentMapping argumentMapping = m_arguments.get(index);
95 if (argumentMapping != null) {
96 return argumentMapping.getName();
97 }
98
99 return null;
100 }
101
102 public String getDeobfArgumentName(int index) {
103 ArgumentMapping argumentMapping = m_arguments.get(index);
104 if (argumentMapping != null) {
105 return argumentMapping.getName();
106 }
107
108 return null;
109 }
110
111 public void setArgumentName(int index, String name) {
112 ArgumentMapping argumentMapping = m_arguments.get(index);
113 if (argumentMapping == null) {
114 argumentMapping = new ArgumentMapping(index, name);
115 boolean wasAdded = m_arguments.put(index, argumentMapping) == null;
116 assert (wasAdded);
117 } else {
118 argumentMapping.setName(name);
119 }
120 }
121
122 public void removeArgumentName(int index) {
123 boolean wasRemoved = m_arguments.remove(index) != null;
124 assert (wasRemoved);
125 }
126
127 @Override
128 public String toString() {
129 StringBuilder buf = new StringBuilder();
130 buf.append("\t");
131 buf.append(m_obfName);
132 buf.append(" <-> ");
133 buf.append(m_deobfName);
134 buf.append("\n");
135 buf.append("\t");
136 buf.append(m_obfSignature);
137 buf.append("\n");
138 buf.append("\tArguments:\n");
139 for (ArgumentMapping argumentMapping : m_arguments.values()) {
140 buf.append("\t\t");
141 buf.append(argumentMapping.getIndex());
142 buf.append(" -> ");
143 buf.append(argumentMapping.getName());
144 buf.append("\n");
145 }
146 return buf.toString();
147 }
148
149 @Override
150 public int compareTo(MethodMapping other) {
151 return (m_obfName + m_obfSignature).compareTo(other.m_obfName + other.m_obfSignature);
152 }
153
154 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
155
156 // rename obf classes in the signature
157 Signature newSignature = new Signature(m_obfSignature, new ClassNameReplacer() {
158 @Override
159 public String replace(String className) {
160 if (className.equals(oldObfClassName)) {
161 return newObfClassName;
162 }
163 return null;
164 }
165 });
166
167 if (!newSignature.equals(m_obfSignature)) {
168 m_obfSignature = newSignature;
169 return true;
170 }
171 return false;
172 }
173
174 public boolean containsArgument(String name) {
175 for (ArgumentMapping argumentMapping : m_arguments.values()) {
176 if (argumentMapping.getName().equals(name)) {
177 return true;
178 }
179 }
180 return false;
181 }
182
183 @Override
184 public BehaviorEntry getObfEntry(ClassEntry classEntry) {
185 if (isConstructor()) {
186 return new ConstructorEntry(classEntry, m_obfSignature);
187 } else {
188 return new MethodEntry(classEntry, m_obfName, m_obfSignature);
189 }
190 }
191}
diff --git a/src/cuchaz/enigma/mapping/NameValidator.java b/src/cuchaz/enigma/mapping/NameValidator.java
deleted file mode 100644
index 12520e12..00000000
--- a/src/cuchaz/enigma/mapping/NameValidator.java
+++ /dev/null
@@ -1,80 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.util.Arrays;
14import java.util.List;
15import java.util.regex.Pattern;
16
17import javassist.bytecode.Descriptor;
18
19public class NameValidator {
20
21 private static final Pattern IdentifierPattern;
22 private static final Pattern ClassPattern;
23 private static final List<String> ReservedWords = Arrays.asList(
24 "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized",
25 "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte",
26 "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch",
27 "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally",
28 "long", "strictfp", "volatile", "const", "float", "native", "super", "while"
29 );
30
31 static {
32
33 // java allows all kinds of weird characters...
34 StringBuilder startChars = new StringBuilder();
35 StringBuilder partChars = new StringBuilder();
36 for (int i = Character.MIN_CODE_POINT; i <= Character.MAX_CODE_POINT; i++) {
37 if (Character.isJavaIdentifierStart(i)) {
38 startChars.appendCodePoint(i);
39 }
40 if (Character.isJavaIdentifierPart(i)) {
41 partChars.appendCodePoint(i);
42 }
43 }
44
45 String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*";
46 IdentifierPattern = Pattern.compile(identifierRegex);
47 ClassPattern = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex));
48 }
49
50 public static String validateClassName(String name, boolean packageRequired) {
51 if (name == null) {
52 return null;
53 }
54 if (!ClassPattern.matcher(name).matches() || ReservedWords.contains(name)) {
55 throw new IllegalNameException(name, "This doesn't look like a legal class name");
56 }
57 if (packageRequired && new ClassEntry(name).getPackageName() == null) {
58 throw new IllegalNameException(name, "Class must be in a package");
59 }
60 return Descriptor.toJvmName(name);
61 }
62
63 public static String validateFieldName(String name) {
64 if (name == null) {
65 return null;
66 }
67 if (!IdentifierPattern.matcher(name).matches() || ReservedWords.contains(name)) {
68 throw new IllegalNameException(name, "This doesn't look like a legal identifier");
69 }
70 return name;
71 }
72
73 public static String validateMethodName(String name) {
74 return validateFieldName(name);
75 }
76
77 public static String validateArgumentName(String name) {
78 return validateFieldName(name);
79 }
80}
diff --git a/src/cuchaz/enigma/mapping/ProcyonEntryFactory.java b/src/cuchaz/enigma/mapping/ProcyonEntryFactory.java
deleted file mode 100644
index 777a12e4..00000000
--- a/src/cuchaz/enigma/mapping/ProcyonEntryFactory.java
+++ /dev/null
@@ -1,55 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import com.strobel.assembler.metadata.FieldDefinition;
14import com.strobel.assembler.metadata.MethodDefinition;
15
16
17public class ProcyonEntryFactory {
18
19 public static FieldEntry getFieldEntry(FieldDefinition def) {
20 return new FieldEntry(
21 new ClassEntry(def.getDeclaringType().getInternalName()),
22 def.getName(),
23 new Type(def.getErasedSignature())
24 );
25 }
26
27 public static MethodEntry getMethodEntry(MethodDefinition def) {
28 return new MethodEntry(
29 new ClassEntry(def.getDeclaringType().getInternalName()),
30 def.getName(),
31 new Signature(def.getErasedSignature())
32 );
33 }
34
35 public static ConstructorEntry getConstructorEntry(MethodDefinition def) {
36 if (def.isTypeInitializer()) {
37 return new ConstructorEntry(
38 new ClassEntry(def.getDeclaringType().getInternalName())
39 );
40 } else {
41 return new ConstructorEntry(
42 new ClassEntry(def.getDeclaringType().getInternalName()),
43 new Signature(def.getErasedSignature())
44 );
45 }
46 }
47
48 public static BehaviorEntry getBehaviorEntry(MethodDefinition def) {
49 if (def.isConstructor() || def.isTypeInitializer()) {
50 return getConstructorEntry(def);
51 } else {
52 return getMethodEntry(def);
53 }
54 }
55}
diff --git a/src/cuchaz/enigma/mapping/Signature.java b/src/cuchaz/enigma/mapping/Signature.java
deleted file mode 100644
index 8f2b6b2e..00000000
--- a/src/cuchaz/enigma/mapping/Signature.java
+++ /dev/null
@@ -1,117 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.List;
15
16import com.google.common.collect.Lists;
17
18import cuchaz.enigma.Util;
19
20public class Signature implements Serializable {
21
22 private static final long serialVersionUID = -5843719505729497539L;
23
24 private List<Type> m_argumentTypes;
25 private Type m_returnType;
26
27 public Signature(String signature) {
28 try {
29 m_argumentTypes = Lists.newArrayList();
30 int i=0;
31 while (i<signature.length()) {
32 char c = signature.charAt(i);
33 if (c == '(') {
34 assert(m_argumentTypes.isEmpty());
35 assert(m_returnType == null);
36 i++;
37 } else if (c == ')') {
38 i++;
39 break;
40 } else {
41 String type = Type.parseFirst(signature.substring(i));
42 m_argumentTypes.add(new Type(type));
43 i += type.length();
44 }
45 }
46 m_returnType = new Type(Type.parseFirst(signature.substring(i)));
47 } catch (Exception ex) {
48 throw new IllegalArgumentException("Unable to parse signature: " + signature, ex);
49 }
50 }
51
52 public Signature(Signature other) {
53 m_argumentTypes = Lists.newArrayList(other.m_argumentTypes);
54 m_returnType = new Type(other.m_returnType);
55 }
56
57 public Signature(Signature other, ClassNameReplacer replacer) {
58 m_argumentTypes = Lists.newArrayList(other.m_argumentTypes);
59 for (int i=0; i<m_argumentTypes.size(); i++) {
60 m_argumentTypes.set(i, new Type(m_argumentTypes.get(i), replacer));
61 }
62 m_returnType = new Type(other.m_returnType, replacer);
63 }
64
65 public List<Type> getArgumentTypes() {
66 return m_argumentTypes;
67 }
68
69 public Type getReturnType() {
70 return m_returnType;
71 }
72
73 @Override
74 public String toString() {
75 StringBuilder buf = new StringBuilder();
76 buf.append("(");
77 for (Type type : m_argumentTypes) {
78 buf.append(type.toString());
79 }
80 buf.append(")");
81 buf.append(m_returnType.toString());
82 return buf.toString();
83 }
84
85 public Iterable<Type> types() {
86 List<Type> types = Lists.newArrayList();
87 types.addAll(m_argumentTypes);
88 types.add(m_returnType);
89 return types;
90 }
91
92 @Override
93 public boolean equals(Object other) {
94 if (other instanceof Signature) {
95 return equals((Signature)other);
96 }
97 return false;
98 }
99
100 public boolean equals(Signature other) {
101 return m_argumentTypes.equals(other.m_argumentTypes) && m_returnType.equals(other.m_returnType);
102 }
103
104 @Override
105 public int hashCode() {
106 return Util.combineHashesOrdered(m_argumentTypes.hashCode(), m_returnType.hashCode());
107 }
108
109 public boolean hasClass(ClassEntry classEntry) {
110 for (Type type : types()) {
111 if (type.hasClass() && type.getClassEntry().equals(classEntry)) {
112 return true;
113 }
114 }
115 return false;
116 }
117}
diff --git a/src/cuchaz/enigma/mapping/SignatureUpdater.java b/src/cuchaz/enigma/mapping/SignatureUpdater.java
deleted file mode 100644
index eb53233e..00000000
--- a/src/cuchaz/enigma/mapping/SignatureUpdater.java
+++ /dev/null
@@ -1,94 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.IOException;
14import java.io.StringReader;
15import java.util.List;
16
17import com.google.common.collect.Lists;
18
19public class SignatureUpdater {
20
21 public interface ClassNameUpdater {
22 String update(String className);
23 }
24
25 public static String update(String signature, ClassNameUpdater updater) {
26 try {
27 StringBuilder buf = new StringBuilder();
28
29 // read the signature character-by-character
30 StringReader reader = new StringReader(signature);
31 int i = -1;
32 while ( (i = reader.read()) != -1) {
33 char c = (char)i;
34
35 // does this character start a class name?
36 if (c == 'L') {
37 // update the class name and add it to the buffer
38 buf.append('L');
39 String className = readClass(reader);
40 if (className == null) {
41 throw new IllegalArgumentException("Malformed signature: " + signature);
42 }
43 buf.append(updater.update(className));
44 buf.append(';');
45 } else {
46 // copy the character into the buffer
47 buf.append(c);
48 }
49 }
50
51 return buf.toString();
52 } catch (IOException ex) {
53 // I'm pretty sure a StringReader will never throw one of these
54 throw new Error(ex);
55 }
56 }
57
58 private static String readClass(StringReader reader) throws IOException {
59 // read all the characters in the buffer until we hit a ';'
60 // remember to treat generics correctly
61 StringBuilder buf = new StringBuilder();
62 int depth = 0;
63 int i = -1;
64 while ( (i = reader.read()) != -1) {
65 char c = (char)i;
66
67 if (c == '<') {
68 depth++;
69 } else if (c == '>') {
70 depth--;
71 } else if (depth == 0) {
72 if (c == ';') {
73 return buf.toString();
74 } else {
75 buf.append(c);
76 }
77 }
78 }
79
80 return null;
81 }
82
83 public static List<String> getClasses(String signature) {
84 final List<String> classNames = Lists.newArrayList();
85 update(signature, new ClassNameUpdater() {
86 @Override
87 public String update(String className) {
88 classNames.add(className);
89 return className;
90 }
91 });
92 return classNames;
93 }
94}
diff --git a/src/cuchaz/enigma/mapping/Translator.java b/src/cuchaz/enigma/mapping/Translator.java
deleted file mode 100644
index 41c7d7cc..00000000
--- a/src/cuchaz/enigma/mapping/Translator.java
+++ /dev/null
@@ -1,289 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.util.List;
14import java.util.Map;
15
16import com.google.common.collect.Lists;
17import com.google.common.collect.Maps;
18
19import cuchaz.enigma.analysis.TranslationIndex;
20
21public class Translator {
22
23 private TranslationDirection m_direction;
24 private Map<String,ClassMapping> m_classes;
25 private TranslationIndex m_index;
26
27 private ClassNameReplacer m_classNameReplacer = new ClassNameReplacer() {
28 @Override
29 public String replace(String className) {
30 return translateEntry(new ClassEntry(className)).getName();
31 }
32 };
33
34 public Translator() {
35 m_direction = null;
36 m_classes = Maps.newHashMap();
37 m_index = new TranslationIndex();
38 }
39
40 public Translator(TranslationDirection direction, Map<String,ClassMapping> classes, TranslationIndex index) {
41 m_direction = direction;
42 m_classes = classes;
43 m_index = index;
44 }
45
46 public TranslationDirection getDirection() {
47 return m_direction;
48 }
49
50 public TranslationIndex getTranslationIndex() {
51 return m_index;
52 }
53
54 @SuppressWarnings("unchecked")
55 public <T extends Entry> T translateEntry(T entry) {
56 if (entry instanceof ClassEntry) {
57 return (T)translateEntry((ClassEntry)entry);
58 } else if (entry instanceof FieldEntry) {
59 return (T)translateEntry((FieldEntry)entry);
60 } else if (entry instanceof MethodEntry) {
61 return (T)translateEntry((MethodEntry)entry);
62 } else if (entry instanceof ConstructorEntry) {
63 return (T)translateEntry((ConstructorEntry)entry);
64 } else if (entry instanceof ArgumentEntry) {
65 return (T)translateEntry((ArgumentEntry)entry);
66 } else {
67 throw new Error("Unknown entry type: " + entry.getClass().getName());
68 }
69 }
70
71 public <T extends Entry> String translate(T entry) {
72 if (entry instanceof ClassEntry) {
73 return translate((ClassEntry)entry);
74 } else if (entry instanceof FieldEntry) {
75 return translate((FieldEntry)entry);
76 } else if (entry instanceof MethodEntry) {
77 return translate((MethodEntry)entry);
78 } else if (entry instanceof ConstructorEntry) {
79 return translate((ConstructorEntry)entry);
80 } else if (entry instanceof ArgumentEntry) {
81 return translate((ArgumentEntry)entry);
82 } else {
83 throw new Error("Unknown entry type: " + entry.getClass().getName());
84 }
85 }
86
87 public String translate(ClassEntry in) {
88 ClassEntry translated = translateEntry(in);
89 if (translated.equals(in)) {
90 return null;
91 }
92 return translated.getName();
93 }
94
95 public String translateClass(String className) {
96 return translate(new ClassEntry(className));
97 }
98
99 public ClassEntry translateEntry(ClassEntry in) {
100
101 if (in.isInnerClass()) {
102
103 // translate as much of the class chain as we can
104 List<ClassMapping> mappingsChain = getClassMappingChain(in);
105 String[] obfClassNames = in.getName().split("\\$");
106 StringBuilder buf = new StringBuilder();
107 for (int i=0; i<obfClassNames.length; i++) {
108 boolean isFirstClass = buf.length() == 0;
109 String className = null;
110 ClassMapping classMapping = mappingsChain.get(i);
111 if (classMapping != null) {
112 className = m_direction.choose(
113 classMapping.getDeobfName(),
114 isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName()
115 );
116 }
117 if (className == null) {
118 className = obfClassNames[i];
119 }
120 if (!isFirstClass) {
121 buf.append("$");
122 }
123 buf.append(className);
124 }
125 return new ClassEntry(buf.toString());
126
127 } else {
128
129 // normal classes are easy
130 ClassMapping classMapping = m_classes.get(in.getName());
131 if (classMapping == null) {
132 return in;
133 }
134 return m_direction.choose(
135 classMapping.getDeobfName() != null ? new ClassEntry(classMapping.getDeobfName()) : in,
136 new ClassEntry(classMapping.getObfFullName())
137 );
138 }
139 }
140
141 public String translate(FieldEntry in) {
142
143 // resolve the class entry
144 ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in);
145 if (resolvedClassEntry != null) {
146
147 // look for the class
148 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
149 if (classMapping != null) {
150
151 // look for the field
152 String translatedName = m_direction.choose(
153 classMapping.getDeobfFieldName(in.getName(), in.getType()),
154 classMapping.getObfFieldName(in.getName(), translateType(in.getType()))
155 );
156 if (translatedName != null) {
157 return translatedName;
158 }
159 }
160 }
161 return null;
162 }
163
164 public FieldEntry translateEntry(FieldEntry in) {
165 String name = translate(in);
166 if (name == null) {
167 name = in.getName();
168 }
169 return new FieldEntry(translateEntry(in.getClassEntry()), name, translateType(in.getType()));
170 }
171
172 public String translate(MethodEntry in) {
173
174 // resolve the class entry
175 ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in);
176 if (resolvedClassEntry != null) {
177
178 // look for class
179 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
180 if (classMapping != null) {
181
182 // look for the method
183 MethodMapping methodMapping = m_direction.choose(
184 classMapping.getMethodByObf(in.getName(), in.getSignature()),
185 classMapping.getMethodByDeobf(in.getName(), translateSignature(in.getSignature()))
186 );
187 if (methodMapping != null) {
188 return m_direction.choose(methodMapping.getDeobfName(), methodMapping.getObfName());
189 }
190 }
191 }
192 return null;
193 }
194
195 public MethodEntry translateEntry(MethodEntry in) {
196 String name = translate(in);
197 if (name == null) {
198 name = in.getName();
199 }
200 return new MethodEntry(translateEntry(in.getClassEntry()), name, translateSignature(in.getSignature()));
201 }
202
203 public ConstructorEntry translateEntry(ConstructorEntry in) {
204 if (in.isStatic()) {
205 return new ConstructorEntry(translateEntry(in.getClassEntry()));
206 } else {
207 return new ConstructorEntry(translateEntry(in.getClassEntry()), translateSignature(in.getSignature()));
208 }
209 }
210
211 public BehaviorEntry translateEntry(BehaviorEntry in) {
212 if (in instanceof MethodEntry) {
213 return translateEntry((MethodEntry)in);
214 } else if (in instanceof ConstructorEntry) {
215 return translateEntry((ConstructorEntry)in);
216 }
217 throw new Error("Wrong entry type!");
218 }
219
220 public String translate(ArgumentEntry in) {
221
222 // look for the class
223 ClassMapping classMapping = findClassMapping(in.getClassEntry());
224 if (classMapping != null) {
225
226 // look for the method
227 MethodMapping methodMapping = m_direction.choose(
228 classMapping.getMethodByObf(in.getMethodName(), in.getMethodSignature()),
229 classMapping.getMethodByDeobf(in.getMethodName(), translateSignature(in.getMethodSignature()))
230 );
231 if (methodMapping != null) {
232 return m_direction.choose(
233 methodMapping.getDeobfArgumentName(in.getIndex()),
234 methodMapping.getObfArgumentName(in.getIndex())
235 );
236 }
237 }
238 return null;
239 }
240
241 public ArgumentEntry translateEntry(ArgumentEntry in) {
242 String name = translate(in);
243 if (name == null) {
244 name = in.getName();
245 }
246 return new ArgumentEntry(translateEntry(in.getBehaviorEntry()), in.getIndex(), name);
247 }
248
249 public Type translateType(Type type) {
250 return new Type(type, m_classNameReplacer);
251 }
252
253 public Signature translateSignature(Signature signature) {
254 return new Signature(signature, m_classNameReplacer);
255 }
256
257 private ClassMapping findClassMapping(ClassEntry in) {
258 List<ClassMapping> mappingChain = getClassMappingChain(in);
259 return mappingChain.get(mappingChain.size() - 1);
260 }
261
262 private List<ClassMapping> getClassMappingChain(ClassEntry in) {
263
264 // get a list of all the classes in the hierarchy
265 String[] parts = in.getName().split("\\$");
266 List<ClassMapping> mappingsChain = Lists.newArrayList();
267
268 // get mappings for the outer class
269 ClassMapping outerClassMapping = m_classes.get(parts[0]);
270 mappingsChain.add(outerClassMapping);
271
272 for (int i=1; i<parts.length; i++) {
273
274 // get mappings for the inner class
275 ClassMapping innerClassMapping = null;
276 if (outerClassMapping != null) {
277 innerClassMapping = m_direction.choose(
278 outerClassMapping.getInnerClassByObfSimple(parts[i]),
279 outerClassMapping.getInnerClassByDeobfThenObfSimple(parts[i])
280 );
281 }
282 mappingsChain.add(innerClassMapping);
283 outerClassMapping = innerClassMapping;
284 }
285
286 assert(mappingsChain.size() == parts.length);
287 return mappingsChain;
288 }
289}
diff --git a/src/cuchaz/enigma/mapping/Type.java b/src/cuchaz/enigma/mapping/Type.java
deleted file mode 100644
index f86a5ccc..00000000
--- a/src/cuchaz/enigma/mapping/Type.java
+++ /dev/null
@@ -1,247 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.Map;
15
16import com.google.common.collect.Maps;
17
18public class Type implements Serializable {
19
20 private static final long serialVersionUID = 7862257669347104063L;
21
22 public enum Primitive {
23 Byte('B'),
24 Character('C'),
25 Short('S'),
26 Integer('I'),
27 Long('J'),
28 Float('F'),
29 Double('D'),
30 Boolean('Z');
31
32 private static final Map<Character,Primitive> m_lookup;
33
34 static {
35 m_lookup = Maps.newTreeMap();
36 for (Primitive val : values()) {
37 m_lookup.put(val.getCode(), val);
38 }
39 }
40
41 public static Primitive get(char code) {
42 return m_lookup.get(code);
43 }
44
45 private char m_code;
46
47 private Primitive(char code) {
48 m_code = code;
49 }
50
51 public char getCode() {
52 return m_code;
53 }
54 }
55
56 public static String parseFirst(String in) {
57
58 if (in == null || in.length() <= 0) {
59 throw new IllegalArgumentException("No type to parse, input is empty!");
60 }
61
62 // read one type from the input
63
64 char c = in.charAt(0);
65
66 // first check for void
67 if (c == 'V') {
68 return "V";
69 }
70
71 // then check for primitives
72 Primitive primitive = Primitive.get(c);
73 if (primitive != null) {
74 return in.substring(0, 1);
75 }
76
77 // then check for classes
78 if (c == 'L') {
79 return readClass(in);
80 }
81
82 // then check for templates
83 if (c == 'T') {
84 return readClass(in);
85 }
86
87 // then check for arrays
88 int dim = countArrayDimension(in);
89 if (dim > 0) {
90 String arrayType = Type.parseFirst(in.substring(dim));
91 return in.substring(0, dim + arrayType.length());
92 }
93
94 throw new IllegalArgumentException("don't know how to parse: " + in);
95 }
96
97 protected String m_name;
98
99 public Type(String name) {
100
101 // don't deal with generics
102 // this is just for raw jvm types
103 if (name.charAt(0) == 'T' || name.indexOf('<') >= 0 || name.indexOf('>') >= 0) {
104 throw new IllegalArgumentException("don't use with generic types or templates: " + name);
105 }
106
107 m_name = name;
108 }
109
110 public Type(Type other) {
111 m_name = other.m_name;
112 }
113
114 public Type(ClassEntry classEntry) {
115 m_name = "L" + classEntry.getClassName() + ";";
116 }
117
118 public Type(Type other, ClassNameReplacer replacer) {
119 m_name = other.m_name;
120 if (other.isClass()) {
121 String replacedName = replacer.replace(other.getClassEntry().getClassName());
122 if (replacedName != null) {
123 m_name = "L" + replacedName + ";";
124 }
125 } else if (other.isArray() && other.hasClass()) {
126 String replacedName = replacer.replace(other.getClassEntry().getClassName());
127 if (replacedName != null) {
128 m_name = Type.getArrayPrefix(other.getArrayDimension()) + "L" + replacedName + ";";
129 }
130 }
131 }
132
133 @Override
134 public String toString() {
135 return m_name;
136 }
137
138 public boolean isVoid() {
139 return m_name.length() == 1 && m_name.charAt(0) == 'V';
140 }
141
142 public boolean isPrimitive() {
143 return m_name.length() == 1 && Primitive.get(m_name.charAt(0)) != null;
144 }
145
146 public Primitive getPrimitive() {
147 if (!isPrimitive()) {
148 throw new IllegalStateException("not a primitive");
149 }
150 return Primitive.get(m_name.charAt(0));
151 }
152
153 public boolean isClass() {
154 return m_name.charAt(0) == 'L' && m_name.charAt(m_name.length() - 1) == ';';
155 }
156
157 public ClassEntry getClassEntry() {
158 if (isClass()) {
159 String name = m_name.substring(1, m_name.length() - 1);
160
161 int pos = name.indexOf('<');
162 if (pos >= 0) {
163 // remove the parameters from the class name
164 name = name.substring(0, pos);
165 }
166
167 return new ClassEntry(name);
168
169 } else if (isArray() && getArrayType().isClass()) {
170 return getArrayType().getClassEntry();
171 } else {
172 throw new IllegalStateException("type doesn't have a class");
173 }
174 }
175
176 public boolean isArray() {
177 return m_name.charAt(0) == '[';
178 }
179
180 public int getArrayDimension() {
181 if (!isArray()) {
182 throw new IllegalStateException("not an array");
183 }
184 return countArrayDimension(m_name);
185 }
186
187 public Type getArrayType() {
188 if (!isArray()) {
189 throw new IllegalStateException("not an array");
190 }
191 return new Type(m_name.substring(getArrayDimension(), m_name.length()));
192 }
193
194 private static String getArrayPrefix(int dimension) {
195 StringBuilder buf = new StringBuilder();
196 for (int i=0; i<dimension; i++) {
197 buf.append("[");
198 }
199 return buf.toString();
200 }
201
202 public boolean hasClass() {
203 return isClass() || (isArray() && getArrayType().hasClass());
204 }
205
206 @Override
207 public boolean equals(Object other) {
208 if (other instanceof Type) {
209 return equals((Type)other);
210 }
211 return false;
212 }
213
214 public boolean equals(Type other) {
215 return m_name.equals(other.m_name);
216 }
217
218 public int hashCode() {
219 return m_name.hashCode();
220 }
221
222 private static int countArrayDimension(String in) {
223 int i=0;
224 for(; i < in.length() && in.charAt(i) == '['; i++);
225 return i;
226 }
227
228 private static String readClass(String in) {
229 // read all the characters in the buffer until we hit a ';'
230 // include the parameters too
231 StringBuilder buf = new StringBuilder();
232 int depth = 0;
233 for (int i=0; i<in.length(); i++) {
234 char c = in.charAt(i);
235 buf.append(c);
236
237 if (c == '<') {
238 depth++;
239 } else if (c == '>') {
240 depth--;
241 } else if (depth == 0 && c == ';') {
242 return buf.toString();
243 }
244 }
245 return null;
246 }
247}
diff --git a/src/main/java/cuchaz/enigma/CommandMain.java b/src/main/java/cuchaz/enigma/CommandMain.java
new file mode 100644
index 00000000..69f56849
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/CommandMain.java
@@ -0,0 +1,186 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.io.FileReader;
15import java.util.jar.JarFile;
16
17import cuchaz.enigma.Deobfuscator.ProgressListener;
18import cuchaz.enigma.mapping.Mappings;
19import cuchaz.enigma.mapping.MappingsReader;
20
21public class CommandMain {
22
23 public static class ConsoleProgressListener implements ProgressListener {
24
25 private static final int ReportTime = 5000; // 5s
26
27 private int m_totalWork;
28 private long m_startTime;
29 private long m_lastReportTime;
30
31 @Override
32 public void init(int totalWork, String title) {
33 m_totalWork = totalWork;
34 m_startTime = System.currentTimeMillis();
35 m_lastReportTime = m_startTime;
36 System.out.println(title);
37 }
38
39 @Override
40 public void onProgress(int numDone, String message) {
41
42 long now = System.currentTimeMillis();
43 boolean isLastUpdate = numDone == m_totalWork;
44 boolean shouldReport = isLastUpdate || now - m_lastReportTime > ReportTime;
45
46 if (shouldReport) {
47 int percent = numDone * 100 / m_totalWork;
48 System.out.println(String.format("\tProgress: %3d%%", percent));
49 m_lastReportTime = now;
50 }
51 if (isLastUpdate) {
52 double elapsedSeconds = (now - m_startTime) / 1000;
53 System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds));
54 }
55 }
56 }
57
58 public static void main(String[] args)
59 throws Exception {
60
61 try {
62
63 // process the command
64 String command = getArg(args, 0, "command", true);
65 if (command.equalsIgnoreCase("deobfuscate")) {
66 deobfuscate(args);
67 } else if (command.equalsIgnoreCase("decompile")) {
68 decompile(args);
69 } else if (command.equalsIgnoreCase("protectify")) {
70 protectify(args);
71 } else if (command.equalsIgnoreCase("publify")) {
72 publify(args);
73 } else {
74 throw new IllegalArgumentException("Command not recognized: " + command);
75 }
76 } catch (IllegalArgumentException ex) {
77 System.out.println(ex.getMessage());
78 printHelp();
79 }
80 }
81
82 private static void printHelp() {
83 System.out.println(String.format("%s - %s", Constants.Name, Constants.Version));
84 System.out.println("Usage:");
85 System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain <command>");
86 System.out.println("\twhere <command> is one of:");
87 System.out.println("\t\tdeobfuscate <in jar> <out jar> [<mappings file>]");
88 System.out.println("\t\tdecompile <in jar> <out folder> [<mappings file>]");
89 System.out.println("\t\tprotectify <in jar> <out jar>");
90 }
91
92 private static void decompile(String[] args)
93 throws Exception {
94 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
95 File fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true));
96 File fileMappings = getReadableFile(getArg(args, 3, "mappings file", false));
97 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn));
98 deobfuscator.writeSources(fileJarOut, new ConsoleProgressListener());
99 }
100
101 private static void deobfuscate(String[] args)
102 throws Exception {
103 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
104 File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true));
105 File fileMappings = getReadableFile(getArg(args, 3, "mappings file", false));
106 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn));
107 deobfuscator.writeJar(fileJarOut, new ConsoleProgressListener());
108 }
109
110 private static void protectify(String[] args)
111 throws Exception {
112 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
113 File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true));
114 Deobfuscator deobfuscator = getDeobfuscator(null, new JarFile(fileJarIn));
115 deobfuscator.protectifyJar(fileJarOut, new ConsoleProgressListener());
116 }
117
118 private static void publify(String[] args)
119 throws Exception {
120 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
121 File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true));
122 Deobfuscator deobfuscator = getDeobfuscator(null, new JarFile(fileJarIn));
123 deobfuscator.publifyJar(fileJarOut, new ConsoleProgressListener());
124 }
125
126 private static Deobfuscator getDeobfuscator(File fileMappings, JarFile jar)
127 throws Exception {
128 System.out.println("Reading jar...");
129 Deobfuscator deobfuscator = new Deobfuscator(jar);
130 if (fileMappings != null) {
131 System.out.println("Reading mappings...");
132 Mappings mappings = new MappingsReader().read(fileMappings);
133 deobfuscator.setMappings(mappings);
134 }
135 return deobfuscator;
136 }
137
138 private static String getArg(String[] args, int i, String name, boolean required) {
139 if (i >= args.length) {
140 if (required) {
141 throw new IllegalArgumentException(name + " is required");
142 } else {
143 return null;
144 }
145 }
146 return args[i];
147 }
148
149 private static File getWritableFile(String path) {
150 if (path == null) {
151 return null;
152 }
153 File file = new File(path).getAbsoluteFile();
154 File dir = file.getParentFile();
155 if (dir == null) {
156 throw new IllegalArgumentException("Cannot write to folder: " + dir);
157 }
158 // quick fix to avoid stupid stuff in Gradle code
159 if (!dir.isDirectory()) {
160 dir.mkdirs();
161 }
162 return file;
163 }
164
165 private static File getWritableFolder(String path) {
166 if (path == null) {
167 return null;
168 }
169 File dir = new File(path).getAbsoluteFile();
170 if (!dir.exists()) {
171 throw new IllegalArgumentException("Cannot write to folder: " + dir);
172 }
173 return dir;
174 }
175
176 private static File getReadableFile(String path) {
177 if (path == null) {
178 return null;
179 }
180 File file = new File(path).getAbsoluteFile();
181 if (!file.exists()) {
182 throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath());
183 }
184 return file;
185 }
186}
diff --git a/src/cuchaz/enigma/Constants.java b/src/main/java/cuchaz/enigma/Constants.java
index 598cf38f..5d2c84bf 100644
--- a/src/cuchaz/enigma/Constants.java
+++ b/src/main/java/cuchaz/enigma/Constants.java
@@ -4,17 +4,17 @@
4 * are made available under the terms of the GNU Lesser General Public 4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at 5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html 6 * http://www.gnu.org/licenses/lgpl.html
7 * 7 * <p>
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma; 11package cuchaz.enigma;
12 12
13public class Constants { 13public class Constants {
14 public static final String Name = "Enigma"; 14 public static final String Name = "Enigma";
15 public static final String Version = "0.11 beta"; 15 public static final String Version = "0.2 - Beta";
16 public static final String Url = "http://www.cuchazinteractive.com/enigma"; 16 public static final String Url = "http://www.cuchazinteractive.com/enigma";
17 public static final int MiB = 1024 * 1024; // 1 mebibyte 17 public static final int MiB = 1024 * 1024; // 1 mebibyte
18 public static final int KiB = 1024; // 1 kebibyte 18 public static final int KiB = 1024; // 1 kebibyte
19 public static final String NonePackage = "none"; 19 public static final String NonePackage = "none";
20} 20}
diff --git a/src/main/java/cuchaz/enigma/ConvertMain.java b/src/main/java/cuchaz/enigma/ConvertMain.java
new file mode 100644
index 00000000..409a2692
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/ConvertMain.java
@@ -0,0 +1,362 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.io.FileReader;
15import java.io.FileWriter;
16import java.io.IOException;
17import java.util.jar.JarFile;
18
19import cuchaz.enigma.convert.*;
20import cuchaz.enigma.gui.ClassMatchingGui;
21import cuchaz.enigma.gui.MemberMatchingGui;
22import cuchaz.enigma.mapping.*;
23
24
25public class ConvertMain {
26
27 public static void main(String[] args)
28 throws IOException, MappingParseException {
29 try {
30 //Get all are args
31 String JarOld = getArg(args, 1, "Path to Old Jar", true);
32 String JarNew = getArg(args, 2, "Path to New Jar", true);
33 String OldMappings = getArg(args, 3, "Path to old .mappings file", true);
34 String NewMappings = getArg(args, 4, "Path to new .mappings file", true);
35 String ClassMatches = getArg(args, 5, "Path to Class .matches file", true);
36 String FieldMatches = getArg(args, 6, "Path to Field .matches file", true);
37 String MethodMatches = getArg(args, 7, "Path to Method .matches file", true);
38 //OldJar
39 JarFile sourceJar = new JarFile(new File(JarOld));
40 //NewJar
41 JarFile destJar = new JarFile(new File(JarNew));
42 //Get the mapping files
43 File inMappingsFile = new File(OldMappings);
44 File outMappingsFile = new File(NewMappings);
45 Mappings mappings = new MappingsReader().read( inMappingsFile);
46 //Make the Match Files..
47 File classMatchesFile = new File(ClassMatches);
48 File fieldMatchesFile = new File(FieldMatches);
49 File methodMatchesFile = new File(MethodMatches);
50
51 String command = getArg(args, 0, "command", true);
52
53 if (command.equalsIgnoreCase("computeClassMatches")) {
54 computeClassMatches(classMatchesFile, sourceJar, destJar, mappings);
55 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile);
56 } else if (command.equalsIgnoreCase("editClassMatches")) {
57 editClasssMatches(classMatchesFile, sourceJar, destJar, mappings);
58 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile);
59 } else if (command.equalsIgnoreCase("computeFieldMatches")) {
60 computeFieldMatches(fieldMatchesFile, destJar, outMappingsFile, classMatchesFile);
61 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile);
62 } else if (command.equalsIgnoreCase("editFieldMatches")) {
63 editFieldMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, fieldMatchesFile);
64 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile);
65 } else if (command.equalsIgnoreCase("computeMethodMatches")) {
66 computeMethodMatches(methodMatchesFile, destJar, outMappingsFile, classMatchesFile);
67 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile);
68 } else if (command.equalsIgnoreCase("editMethodMatches")) {
69 editMethodMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, methodMatchesFile);
70 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile);
71 } else if (command.equalsIgnoreCase("convertMappings")) {
72 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile);
73 }
74 } catch (IllegalArgumentException ex) {
75 System.out.println(ex.getMessage());
76 printHelp();
77 }
78 }
79
80 private static void printHelp() {
81 System.out.println(String.format("%s - %s", Constants.Name, Constants.Version));
82 System.out.println("Usage:");
83 System.out.println("\tjava -cp enigma.jar cuchaz.enigma.ConvertMain <command> <old-jar> <new-jar> <old-mappings> <new-mappings> <class-matches> <field-matches> <method-matches>");
84 System.out.println("\tWhere <command> is one of:");
85 System.out.println("\t\tcomputeClassMatches");
86 System.out.println("\t\teditClassMatches");
87 System.out.println("\t\tcomputeFieldMatches");
88 System.out.println("\t\teditFieldMatches");
89 System.out.println("\t\teditMethodMatches");
90 System.out.println("\t\tconvertMappings");
91 System.out.println("\tWhere <old-jar> is the already mapped jar.");
92 System.out.println("\tWhere <new-jar> is the unmapped jar.");
93 System.out.println("\tWhere <old-mappings> is the path to the mappings for the old jar.");
94 System.out.println("\tWhere <new-mappings> is the new mappings. (Where you want to save them and there name)");
95 System.out.println("\tWhere <class-matches> is the class matches file.");
96 System.out.println("\tWhere <field-matches> is the field matches file.");
97 System.out.println("\tWhere <method-matches> is the method matches file.");
98 }
99
100 //Copy of getArg from CommandMain.... Should make a utils class.
101 private static String getArg(String[] args, int i, String name, boolean required) {
102 if (i >= args.length) {
103 if (required) {
104 throw new IllegalArgumentException(name + " is required");
105 } else {
106 return null;
107 }
108 }
109 return args[i];
110 }
111
112 private static void computeClassMatches(File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings)
113 throws IOException {
114 ClassMatches classMatches = MappingsConverter.computeClassMatches(sourceJar, destJar, mappings);
115 MatchesWriter.writeClasses(classMatches, classMatchesFile);
116 System.out.println("Wrote:\n\t" + classMatchesFile.getAbsolutePath());
117 }
118
119 private static void editClasssMatches(final File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings)
120 throws IOException {
121 System.out.println("Reading class matches...");
122 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
123 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
124 deobfuscators.source.setMappings(mappings);
125 System.out.println("Starting GUI...");
126 new ClassMatchingGui(classMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new ClassMatchingGui.SaveListener() {
127 @Override
128 public void save(ClassMatches matches) {
129 try {
130 MatchesWriter.writeClasses(matches, classMatchesFile);
131 } catch (IOException ex) {
132 throw new Error(ex);
133 }
134 }
135 });
136 }
137
138 @SuppressWarnings("unused")
139 private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile)
140 throws IOException {
141 System.out.println("Reading class matches...");
142 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
143 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
144 deobfuscators.source.setMappings(mappings);
145
146 Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
147 new MappingsWriter().write(outMappingsFile, newMappings);
148 System.out.println("Write converted mappings to: " + outMappingsFile.getAbsolutePath());
149 }
150
151 private static void computeFieldMatches(File memberMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile)
152 throws IOException, MappingParseException {
153
154 System.out.println("Reading class matches...");
155 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
156 System.out.println("Reading mappings...");
157 Mappings destMappings = new MappingsReader().read(destMappingsFile);
158 System.out.println("Indexing dest jar...");
159 Deobfuscator destDeobfuscator = new Deobfuscator(destJar);
160
161 System.out.println("Writing matches...");
162
163 // get the matched and unmatched mappings
164 MemberMatches<FieldEntry> fieldMatches = MappingsConverter.computeMemberMatches(
165 destDeobfuscator,
166 destMappings,
167 classMatches,
168 MappingsConverter.getFieldDoer()
169 );
170
171 MatchesWriter.writeMembers(fieldMatches, memberMatchesFile);
172 System.out.println("Wrote:\n\t" + memberMatchesFile.getAbsolutePath());
173 }
174
175 private static void editFieldMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File fieldMatchesFile)
176 throws IOException, MappingParseException {
177
178 System.out.println("Reading matches...");
179 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
180 MemberMatches<FieldEntry> fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
181
182 // prep deobfuscators
183 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
184 deobfuscators.source.setMappings(sourceMappings);
185 Mappings destMappings = new MappingsReader().read(destMappingsFile);
186 MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
187 checker.dropBrokenMappings(destMappings);
188 deobfuscators.dest.setMappings(destMappings);
189
190 new MemberMatchingGui<FieldEntry>(classMatches, fieldMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new MemberMatchingGui.SaveListener<FieldEntry>() {
191 @Override
192 public void save(MemberMatches<FieldEntry> matches) {
193 try {
194 MatchesWriter.writeMembers(matches, fieldMatchesFile);
195 } catch (IOException ex) {
196 throw new Error(ex);
197 }
198 }
199 });
200 }
201
202 @SuppressWarnings("unused")
203 private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile)
204 throws IOException {
205
206 System.out.println("Reading matches...");
207 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
208 MemberMatches<FieldEntry> fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
209
210 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
211 deobfuscators.source.setMappings(mappings);
212
213 // apply matches
214 Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
215 MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer());
216
217 // write out the converted mappings
218
219 new MappingsWriter().write(outMappingsFile, newMappings);
220 System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
221 }
222
223
224 private static void computeMethodMatches(File methodMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile)
225 throws IOException, MappingParseException {
226
227 System.out.println("Reading class matches...");
228 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
229 System.out.println("Reading mappings...");
230 Mappings destMappings = new MappingsReader().read(destMappingsFile);
231 System.out.println("Indexing dest jar...");
232 Deobfuscator destDeobfuscator = new Deobfuscator(destJar);
233
234 System.out.println("Writing method matches...");
235
236 // get the matched and unmatched mappings
237 MemberMatches<BehaviorEntry> methodMatches = MappingsConverter.computeMemberMatches(
238 destDeobfuscator,
239 destMappings,
240 classMatches,
241 MappingsConverter.getMethodDoer()
242 );
243
244 MatchesWriter.writeMembers(methodMatches, methodMatchesFile);
245 System.out.println("Wrote:\n\t" + methodMatchesFile.getAbsolutePath());
246 }
247
248 private static void editMethodMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File methodMatchesFile)
249 throws IOException, MappingParseException {
250
251 System.out.println("Reading matches...");
252 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
253 MemberMatches<BehaviorEntry> methodMatches = MatchesReader.readMembers(methodMatchesFile);
254
255 // prep deobfuscators
256 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
257 deobfuscators.source.setMappings(sourceMappings);
258 Mappings destMappings = new MappingsReader().read(destMappingsFile);
259 MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
260 checker.dropBrokenMappings(destMappings);
261 deobfuscators.dest.setMappings(destMappings);
262
263 new MemberMatchingGui<BehaviorEntry>(classMatches, methodMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new MemberMatchingGui.SaveListener<BehaviorEntry>() {
264 @Override
265 public void save(MemberMatches<BehaviorEntry> matches) {
266 try {
267 MatchesWriter.writeMembers(matches, methodMatchesFile);
268 } catch (IOException ex) {
269 throw new Error(ex);
270 }
271 }
272 });
273 }
274
275 private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile, File methodMatchesFile)
276 throws IOException {
277
278 System.out.println("Reading matches...");
279 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
280 MemberMatches<FieldEntry> fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
281 MemberMatches<BehaviorEntry> methodMatches = MatchesReader.readMembers(methodMatchesFile);
282
283 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
284 deobfuscators.source.setMappings(mappings);
285
286 // apply matches
287 Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
288 MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer());
289 MappingsConverter.applyMemberMatches(newMappings, classMatches, methodMatches, MappingsConverter.getMethodDoer());
290
291 // check the final mappings
292 MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
293 checker.dropBrokenMappings(newMappings);
294
295 for (java.util.Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedClassMappings().entrySet()) {
296 System.out.println("WARNING: Broken class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
297 }
298 for (java.util.Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedInnerClassMappings().entrySet()) {
299 System.out.println("WARNING: Broken inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
300 }
301 for (java.util.Map.Entry<FieldEntry, FieldMapping> mapping : checker.getDroppedFieldMappings().entrySet()) {
302 System.out.println("WARNING: Broken field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
303 }
304 for (java.util.Map.Entry<BehaviorEntry, MethodMapping> mapping : checker.getDroppedMethodMappings().entrySet()) {
305 System.out.println("WARNING: Broken behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
306 }
307
308 //TODO Fix
309 // write out the converted mappings
310// try (FileWriter out = new FileWriter(outMappingsFile)) {
311// new MappingsWriter().write(out, newMappings);
312// }
313 System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
314 }
315
316 private static class Deobfuscators {
317
318 public Deobfuscator source;
319 public Deobfuscator dest;
320
321 public Deobfuscators(JarFile sourceJar, JarFile destJar) {
322 System.out.println("Indexing source jar...");
323 IndexerThread sourceIndexer = new IndexerThread(sourceJar);
324 sourceIndexer.start();
325 System.out.println("Indexing dest jar...");
326 IndexerThread destIndexer = new IndexerThread(destJar);
327 destIndexer.start();
328 sourceIndexer.joinOrBail();
329 destIndexer.joinOrBail();
330 source = sourceIndexer.deobfuscator;
331 dest = destIndexer.deobfuscator;
332 }
333 }
334
335 private static class IndexerThread extends Thread {
336
337 private JarFile m_jarFile;
338 public Deobfuscator deobfuscator;
339
340 public IndexerThread(JarFile jarFile) {
341 m_jarFile = jarFile;
342 deobfuscator = null;
343 }
344
345 public void joinOrBail() {
346 try {
347 join();
348 } catch (InterruptedException ex) {
349 throw new Error(ex);
350 }
351 }
352
353 @Override
354 public void run() {
355 try {
356 deobfuscator = new Deobfuscator(m_jarFile);
357 } catch (IOException ex) {
358 throw new Error(ex);
359 }
360 }
361 }
362} \ No newline at end of file
diff --git a/src/main/java/cuchaz/enigma/Deobfuscator.java b/src/main/java/cuchaz/enigma/Deobfuscator.java
new file mode 100644
index 00000000..2a18e657
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/Deobfuscator.java
@@ -0,0 +1,530 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import com.google.common.collect.Maps;
14import com.google.common.collect.Sets;
15
16import com.strobel.assembler.metadata.MetadataSystem;
17import com.strobel.assembler.metadata.TypeDefinition;
18import com.strobel.assembler.metadata.TypeReference;
19import com.strobel.decompiler.DecompilerContext;
20import com.strobel.decompiler.DecompilerSettings;
21import com.strobel.decompiler.PlainTextOutput;
22import com.strobel.decompiler.languages.java.JavaOutputVisitor;
23import com.strobel.decompiler.languages.java.ast.AstBuilder;
24import com.strobel.decompiler.languages.java.ast.CompilationUnit;
25import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor;
26
27import java.io.*;
28import java.util.List;
29import java.util.Map;
30import java.util.Set;
31import java.util.jar.JarEntry;
32import java.util.jar.JarFile;
33import java.util.jar.JarOutputStream;
34
35import cuchaz.enigma.analysis.*;
36import cuchaz.enigma.bytecode.ClassProtectifier;
37import cuchaz.enigma.bytecode.ClassPublifier;
38import cuchaz.enigma.mapping.*;
39import javassist.CtClass;
40import javassist.bytecode.Descriptor;
41
42public class Deobfuscator {
43
44 public interface ProgressListener {
45 void init(int totalWork, String title);
46
47 void onProgress(int numDone, String message);
48 }
49
50 private JarFile m_jar;
51 private DecompilerSettings m_settings;
52 private JarIndex m_jarIndex;
53 private Mappings m_mappings;
54 private MappingsRenamer m_renamer;
55 private Map<TranslationDirection, Translator> m_translatorCache;
56
57 public Deobfuscator(JarFile jar) throws IOException {
58 m_jar = jar;
59
60 // build the jar index
61 m_jarIndex = new JarIndex();
62 m_jarIndex.indexJar(m_jar, true);
63
64 // config the decompiler
65 m_settings = DecompilerSettings.javaDefaults();
66 m_settings.setMergeVariables(true);
67 m_settings.setForceExplicitImports(true);
68 m_settings.setForceExplicitTypeArguments(true);
69 m_settings.setShowDebugLineNumbers(true);
70 // DEBUG
71 //m_settings.setShowSyntheticMembers(true);
72
73 // init defaults
74 m_translatorCache = Maps.newTreeMap();
75
76 // init mappings
77 setMappings(new Mappings());
78 }
79
80 public JarFile getJar() {
81 return m_jar;
82 }
83
84 public String getJarName() {
85 return m_jar.getName();
86 }
87
88 public JarIndex getJarIndex() {
89 return m_jarIndex;
90 }
91
92 public Mappings getMappings() {
93 return m_mappings;
94 }
95
96 public void setMappings(Mappings val) {
97 setMappings(val, true);
98 }
99
100 public void setMappings(Mappings val, boolean warnAboutDrops) {
101 if (val == null) {
102 val = new Mappings();
103 }
104
105 // drop mappings that don't match the jar
106 MappingsChecker checker = new MappingsChecker(m_jarIndex);
107 checker.dropBrokenMappings(val);
108 if (warnAboutDrops) {
109 for (java.util.Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedClassMappings().entrySet()) {
110 System.out.println("WARNING: Couldn't find class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
111 }
112 for (java.util.Map.Entry<ClassEntry, ClassMapping> mapping : checker.getDroppedInnerClassMappings().entrySet()) {
113 System.out.println("WARNING: Couldn't find inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
114 }
115 for (java.util.Map.Entry<FieldEntry, FieldMapping> mapping : checker.getDroppedFieldMappings().entrySet()) {
116 System.out.println("WARNING: Couldn't find field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
117 }
118 for (java.util.Map.Entry<BehaviorEntry, MethodMapping> mapping : checker.getDroppedMethodMappings().entrySet()) {
119 System.out.println("WARNING: Couldn't find behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
120 }
121 }
122
123 // check for related method inconsistencies
124 if (checker.getRelatedMethodChecker().hasProblems()) {
125 throw new Error("Related methods are inconsistent! Need to fix the mappings manually.\n" + checker.getRelatedMethodChecker().getReport());
126 }
127
128 m_mappings = val;
129 m_renamer = new MappingsRenamer(m_jarIndex, val);
130 m_translatorCache.clear();
131 }
132
133 public Translator getTranslator(TranslationDirection direction) {
134 Translator translator = m_translatorCache.get(direction);
135 if (translator == null) {
136 translator = m_mappings.getTranslator(direction, m_jarIndex.getTranslationIndex());
137 m_translatorCache.put(direction, translator);
138 }
139 return translator;
140 }
141
142 public void getSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) {
143 for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) {
144 // skip inner classes
145 if (obfClassEntry.isInnerClass()) {
146 continue;
147 }
148
149 // separate the classes
150 ClassEntry deobfClassEntry = deobfuscateEntry(obfClassEntry);
151 if (!deobfClassEntry.equals(obfClassEntry)) {
152 // if the class has a mapping, clearly it's deobfuscated
153 deobfClasses.add(deobfClassEntry);
154 } else if (!obfClassEntry.getPackageName().equals(Constants.NonePackage)) {
155 // also call it deobufscated if it's not in the none package
156 deobfClasses.add(obfClassEntry);
157 } else {
158 // otherwise, assume it's still obfuscated
159 obfClasses.add(obfClassEntry);
160 }
161 }
162 }
163
164 public CompilationUnit getSourceTree(String className) {
165
166 // we don't know if this class name is obfuscated or deobfuscated
167 // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out
168 // the decompiler only sees classes after deobfuscation, so we need to load it by the deobfuscated name if there is one
169
170 // first, assume class name is deobf
171 String deobfClassName = className;
172
173 // if it wasn't actually deobf, then we can find a mapping for it and get the deobf name
174 ClassMapping classMapping = m_mappings.getClassByObf(className);
175 if (classMapping != null && classMapping.getDeobfName() != null) {
176 deobfClassName = classMapping.getDeobfName();
177 }
178
179 // set the type loader
180 TranslatingTypeLoader loader = new TranslatingTypeLoader(
181 m_jar,
182 m_jarIndex,
183 getTranslator(TranslationDirection.Obfuscating),
184 getTranslator(TranslationDirection.Deobfuscating)
185 );
186 m_settings.setTypeLoader(loader);
187
188 // see if procyon can find the type
189 TypeReference type = new MetadataSystem(loader).lookupType(deobfClassName);
190 if (type == null) {
191 throw new Error(String.format("Unable to find type: %s (deobf: %s)\nTried class names: %s",
192 className, deobfClassName, loader.getClassNamesToTry(deobfClassName)
193 ));
194 }
195 TypeDefinition resolvedType = type.resolve();
196
197 // decompile it!
198 DecompilerContext context = new DecompilerContext();
199 context.setCurrentType(resolvedType);
200 context.setSettings(m_settings);
201 AstBuilder builder = new AstBuilder(context);
202 builder.addType(resolvedType);
203 builder.runTransformations(null);
204 return builder.getCompilationUnit();
205 }
206
207 public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source) {
208 return getSourceIndex(sourceTree, source, null);
209 }
210
211 public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source, Boolean ignoreBadTokens) {
212
213 // build the source index
214 SourceIndex index;
215 if (ignoreBadTokens != null) {
216 index = new SourceIndex(source, ignoreBadTokens);
217 } else {
218 index = new SourceIndex(source);
219 }
220 sourceTree.acceptVisitor(new SourceIndexVisitor(), index);
221
222 // DEBUG
223 // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null );
224
225 // resolve all the classes in the source references
226 for (Token token : index.referenceTokens()) {
227 EntryReference<Entry, Entry> deobfReference = index.getDeobfReference(token);
228
229 // get the obfuscated entry
230 Entry obfEntry = obfuscateEntry(deobfReference.entry);
231
232 // try to resolve the class
233 ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(obfEntry);
234 if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getClassEntry())) {
235 // change the class of the entry
236 obfEntry = obfEntry.cloneToNewClass(resolvedObfClassEntry);
237
238 // save the new deobfuscated reference
239 deobfReference.entry = deobfuscateEntry(obfEntry);
240 index.replaceDeobfReference(token, deobfReference);
241 }
242
243 // DEBUG
244 // System.out.println( token + " -> " + reference + " -> " + index.getReferenceToken( reference ) );
245 }
246
247 return index;
248 }
249
250 public String getSource(CompilationUnit sourceTree) {
251 // render the AST into source
252 StringWriter buf = new StringWriter();
253 sourceTree.acceptVisitor(new InsertParenthesesVisitor(), null);
254 sourceTree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(buf), m_settings), null);
255 return buf.toString();
256 }
257
258 public void writeSources(File dirOut, ProgressListener progress) throws IOException {
259 // get the classes to decompile
260 Set<ClassEntry> classEntries = Sets.newHashSet();
261 for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) {
262 // skip inner classes
263 if (obfClassEntry.isInnerClass()) {
264 continue;
265 }
266
267 classEntries.add(obfClassEntry);
268 }
269
270 if (progress != null) {
271 progress.init(classEntries.size(), "Decompiling classes...");
272 }
273
274 // DEOBFUSCATE ALL THE THINGS!! @_@
275 int i = 0;
276 for (ClassEntry obfClassEntry : classEntries) {
277 ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry));
278 if (progress != null) {
279 progress.onProgress(i++, deobfClassEntry.toString());
280 }
281
282 try {
283 // get the source
284 String source = getSource(getSourceTree(obfClassEntry.getName()));
285
286 // write the file
287 File file = new File(dirOut, deobfClassEntry.getName().replace('.', '/') + ".java");
288 file.getParentFile().mkdirs();
289 try (FileWriter out = new FileWriter(file)) {
290 out.write(source);
291 }
292 } catch (Throwable t) {
293 // don't crash the whole world here, just log the error and keep going
294 // TODO: set up logback via log4j
295 System.err.println("Unable to deobfuscate class " + deobfClassEntry.toString() + " (" + obfClassEntry.toString() + ")");
296 t.printStackTrace(System.err);
297 }
298 }
299 if (progress != null) {
300 progress.onProgress(i, "Done!");
301 }
302 }
303
304 public void writeJar(File out, ProgressListener progress) {
305 final TranslatingTypeLoader loader = new TranslatingTypeLoader(
306 m_jar,
307 m_jarIndex,
308 getTranslator(TranslationDirection.Obfuscating),
309 getTranslator(TranslationDirection.Deobfuscating)
310 );
311 transformJar(out, progress, new ClassTransformer() {
312
313 @Override
314 public CtClass transform(CtClass c) throws Exception {
315 return loader.transformClass(c);
316 }
317 });
318 }
319
320 public void protectifyJar(File out, ProgressListener progress) {
321 transformJar(out, progress, new ClassTransformer() {
322
323 @Override
324 public CtClass transform(CtClass c) throws Exception {
325 return ClassProtectifier.protectify(c);
326 }
327 });
328 }
329
330 public void publifyJar(File out, ProgressListener progress) {
331 transformJar(out, progress, new ClassTransformer() {
332
333 @Override
334 public CtClass transform(CtClass c) throws Exception {
335 return ClassPublifier.publify(c);
336 }
337 });
338 }
339
340 private interface ClassTransformer {
341 CtClass transform(CtClass c) throws Exception;
342 }
343
344 private void transformJar(File out, ProgressListener progress, ClassTransformer transformer) {
345 try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) {
346 if (progress != null) {
347 progress.init(JarClassIterator.getClassEntries(m_jar).size(), "Transforming classes...");
348 }
349
350 int i = 0;
351 for (CtClass c : JarClassIterator.classes(m_jar)) {
352 if (progress != null) {
353 progress.onProgress(i++, c.getName());
354 }
355
356 try {
357 c = transformer.transform(c);
358 outJar.putNextEntry(new JarEntry(c.getName().replace('.', '/') + ".class"));
359 outJar.write(c.toBytecode());
360 outJar.closeEntry();
361 } catch (Throwable t) {
362 throw new Error("Unable to transform class " + c.getName(), t);
363 }
364 }
365 if (progress != null) {
366 progress.onProgress(i, "Done!");
367 }
368
369 outJar.close();
370 } catch (IOException ex) {
371 throw new Error("Unable to write to Jar file!");
372 }
373 }
374
375 public <T extends Entry> T obfuscateEntry(T deobfEntry) {
376 if (deobfEntry == null) {
377 return null;
378 }
379 return getTranslator(TranslationDirection.Obfuscating).translateEntry(deobfEntry);
380 }
381
382 public <T extends Entry> T deobfuscateEntry(T obfEntry) {
383 if (obfEntry == null) {
384 return null;
385 }
386 return getTranslator(TranslationDirection.Deobfuscating).translateEntry(obfEntry);
387 }
388
389 public <E extends Entry, C extends Entry> EntryReference<E, C> obfuscateReference(EntryReference<E, C> deobfReference) {
390 if (deobfReference == null) {
391 return null;
392 }
393 return new EntryReference<E, C>(
394 obfuscateEntry(deobfReference.entry),
395 obfuscateEntry(deobfReference.context),
396 deobfReference
397 );
398 }
399
400 public <E extends Entry, C extends Entry> EntryReference<E, C> deobfuscateReference(EntryReference<E, C> obfReference) {
401 if (obfReference == null) {
402 return null;
403 }
404 return new EntryReference<E, C>(
405 deobfuscateEntry(obfReference.entry),
406 deobfuscateEntry(obfReference.context),
407 obfReference
408 );
409 }
410
411 public boolean isObfuscatedIdentifier(Entry obfEntry) {
412
413 if (obfEntry instanceof MethodEntry) {
414
415 // HACKHACK: Object methods are not obfuscated identifiers
416 MethodEntry obfMethodEntry = (MethodEntry) obfEntry;
417 String name = obfMethodEntry.getName();
418 String sig = obfMethodEntry.getSignature().toString();
419 if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) {
420 return false;
421 } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) {
422 return false;
423 } else if (name.equals("finalize") && sig.equals("()V")) {
424 return false;
425 } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) {
426 return false;
427 } else if (name.equals("hashCode") && sig.equals("()I")) {
428 return false;
429 } else if (name.equals("notify") && sig.equals("()V")) {
430 return false;
431 } else if (name.equals("notifyAll") && sig.equals("()V")) {
432 return false;
433 } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) {
434 return false;
435 } else if (name.equals("wait") && sig.equals("()V")) {
436 return false;
437 } else if (name.equals("wait") && sig.equals("(J)V")) {
438 return false;
439 } else if (name.equals("wait") && sig.equals("(JI)V")) {
440 return false;
441 }
442 }
443
444 return m_jarIndex.containsObfEntry(obfEntry);
445 }
446
447 public boolean isRenameable(EntryReference<Entry, Entry> obfReference) {
448 return obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry());
449 }
450
451 // NOTE: these methods are a bit messy... oh well
452
453 public boolean hasDeobfuscatedName(Entry obfEntry) {
454 Translator translator = getTranslator(TranslationDirection.Deobfuscating);
455 if (obfEntry instanceof ClassEntry) {
456 ClassEntry obfClass = (ClassEntry) obfEntry;
457 List<ClassMapping> mappingChain = m_mappings.getClassMappingChain(obfClass);
458 ClassMapping classMapping = mappingChain.get(mappingChain.size() - 1);
459 return classMapping != null && classMapping.getDeobfName() != null;
460 } else if (obfEntry instanceof FieldEntry) {
461 return translator.translate((FieldEntry) obfEntry) != null;
462 } else if (obfEntry instanceof MethodEntry) {
463 return translator.translate((MethodEntry) obfEntry) != null;
464 } else if (obfEntry instanceof ConstructorEntry) {
465 // constructors have no names
466 return false;
467 } else if (obfEntry instanceof ArgumentEntry) {
468 return translator.translate((ArgumentEntry) obfEntry) != null;
469 } else {
470 throw new Error("Unknown entry type: " + obfEntry.getClass().getName());
471 }
472 }
473
474 public void rename(Entry obfEntry, String newName) {
475 if (obfEntry instanceof ClassEntry) {
476 m_renamer.setClassName((ClassEntry) obfEntry, Descriptor.toJvmName(newName));
477 } else if (obfEntry instanceof FieldEntry) {
478 m_renamer.setFieldName((FieldEntry) obfEntry, newName);
479 } else if (obfEntry instanceof MethodEntry) {
480 m_renamer.setMethodTreeName((MethodEntry) obfEntry, newName);
481 } else if (obfEntry instanceof ConstructorEntry) {
482 throw new IllegalArgumentException("Cannot rename constructors");
483 } else if (obfEntry instanceof ArgumentEntry) {
484 m_renamer.setArgumentName((ArgumentEntry) obfEntry, newName);
485 } else {
486 throw new Error("Unknown entry type: " + obfEntry.getClass().getName());
487 }
488
489 // clear caches
490 m_translatorCache.clear();
491 }
492
493 public void removeMapping(Entry obfEntry) {
494 if (obfEntry instanceof ClassEntry) {
495 m_renamer.removeClassMapping((ClassEntry) obfEntry);
496 } else if (obfEntry instanceof FieldEntry) {
497 m_renamer.removeFieldMapping((FieldEntry) obfEntry);
498 } else if (obfEntry instanceof MethodEntry) {
499 m_renamer.removeMethodTreeMapping((MethodEntry) obfEntry);
500 } else if (obfEntry instanceof ConstructorEntry) {
501 throw new IllegalArgumentException("Cannot rename constructors");
502 } else if (obfEntry instanceof ArgumentEntry) {
503 m_renamer.removeArgumentMapping((ArgumentEntry) obfEntry);
504 } else {
505 throw new Error("Unknown entry type: " + obfEntry);
506 }
507
508 // clear caches
509 m_translatorCache.clear();
510 }
511
512 public void markAsDeobfuscated(Entry obfEntry) {
513 if (obfEntry instanceof ClassEntry) {
514 m_renamer.markClassAsDeobfuscated((ClassEntry) obfEntry);
515 } else if (obfEntry instanceof FieldEntry) {
516 m_renamer.markFieldAsDeobfuscated((FieldEntry) obfEntry);
517 } else if (obfEntry instanceof MethodEntry) {
518 m_renamer.markMethodTreeAsDeobfuscated((MethodEntry) obfEntry);
519 } else if (obfEntry instanceof ConstructorEntry) {
520 throw new IllegalArgumentException("Cannot rename constructors");
521 } else if (obfEntry instanceof ArgumentEntry) {
522 m_renamer.markArgumentAsDeobfuscated((ArgumentEntry) obfEntry);
523 } else {
524 throw new Error("Unknown entry type: " + obfEntry);
525 }
526
527 // clear caches
528 m_translatorCache.clear();
529 }
530}
diff --git a/src/main/java/cuchaz/enigma/ExceptionIgnorer.java b/src/main/java/cuchaz/enigma/ExceptionIgnorer.java
new file mode 100644
index 00000000..fc89fa6d
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/ExceptionIgnorer.java
@@ -0,0 +1,34 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13public class ExceptionIgnorer {
14
15 public static boolean shouldIgnore(Throwable t) {
16
17 // is this that pesky concurrent access bug in the highlight painter system?
18 // (ancient ui code is ancient)
19 if (t instanceof ArrayIndexOutOfBoundsException) {
20 StackTraceElement[] stackTrace = t.getStackTrace();
21 if (stackTrace.length > 1) {
22
23 // does this stack frame match javax.swing.text.DefaultHighlighter.paint*() ?
24 StackTraceElement frame = stackTrace[1];
25 if (frame.getClassName().equals("javax.swing.text.DefaultHighlighter") && frame.getMethodName().startsWith("paint")) {
26 return true;
27 }
28 }
29 }
30
31 return false;
32 }
33
34}
diff --git a/src/main/java/cuchaz/enigma/Main.java b/src/main/java/cuchaz/enigma/Main.java
new file mode 100644
index 00000000..68959aa5
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/Main.java
@@ -0,0 +1,51 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.util.jar.JarFile;
15
16import cuchaz.enigma.gui.Gui;
17
18public class Main {
19
20 public static void main(String[] args) throws Exception {
21 Gui gui = new Gui();
22
23 // parse command-line args
24 if (args.length >= 1) {
25 gui.getController().openJar(new JarFile(getFile(args[0])));
26 }
27 if (args.length >= 2) {
28 gui.getController().openMappings(getFile(args[1]));
29 }
30
31 // DEBUG
32 //gui.getController().openDeclaration(new ClassEntry("none/bxq"));
33 }
34
35 private static File getFile(String path) {
36 // expand ~ to the home dir
37 if (path.startsWith("~")) {
38 // get the home dir
39 File dirHome = new File(System.getProperty("user.home"));
40
41 // is the path just ~/ or is it ~user/ ?
42 if (path.startsWith("~/")) {
43 return new File(dirHome, path.substring(2));
44 } else {
45 return new File(dirHome.getParentFile(), path.substring(1));
46 }
47 }
48
49 return new File(path);
50 }
51}
diff --git a/src/main/java/cuchaz/enigma/MainFormatConverter.java b/src/main/java/cuchaz/enigma/MainFormatConverter.java
new file mode 100644
index 00000000..29e334ec
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/MainFormatConverter.java
@@ -0,0 +1,121 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import com.google.common.collect.Maps;
14
15import java.io.File;
16import java.io.FileReader;
17import java.io.FileWriter;
18import java.lang.reflect.Field;
19import java.util.Map;
20import java.util.jar.JarFile;
21
22import cuchaz.enigma.analysis.JarClassIterator;
23import cuchaz.enigma.mapping.*;
24import javassist.CtClass;
25import javassist.CtField;
26
27public class MainFormatConverter {
28
29 public static void main(String[] args)
30 throws Exception {
31
32 System.out.println("Getting field types from jar...");
33
34 JarFile jar = new JarFile(System.getProperty("user.home") + "/.minecraft/versions/1.8/1.8.jar");
35 Map<String, Type> fieldTypes = Maps.newHashMap();
36 for (CtClass c : JarClassIterator.classes(jar)) {
37 for (CtField field : c.getDeclaredFields()) {
38 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
39 fieldTypes.put(getFieldKey(fieldEntry), moveClasssesOutOfDefaultPackage(fieldEntry.getType()));
40 }
41 }
42
43 System.out.println("Reading mappings...");
44
45 File fileMappings = new File("../Enigma Mappings/1.8.mappings");
46 MappingsReader mappingsReader = new MappingsReader() {
47
48 @Override
49 protected FieldMapping readField(String obf, String deobf, String type) {
50 // assume the void type for now
51 return new FieldMapping(obf, new Type("V"), deobf);
52 }
53 };
54 Mappings mappings = mappingsReader.read(fileMappings);
55
56 System.out.println("Updating field types...");
57
58 for (ClassMapping classMapping : mappings.classes()) {
59 updateFieldsInClass(fieldTypes, classMapping);
60 }
61
62 System.out.println("Saving mappings...");
63
64 //TODO Fix
65// try (FileWriter writer = new FileWriter(fileMappings)) {
66// new MappingsWriter().write(writer, mappings);
67// }
68
69 System.out.println("Done!");
70 }
71
72 private static Type moveClasssesOutOfDefaultPackage(Type type) {
73 return new Type(type, new ClassNameReplacer() {
74 @Override
75 public String replace(String className) {
76 ClassEntry entry = new ClassEntry(className);
77 if (entry.isInDefaultPackage()) {
78 return Constants.NonePackage + "/" + className;
79 }
80 return null;
81 }
82 });
83 }
84
85 private static void updateFieldsInClass(Map<String, Type> fieldTypes, ClassMapping classMapping)
86 throws Exception {
87
88 // update the fields
89 for (FieldMapping fieldMapping : classMapping.fields()) {
90 setFieldType(fieldTypes, classMapping, fieldMapping);
91 }
92
93 // recurse
94 for (ClassMapping innerClassMapping : classMapping.innerClasses()) {
95 updateFieldsInClass(fieldTypes, innerClassMapping);
96 }
97 }
98
99 private static void setFieldType(Map<String, Type> fieldTypes, ClassMapping classMapping, FieldMapping fieldMapping)
100 throws Exception {
101
102 // get the new type
103 Type newType = fieldTypes.get(getFieldKey(classMapping, fieldMapping));
104 if (newType == null) {
105 throw new Error("Can't find type for field: " + getFieldKey(classMapping, fieldMapping));
106 }
107
108 // hack in the new field type
109 Field field = fieldMapping.getClass().getDeclaredField("m_obfType");
110 field.setAccessible(true);
111 field.set(fieldMapping, newType);
112 }
113
114 private static Object getFieldKey(ClassMapping classMapping, FieldMapping fieldMapping) {
115 return classMapping.getObfSimpleName() + "." + fieldMapping.getObfName();
116 }
117
118 private static String getFieldKey(FieldEntry obfFieldEntry) {
119 return obfFieldEntry.getClassEntry().getSimpleName() + "." + obfFieldEntry.getName();
120 }
121}
diff --git a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java
new file mode 100644
index 00000000..6c429a6a
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java
@@ -0,0 +1,241 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import com.google.common.collect.Lists;
14import com.google.common.collect.Maps;
15
16import com.strobel.assembler.metadata.Buffer;
17import com.strobel.assembler.metadata.ClasspathTypeLoader;
18import com.strobel.assembler.metadata.ITypeLoader;
19
20import java.io.ByteArrayOutputStream;
21import java.io.IOException;
22import java.io.InputStream;
23import java.util.List;
24import java.util.Map;
25import java.util.jar.JarEntry;
26import java.util.jar.JarFile;
27
28import cuchaz.enigma.analysis.BridgeMarker;
29import cuchaz.enigma.analysis.JarIndex;
30import cuchaz.enigma.bytecode.*;
31import cuchaz.enigma.mapping.ClassEntry;
32import cuchaz.enigma.mapping.Translator;
33import javassist.*;
34import javassist.bytecode.Descriptor;
35
36public class TranslatingTypeLoader implements ITypeLoader {
37
38 private JarFile m_jar;
39 private JarIndex m_jarIndex;
40 private Translator m_obfuscatingTranslator;
41 private Translator m_deobfuscatingTranslator;
42 private Map<String, byte[]> m_cache;
43 private ClasspathTypeLoader m_defaultTypeLoader;
44
45 public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex) {
46 this(jar, jarIndex, new Translator(), new Translator());
47 }
48
49 public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator) {
50 m_jar = jar;
51 m_jarIndex = jarIndex;
52 m_obfuscatingTranslator = obfuscatingTranslator;
53 m_deobfuscatingTranslator = deobfuscatingTranslator;
54 m_cache = Maps.newHashMap();
55 m_defaultTypeLoader = new ClasspathTypeLoader();
56 }
57
58 public void clearCache() {
59 m_cache.clear();
60 }
61
62 @Override
63 public boolean tryLoadType(String className, Buffer out) {
64
65 // check the cache
66 byte[] data;
67 if (m_cache.containsKey(className)) {
68 data = m_cache.get(className);
69 } else {
70 data = loadType(className);
71 m_cache.put(className, data);
72 }
73
74 if (data == null) {
75 // chain to default type loader
76 return m_defaultTypeLoader.tryLoadType(className, out);
77 }
78
79 // send the class to the decompiler
80 out.reset(data.length);
81 System.arraycopy(data, 0, out.array(), out.position(), data.length);
82 out.position(0);
83 return true;
84 }
85
86 public CtClass loadClass(String deobfClassName) {
87
88 byte[] data = loadType(deobfClassName);
89 if (data == null) {
90 return null;
91 }
92
93 // return a javassist handle for the class
94 String javaClassFileName = Descriptor.toJavaName(deobfClassName);
95 ClassPool classPool = new ClassPool();
96 classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, data));
97 try {
98 return classPool.get(javaClassFileName);
99 } catch (NotFoundException ex) {
100 throw new Error(ex);
101 }
102 }
103
104 private byte[] loadType(String className) {
105
106 // NOTE: don't know if class name is obf or deobf
107 ClassEntry classEntry = new ClassEntry(className);
108 ClassEntry obfClassEntry = m_obfuscatingTranslator.translateEntry(classEntry);
109
110 // is this an inner class referenced directly? (ie trying to load b instead of a$b)
111 if (!obfClassEntry.isInnerClass()) {
112 List<ClassEntry> classChain = m_jarIndex.getObfClassChain(obfClassEntry);
113 if (classChain.size() > 1) {
114 System.err.println(String.format("WARNING: no class %s after inner class reconstruction. Try %s",
115 className, obfClassEntry.buildClassEntry(classChain)
116 ));
117 return null;
118 }
119 }
120
121 // is this a class we should even know about?
122 if (!m_jarIndex.containsObfClass(obfClassEntry)) {
123 return null;
124 }
125
126 // DEBUG
127 //System.out.println(String.format("Looking for %s (obf: %s)", classEntry.getName(), obfClassEntry.getName()));
128
129 // find the class in the jar
130 String classInJarName = findClassInJar(obfClassEntry);
131 if (classInJarName == null) {
132 // couldn't find it
133 return null;
134 }
135
136 try {
137 // read the class file into a buffer
138 ByteArrayOutputStream data = new ByteArrayOutputStream();
139 byte[] buf = new byte[1024 * 1024]; // 1 KiB
140 InputStream in = m_jar.getInputStream(m_jar.getJarEntry(classInJarName + ".class"));
141 while (true) {
142 int bytesRead = in.read(buf);
143 if (bytesRead <= 0) {
144 break;
145 }
146 data.write(buf, 0, bytesRead);
147 }
148 data.close();
149 in.close();
150 buf = data.toByteArray();
151
152 // load the javassist handle to the raw class
153 ClassPool classPool = new ClassPool();
154 String classInJarJavaName = Descriptor.toJavaName(classInJarName);
155 classPool.insertClassPath(new ByteArrayClassPath(classInJarJavaName, buf));
156 CtClass c = classPool.get(classInJarJavaName);
157
158 c = transformClass(c);
159
160 // sanity checking
161 assertClassName(c, classEntry);
162
163 // DEBUG
164 //Util.writeClass( c );
165
166 // we have a transformed class!
167 return c.toBytecode();
168 } catch (IOException | NotFoundException | CannotCompileException ex) {
169 throw new Error(ex);
170 }
171 }
172
173 private String findClassInJar(ClassEntry obfClassEntry) {
174
175 // try to find the class in the jar
176 for (String className : getClassNamesToTry(obfClassEntry)) {
177 JarEntry jarEntry = m_jar.getJarEntry(className + ".class");
178 if (jarEntry != null) {
179 return className;
180 }
181 }
182
183 // didn't find it ;_;
184 return null;
185 }
186
187 public List<String> getClassNamesToTry(String className) {
188 return getClassNamesToTry(m_obfuscatingTranslator.translateEntry(new ClassEntry(className)));
189 }
190
191 public List<String> getClassNamesToTry(ClassEntry obfClassEntry) {
192 List<String> classNamesToTry = Lists.newArrayList();
193 classNamesToTry.add(obfClassEntry.getName());
194 if (obfClassEntry.getPackageName().equals(Constants.NonePackage)) {
195 // taking off the none package, if any
196 classNamesToTry.add(obfClassEntry.getSimpleName());
197 }
198 if (obfClassEntry.isInnerClass()) {
199 // try just the inner class name
200 classNamesToTry.add(obfClassEntry.getInnermostClassName());
201 }
202 return classNamesToTry;
203 }
204
205 public CtClass transformClass(CtClass c)
206 throws IOException, NotFoundException, CannotCompileException {
207
208 // we moved a lot of classes out of the default package into the none package
209 // make sure all the class references are consistent
210 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
211
212 // reconstruct inner classes
213 new InnerClassWriter(m_jarIndex).write(c);
214
215 // re-get the javassist handle since we changed class names
216 ClassEntry obfClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
217 String javaClassReconstructedName = Descriptor.toJavaName(obfClassEntry.getName());
218 ClassPool classPool = new ClassPool();
219 classPool.insertClassPath(new ByteArrayClassPath(javaClassReconstructedName, c.toBytecode()));
220 c = classPool.get(javaClassReconstructedName);
221
222 // check that the file is correct after inner class reconstruction (ie cause Javassist to fail fast if something is wrong)
223 assertClassName(c, obfClassEntry);
224
225 // do all kinds of deobfuscating transformations on the class
226 new BridgeMarker(m_jarIndex).markBridges(c);
227 new MethodParameterWriter(m_deobfuscatingTranslator).writeMethodArguments(c);
228 new LocalVariableRenamer(m_deobfuscatingTranslator).rename(c);
229 new ClassTranslator(m_deobfuscatingTranslator).translate(c);
230
231 return c;
232 }
233
234 private void assertClassName(CtClass c, ClassEntry obfClassEntry) {
235 String name1 = Descriptor.toJvmName(c.getName());
236 assert (name1.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name1);
237
238 String name2 = Descriptor.toJvmName(c.getClassFile().getName());
239 assert (name2.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name2);
240 }
241}
diff --git a/src/main/java/cuchaz/enigma/Util.java b/src/main/java/cuchaz/enigma/Util.java
new file mode 100644
index 00000000..1bcdb9ea
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/Util.java
@@ -0,0 +1,99 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import com.google.common.io.CharStreams;
14
15import java.awt.Desktop;
16import java.io.*;
17import java.net.URI;
18import java.net.URISyntaxException;
19import java.util.Arrays;
20import java.util.jar.JarFile;
21
22import javassist.CannotCompileException;
23import javassist.CtClass;
24import javassist.bytecode.Descriptor;
25
26public class Util {
27
28 public static int combineHashesOrdered(Object... objs) {
29 return combineHashesOrdered(Arrays.asList(objs));
30 }
31
32 public static int combineHashesOrdered(Iterable<Object> objs) {
33 final int prime = 67;
34 int result = 1;
35 for (Object obj : objs) {
36 result *= prime;
37 if (obj != null) {
38 result += obj.hashCode();
39 }
40 }
41 return result;
42 }
43
44 public static void closeQuietly(Closeable closeable) {
45 if (closeable != null) {
46 try {
47 closeable.close();
48 } catch (IOException ex) {
49 // just ignore any further exceptions
50 }
51 }
52 }
53
54 public static void closeQuietly(JarFile jarFile) {
55 // silly library should implement Closeable...
56 if (jarFile != null) {
57 try {
58 jarFile.close();
59 } catch (IOException ex) {
60 // just ignore any further exceptions
61 }
62 }
63 }
64
65 public static String readStreamToString(InputStream in) throws IOException {
66 return CharStreams.toString(new InputStreamReader(in, "UTF-8"));
67 }
68
69 public static String readResourceToString(String path) throws IOException {
70 InputStream in = Util.class.getResourceAsStream(path);
71 if (in == null) {
72 throw new IllegalArgumentException("Resource not found! " + path);
73 }
74 return readStreamToString(in);
75 }
76
77 public static void openUrl(String url) {
78 if (Desktop.isDesktopSupported()) {
79 Desktop desktop = Desktop.getDesktop();
80 try {
81 desktop.browse(new URI(url));
82 } catch (IOException ex) {
83 throw new Error(ex);
84 } catch (URISyntaxException ex) {
85 throw new IllegalArgumentException(ex);
86 }
87 }
88 }
89
90 public static void writeClass(CtClass c) {
91 String name = Descriptor.toJavaName(c.getName());
92 File file = new File(name + ".class");
93 try (FileOutputStream out = new FileOutputStream(file)) {
94 out.write(c.toBytecode());
95 } catch (IOException | CannotCompileException ex) {
96 throw new Error(ex);
97 }
98 }
99}
diff --git a/src/main/java/cuchaz/enigma/analysis/Access.java b/src/main/java/cuchaz/enigma/analysis/Access.java
new file mode 100644
index 00000000..877327f1
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/Access.java
@@ -0,0 +1,43 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.lang.reflect.Modifier;
14
15import javassist.CtBehavior;
16import javassist.CtField;
17
18public enum Access {
19
20 Public,
21 Protected,
22 Private;
23
24 public static Access get(CtBehavior behavior) {
25 return get(behavior.getModifiers());
26 }
27
28 public static Access get(CtField field) {
29 return get(field.getModifiers());
30 }
31
32 public static Access get(int modifiers) {
33 if (Modifier.isPublic(modifiers)) {
34 return Public;
35 } else if (Modifier.isProtected(modifiers)) {
36 return Protected;
37 } else if (Modifier.isPrivate(modifiers)) {
38 return Private;
39 }
40 // assume public by default
41 return Public;
42 }
43}
diff --git a/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java
new file mode 100644
index 00000000..776f0909
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java
@@ -0,0 +1,93 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.Sets;
14
15import java.util.Set;
16
17import javax.swing.tree.DefaultMutableTreeNode;
18import javax.swing.tree.TreeNode;
19
20import cuchaz.enigma.mapping.BehaviorEntry;
21import cuchaz.enigma.mapping.Entry;
22import cuchaz.enigma.mapping.Translator;
23
24public class BehaviorReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<BehaviorEntry, BehaviorEntry> {
25
26 private static final long serialVersionUID = -3658163700783307520L;
27
28 private Translator m_deobfuscatingTranslator;
29 private BehaviorEntry m_entry;
30 private EntryReference<BehaviorEntry, BehaviorEntry> m_reference;
31 private Access m_access;
32
33 public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, BehaviorEntry entry) {
34 m_deobfuscatingTranslator = deobfuscatingTranslator;
35 m_entry = entry;
36 m_reference = null;
37 }
38
39 public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<BehaviorEntry, BehaviorEntry> reference, Access access) {
40 m_deobfuscatingTranslator = deobfuscatingTranslator;
41 m_entry = reference.entry;
42 m_reference = reference;
43 m_access = access;
44 }
45
46 @Override
47 public BehaviorEntry getEntry() {
48 return m_entry;
49 }
50
51 @Override
52 public EntryReference<BehaviorEntry, BehaviorEntry> getReference() {
53 return m_reference;
54 }
55
56 @Override
57 public String toString() {
58 if (m_reference != null) {
59 return String.format("%s (%s)", m_deobfuscatingTranslator.translateEntry(m_reference.context), m_access);
60 }
61 return m_deobfuscatingTranslator.translateEntry(m_entry).toString();
62 }
63
64 public void load(JarIndex index, boolean recurse) {
65 // get all the child nodes
66 for (EntryReference<BehaviorEntry, BehaviorEntry> reference : index.getBehaviorReferences(m_entry)) {
67 add(new BehaviorReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_entry)));
68 }
69
70 if (recurse && children != null) {
71 for (Object child : children) {
72 if (child instanceof BehaviorReferenceTreeNode) {
73 BehaviorReferenceTreeNode node = (BehaviorReferenceTreeNode) child;
74
75 // don't recurse into ancestor
76 Set<Entry> ancestors = Sets.newHashSet();
77 TreeNode n = (TreeNode) node;
78 while (n.getParent() != null) {
79 n = n.getParent();
80 if (n instanceof BehaviorReferenceTreeNode) {
81 ancestors.add(((BehaviorReferenceTreeNode) n).getEntry());
82 }
83 }
84 if (ancestors.contains(node.getEntry())) {
85 continue;
86 }
87
88 node.load(index, true);
89 }
90 }
91 }
92 }
93}
diff --git a/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java b/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java
new file mode 100644
index 00000000..1df76255
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java
@@ -0,0 +1,43 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import cuchaz.enigma.mapping.EntryFactory;
14import cuchaz.enigma.mapping.MethodEntry;
15import javassist.CtClass;
16import javassist.CtMethod;
17import javassist.bytecode.AccessFlag;
18
19public class BridgeMarker {
20
21 private JarIndex m_jarIndex;
22
23 public BridgeMarker(JarIndex jarIndex) {
24 m_jarIndex = jarIndex;
25 }
26
27 public void markBridges(CtClass c) {
28
29 for (CtMethod method : c.getDeclaredMethods()) {
30 MethodEntry methodEntry = EntryFactory.getMethodEntry(method);
31
32 // is this a bridge method?
33 MethodEntry bridgedMethodEntry = m_jarIndex.getBridgedMethod(methodEntry);
34 if (bridgedMethodEntry != null) {
35
36 // it's a bridge method! add the bridge flag
37 int flags = method.getMethodInfo().getAccessFlags();
38 flags |= AccessFlag.BRIDGE;
39 method.getMethodInfo().setAccessFlags(flags);
40 }
41 }
42 }
43}
diff --git a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
new file mode 100644
index 00000000..8f8986c1
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
@@ -0,0 +1,78 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.Lists;
14
15import java.util.List;
16
17import javax.swing.tree.DefaultMutableTreeNode;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.MethodEntry;
21import cuchaz.enigma.mapping.Translator;
22
23public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
24
25 private static final long serialVersionUID = 3112703459157851912L;
26
27 private Translator m_deobfuscatingTranslator;
28 private ClassEntry m_entry;
29
30 public ClassImplementationsTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) {
31 m_deobfuscatingTranslator = deobfuscatingTranslator;
32 m_entry = entry;
33 }
34
35 public ClassEntry getClassEntry() {
36 return m_entry;
37 }
38
39 public String getDeobfClassName() {
40 return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
41 }
42
43 @Override
44 public String toString() {
45 String className = getDeobfClassName();
46 if (className == null) {
47 className = m_entry.getClassName();
48 }
49 return className;
50 }
51
52 public void load(JarIndex index) {
53 // get all method implementations
54 List<ClassImplementationsTreeNode> nodes = Lists.newArrayList();
55 for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) {
56 nodes.add(new ClassImplementationsTreeNode(m_deobfuscatingTranslator, new ClassEntry(implementingClassName)));
57 }
58
59 // add them to this node
60 nodes.forEach(this::add);
61 }
62
63 public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) {
64 // is this the node?
65 if (node.m_entry.equals(entry)) {
66 return node;
67 }
68
69 // recurse
70 for (int i = 0; i < node.getChildCount(); i++) {
71 ClassImplementationsTreeNode foundNode = findNode((ClassImplementationsTreeNode) node.getChildAt(i), entry);
72 if (foundNode != null) {
73 return foundNode;
74 }
75 }
76 return null;
77 }
78}
diff --git a/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java
new file mode 100644
index 00000000..ca2b8215
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java
@@ -0,0 +1,83 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.Lists;
14
15import java.util.List;
16
17import javax.swing.tree.DefaultMutableTreeNode;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.Translator;
21
22public class ClassInheritanceTreeNode extends DefaultMutableTreeNode {
23
24 private static final long serialVersionUID = 4432367405826178490L;
25
26 private Translator m_deobfuscatingTranslator;
27 private String m_obfClassName;
28
29 public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) {
30 m_deobfuscatingTranslator = deobfuscatingTranslator;
31 m_obfClassName = obfClassName;
32 }
33
34 public String getObfClassName() {
35 return m_obfClassName;
36 }
37
38 public String getDeobfClassName() {
39 return m_deobfuscatingTranslator.translateClass(m_obfClassName);
40 }
41
42 @Override
43 public String toString() {
44 String deobfClassName = getDeobfClassName();
45 if (deobfClassName != null) {
46 return deobfClassName;
47 }
48 return m_obfClassName;
49 }
50
51 public void load(TranslationIndex ancestries, boolean recurse) {
52 // get all the child nodes
53 List<ClassInheritanceTreeNode> nodes = Lists.newArrayList();
54 for (ClassEntry subclassEntry : ancestries.getSubclass(new ClassEntry(m_obfClassName))) {
55 nodes.add(new ClassInheritanceTreeNode(m_deobfuscatingTranslator, subclassEntry.getName()));
56 }
57
58 // add them to this node
59 nodes.forEach(this::add);
60
61 if (recurse) {
62 for (ClassInheritanceTreeNode node : nodes) {
63 node.load(ancestries, true);
64 }
65 }
66 }
67
68 public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) {
69 // is this the node?
70 if (node.getObfClassName().equals(entry.getName())) {
71 return node;
72 }
73
74 // recurse
75 for (int i = 0; i < node.getChildCount(); i++) {
76 ClassInheritanceTreeNode foundNode = findNode((ClassInheritanceTreeNode) node.getChildAt(i), entry);
77 if (foundNode != null) {
78 return foundNode;
79 }
80 }
81 return null;
82 }
83}
diff --git a/src/main/java/cuchaz/enigma/analysis/EntryReference.java b/src/main/java/cuchaz/enigma/analysis/EntryReference.java
new file mode 100644
index 00000000..eb58388c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/EntryReference.java
@@ -0,0 +1,126 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.Arrays;
14import java.util.List;
15
16import cuchaz.enigma.Util;
17import cuchaz.enigma.mapping.ClassEntry;
18import cuchaz.enigma.mapping.ConstructorEntry;
19import cuchaz.enigma.mapping.Entry;
20
21public class EntryReference<E extends Entry, C extends Entry> {
22
23 private static final List<String> ConstructorNonNames = Arrays.asList("this", "super", "static");
24 public E entry;
25 public C context;
26
27 private boolean m_isNamed;
28
29 public EntryReference(E entry, String sourceName) {
30 this(entry, sourceName, null);
31 }
32
33 public EntryReference(E entry, String sourceName, C context) {
34 if (entry == null) {
35 throw new IllegalArgumentException("Entry cannot be null!");
36 }
37
38 this.entry = entry;
39 this.context = context;
40
41 m_isNamed = sourceName != null && sourceName.length() > 0;
42 if (entry instanceof ConstructorEntry && ConstructorNonNames.contains(sourceName)) {
43 m_isNamed = false;
44 }
45 }
46
47 public EntryReference(E entry, C context, EntryReference<E, C> other) {
48 this.entry = entry;
49 this.context = context;
50 m_isNamed = other.m_isNamed;
51 }
52
53 public ClassEntry getLocationClassEntry() {
54 if (context != null) {
55 return context.getClassEntry();
56 }
57 return entry.getClassEntry();
58 }
59
60 public boolean isNamed() {
61 return m_isNamed;
62 }
63
64 public Entry getNameableEntry() {
65 if (entry instanceof ConstructorEntry) {
66 // renaming a constructor really means renaming the class
67 return entry.getClassEntry();
68 }
69 return entry;
70 }
71
72 public String getNamableName() {
73 if (getNameableEntry() instanceof ClassEntry) {
74 ClassEntry classEntry = (ClassEntry) getNameableEntry();
75 if (classEntry.isInnerClass()) {
76 // make sure we only rename the inner class name
77 return classEntry.getInnermostClassName();
78 }
79 }
80
81 return getNameableEntry().getName();
82 }
83
84 @Override
85 public int hashCode() {
86 if (context != null) {
87 return Util.combineHashesOrdered(entry.hashCode(), context.hashCode());
88 }
89 return entry.hashCode();
90 }
91
92 @Override
93 public boolean equals(Object other) {
94 if (other instanceof EntryReference) {
95 return equals((EntryReference<?, ?>) other);
96 }
97 return false;
98 }
99
100 public boolean equals(EntryReference<?, ?> other) {
101 // check entry first
102 boolean isEntrySame = entry.equals(other.entry);
103 if (!isEntrySame) {
104 return false;
105 }
106
107 // check caller
108 if (context == null && other.context == null) {
109 return true;
110 } else if (context != null && other.context != null) {
111 return context.equals(other.context);
112 }
113 return false;
114 }
115
116 @Override
117 public String toString() {
118 StringBuilder buf = new StringBuilder();
119 buf.append(entry);
120 if (context != null) {
121 buf.append(" called from ");
122 buf.append(context);
123 }
124 return buf.toString();
125 }
126}
diff --git a/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java b/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java
new file mode 100644
index 00000000..b99537cc
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java
@@ -0,0 +1,184 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.Lists;
14import com.google.common.collect.Multimap;
15import com.google.common.collect.Sets;
16
17import java.util.AbstractMap;
18import java.util.List;
19import java.util.Map;
20import java.util.Set;
21
22import cuchaz.enigma.mapping.*;
23
24public class EntryRenamer {
25
26 public static <T> void renameClassesInSet(Map<String, String> renames, Set<T> set) {
27 List<T> entries = Lists.newArrayList();
28 for (T val : set) {
29 entries.add(renameClassesInThing(renames, val));
30 }
31 set.clear();
32 set.addAll(entries);
33 }
34
35 public static <Key, Val> void renameClassesInMap(Map<String, String> renames, Map<Key, Val> map) {
36 // for each key/value pair...
37 Set<Map.Entry<Key, Val>> entriesToAdd = Sets.newHashSet();
38 for (Map.Entry<Key, Val> entry : map.entrySet()) {
39 entriesToAdd.add(new AbstractMap.SimpleEntry<Key, Val>(
40 renameClassesInThing(renames, entry.getKey()),
41 renameClassesInThing(renames, entry.getValue())
42 ));
43 }
44 map.clear();
45 for (Map.Entry<Key, Val> entry : entriesToAdd) {
46 map.put(entry.getKey(), entry.getValue());
47 }
48 }
49
50 public static <Key, Val> void renameClassesInMultimap(Map<String, String> renames, Multimap<Key, Val> map) {
51 // for each key/value pair...
52 Set<Map.Entry<Key, Val>> entriesToAdd = Sets.newHashSet();
53 for (Map.Entry<Key, Val> entry : map.entries()) {
54 entriesToAdd.add(new AbstractMap.SimpleEntry<Key, Val>(
55 renameClassesInThing(renames, entry.getKey()),
56 renameClassesInThing(renames, entry.getValue())
57 ));
58 }
59 map.clear();
60 for (Map.Entry<Key, Val> entry : entriesToAdd) {
61 map.put(entry.getKey(), entry.getValue());
62 }
63 }
64
65 public static <Key, Val> void renameMethodsInMultimap(Map<MethodEntry, MethodEntry> renames, Multimap<Key, Val> map) {
66 // for each key/value pair...
67 Set<Map.Entry<Key, Val>> entriesToAdd = Sets.newHashSet();
68 for (Map.Entry<Key, Val> entry : map.entries()) {
69 entriesToAdd.add(new AbstractMap.SimpleEntry<Key, Val>(
70 renameMethodsInThing(renames, entry.getKey()),
71 renameMethodsInThing(renames, entry.getValue())
72 ));
73 }
74 map.clear();
75 for (Map.Entry<Key, Val> entry : entriesToAdd) {
76 map.put(entry.getKey(), entry.getValue());
77 }
78 }
79
80 public static <Key, Val> void renameMethodsInMap(Map<MethodEntry, MethodEntry> renames, Map<Key, Val> map) {
81 // for each key/value pair...
82 Set<Map.Entry<Key, Val>> entriesToAdd = Sets.newHashSet();
83 for (Map.Entry<Key, Val> entry : map.entrySet()) {
84 entriesToAdd.add(new AbstractMap.SimpleEntry<Key, Val>(
85 renameMethodsInThing(renames, entry.getKey()),
86 renameMethodsInThing(renames, entry.getValue())
87 ));
88 }
89 map.clear();
90 for (Map.Entry<Key, Val> entry : entriesToAdd) {
91 map.put(entry.getKey(), entry.getValue());
92 }
93 }
94
95 @SuppressWarnings("unchecked")
96 public static <T> T renameMethodsInThing(Map<MethodEntry, MethodEntry> renames, T thing) {
97 if (thing instanceof MethodEntry) {
98 MethodEntry methodEntry = (MethodEntry) thing;
99 MethodEntry newMethodEntry = renames.get(methodEntry);
100 if (newMethodEntry != null) {
101 return (T) new MethodEntry(
102 methodEntry.getClassEntry(),
103 newMethodEntry.getName(),
104 methodEntry.getSignature()
105 );
106 }
107 return thing;
108 } else if (thing instanceof ArgumentEntry) {
109 ArgumentEntry argumentEntry = (ArgumentEntry) thing;
110 return (T) new ArgumentEntry(
111 renameMethodsInThing(renames, argumentEntry.getBehaviorEntry()),
112 argumentEntry.getIndex(),
113 argumentEntry.getName()
114 );
115 } else if (thing instanceof EntryReference) {
116 EntryReference<Entry, Entry> reference = (EntryReference<Entry, Entry>) thing;
117 reference.entry = renameMethodsInThing(renames, reference.entry);
118 reference.context = renameMethodsInThing(renames, reference.context);
119 return thing;
120 }
121 return thing;
122 }
123
124 @SuppressWarnings("unchecked")
125 public static <T> T renameClassesInThing(final Map<String, String> renames, T thing) {
126 if (thing instanceof String) {
127 String stringEntry = (String) thing;
128 if (renames.containsKey(stringEntry)) {
129 return (T) renames.get(stringEntry);
130 }
131 } else if (thing instanceof ClassEntry) {
132 ClassEntry classEntry = (ClassEntry) thing;
133 return (T) new ClassEntry(renameClassesInThing(renames, classEntry.getClassName()));
134 } else if (thing instanceof FieldEntry) {
135 FieldEntry fieldEntry = (FieldEntry) thing;
136 return (T) new FieldEntry(
137 renameClassesInThing(renames, fieldEntry.getClassEntry()),
138 fieldEntry.getName(),
139 renameClassesInThing(renames, fieldEntry.getType())
140 );
141 } else if (thing instanceof ConstructorEntry) {
142 ConstructorEntry constructorEntry = (ConstructorEntry) thing;
143 return (T) new ConstructorEntry(
144 renameClassesInThing(renames, constructorEntry.getClassEntry()),
145 renameClassesInThing(renames, constructorEntry.getSignature())
146 );
147 } else if (thing instanceof MethodEntry) {
148 MethodEntry methodEntry = (MethodEntry) thing;
149 return (T) new MethodEntry(
150 renameClassesInThing(renames, methodEntry.getClassEntry()),
151 methodEntry.getName(),
152 renameClassesInThing(renames, methodEntry.getSignature())
153 );
154 } else if (thing instanceof ArgumentEntry) {
155 ArgumentEntry argumentEntry = (ArgumentEntry) thing;
156 return (T) new ArgumentEntry(
157 renameClassesInThing(renames, argumentEntry.getBehaviorEntry()),
158 argumentEntry.getIndex(),
159 argumentEntry.getName()
160 );
161 } else if (thing instanceof EntryReference) {
162 EntryReference<Entry, Entry> reference = (EntryReference<Entry, Entry>) thing;
163 reference.entry = renameClassesInThing(renames, reference.entry);
164 reference.context = renameClassesInThing(renames, reference.context);
165 return thing;
166 } else if (thing instanceof Signature) {
167 return (T) new Signature((Signature) thing, new ClassNameReplacer() {
168 @Override
169 public String replace(String className) {
170 return renameClassesInThing(renames, className);
171 }
172 });
173 } else if (thing instanceof Type) {
174 return (T) new Type((Type) thing, new ClassNameReplacer() {
175 @Override
176 public String replace(String className) {
177 return renameClassesInThing(renames, className);
178 }
179 });
180 }
181
182 return thing;
183 }
184}
diff --git a/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
new file mode 100644
index 00000000..4b302e05
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
@@ -0,0 +1,81 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import javax.swing.tree.DefaultMutableTreeNode;
14
15import cuchaz.enigma.mapping.BehaviorEntry;
16import cuchaz.enigma.mapping.FieldEntry;
17import cuchaz.enigma.mapping.Translator;
18
19public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<FieldEntry, BehaviorEntry> {
20
21 private static final long serialVersionUID = -7934108091928699835L;
22
23 private Translator m_deobfuscatingTranslator;
24 private FieldEntry m_entry;
25 private EntryReference<FieldEntry, BehaviorEntry> m_reference;
26 private Access m_access;
27
28 public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) {
29 m_deobfuscatingTranslator = deobfuscatingTranslator;
30 m_entry = entry;
31 m_reference = null;
32 }
33
34 private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<FieldEntry, BehaviorEntry> reference, Access access) {
35 m_deobfuscatingTranslator = deobfuscatingTranslator;
36 m_entry = reference.entry;
37 m_reference = reference;
38 m_access = access;
39 }
40
41 @Override
42 public FieldEntry getEntry() {
43 return m_entry;
44 }
45
46 @Override
47 public EntryReference<FieldEntry, BehaviorEntry> getReference() {
48 return m_reference;
49 }
50
51 @Override
52 public String toString() {
53 if (m_reference != null) {
54 return String.format("%s (%s)", m_deobfuscatingTranslator.translateEntry(m_reference.context), m_access);
55 }
56 return m_deobfuscatingTranslator.translateEntry(m_entry).toString();
57 }
58
59 public void load(JarIndex index, boolean recurse) {
60 // get all the child nodes
61 if (m_reference == null) {
62 for (EntryReference<FieldEntry, BehaviorEntry> reference : index.getFieldReferences(m_entry)) {
63 add(new FieldReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_entry)));
64 }
65 } else {
66 for (EntryReference<BehaviorEntry, BehaviorEntry> reference : index.getBehaviorReferences(m_reference.context)) {
67 add(new BehaviorReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_reference.context)));
68 }
69 }
70
71 if (recurse && children != null) {
72 for (Object node : children) {
73 if (node instanceof BehaviorReferenceTreeNode) {
74 ((BehaviorReferenceTreeNode) node).load(index, true);
75 } else if (node instanceof FieldReferenceTreeNode) {
76 ((FieldReferenceTreeNode) node).load(index, true);
77 }
78 }
79 }
80 }
81}
diff --git a/src/main/java/cuchaz/enigma/analysis/JarClassIterator.java b/src/main/java/cuchaz/enigma/analysis/JarClassIterator.java
new file mode 100644
index 00000000..17a17151
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/JarClassIterator.java
@@ -0,0 +1,136 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.Lists;
14
15import java.io.ByteArrayOutputStream;
16import java.io.IOException;
17import java.io.InputStream;
18import java.util.Enumeration;
19import java.util.Iterator;
20import java.util.List;
21import java.util.jar.JarEntry;
22import java.util.jar.JarFile;
23
24import cuchaz.enigma.Constants;
25import cuchaz.enigma.mapping.ClassEntry;
26import javassist.ByteArrayClassPath;
27import javassist.ClassPool;
28import javassist.CtClass;
29import javassist.NotFoundException;
30import javassist.bytecode.Descriptor;
31
32public class JarClassIterator implements Iterator<CtClass> {
33
34 private JarFile m_jar;
35 private Iterator<JarEntry> m_iter;
36
37 public JarClassIterator(JarFile jar) {
38 m_jar = jar;
39
40 // get the jar entries that correspond to classes
41 List<JarEntry> classEntries = Lists.newArrayList();
42 Enumeration<JarEntry> entries = m_jar.entries();
43 while (entries.hasMoreElements()) {
44 JarEntry entry = entries.nextElement();
45
46 // is this a class file?
47 if (entry.getName().endsWith(".class")) {
48 classEntries.add(entry);
49 }
50 }
51 m_iter = classEntries.iterator();
52 }
53
54 @Override
55 public boolean hasNext() {
56 return m_iter.hasNext();
57 }
58
59 @Override
60 public CtClass next() {
61 JarEntry entry = m_iter.next();
62 try {
63 return getClass(m_jar, entry);
64 } catch (IOException | NotFoundException ex) {
65 throw new Error("Unable to load class: " + entry.getName());
66 }
67 }
68
69 @Override
70 public void remove() {
71 throw new UnsupportedOperationException();
72 }
73
74 public static List<ClassEntry> getClassEntries(JarFile jar) {
75 List<ClassEntry> classEntries = Lists.newArrayList();
76 Enumeration<JarEntry> entries = jar.entries();
77 while (entries.hasMoreElements()) {
78 JarEntry entry = entries.nextElement();
79
80 // is this a class file?
81 if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
82 classEntries.add(getClassEntry(entry));
83 }
84 }
85 return classEntries;
86 }
87
88 public static Iterable<CtClass> classes(final JarFile jar) {
89 return new Iterable<CtClass>() {
90 @Override
91 public Iterator<CtClass> iterator() {
92 return new JarClassIterator(jar);
93 }
94 };
95 }
96
97 public static CtClass getClass(JarFile jar, ClassEntry classEntry) {
98 try {
99 return getClass(jar, new JarEntry(classEntry.getName() + ".class"));
100 } catch (IOException | NotFoundException ex) {
101 throw new Error("Unable to load class: " + classEntry.getName());
102 }
103 }
104
105 private static CtClass getClass(JarFile jar, JarEntry entry) throws IOException, NotFoundException {
106 // read the class into a buffer
107 ByteArrayOutputStream bos = new ByteArrayOutputStream();
108 byte[] buf = new byte[Constants.KiB];
109 int totalNumBytesRead = 0;
110 InputStream in = jar.getInputStream(entry);
111 while (in.available() > 0) {
112 int numBytesRead = in.read(buf);
113 if (numBytesRead < 0) {
114 break;
115 }
116 bos.write(buf, 0, numBytesRead);
117
118 // sanity checking
119 totalNumBytesRead += numBytesRead;
120 if (totalNumBytesRead > Constants.MiB) {
121 throw new Error("Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!");
122 }
123 }
124
125 // get a javassist handle for the class
126 String className = Descriptor.toJavaName(getClassEntry(entry).getName());
127 ClassPool classPool = new ClassPool();
128 classPool.appendSystemPath();
129 classPool.insertClassPath(new ByteArrayClassPath(className, bos.toByteArray()));
130 return classPool.get(className);
131 }
132
133 private static ClassEntry getClassEntry(JarEntry entry) {
134 return new ClassEntry(entry.getName().substring(0, entry.getName().length() - ".class".length()));
135 }
136}
diff --git a/src/main/java/cuchaz/enigma/analysis/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/JarIndex.java
new file mode 100644
index 00000000..848d8511
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/JarIndex.java
@@ -0,0 +1,802 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.*;
14
15import java.lang.reflect.Modifier;
16import java.util.*;
17import java.util.jar.JarFile;
18
19import cuchaz.enigma.Constants;
20import cuchaz.enigma.bytecode.ClassRenamer;
21import cuchaz.enigma.mapping.*;
22import cuchaz.enigma.mapping.Translator;
23import javassist.*;
24import javassist.bytecode.*;
25import javassist.expr.*;
26
27public class JarIndex {
28
29 private Set<ClassEntry> m_obfClassEntries;
30 private TranslationIndex m_translationIndex;
31 private Map<Entry, Access> m_access;
32 private Multimap<ClassEntry, FieldEntry> m_fields;
33 private Multimap<ClassEntry, BehaviorEntry> m_behaviors;
34 private Multimap<String, MethodEntry> m_methodImplementations;
35 private Multimap<BehaviorEntry, EntryReference<BehaviorEntry, BehaviorEntry>> m_behaviorReferences;
36 private Multimap<FieldEntry, EntryReference<FieldEntry, BehaviorEntry>> m_fieldReferences;
37 private Multimap<ClassEntry, ClassEntry> m_innerClassesByOuter;
38 private Map<ClassEntry, ClassEntry> m_outerClassesByInner;
39 private Map<ClassEntry, BehaviorEntry> m_anonymousClasses;
40 private Map<MethodEntry, MethodEntry> m_bridgedMethods;
41
42 public JarIndex() {
43 m_obfClassEntries = Sets.newHashSet();
44 m_translationIndex = new TranslationIndex();
45 m_access = Maps.newHashMap();
46 m_fields = HashMultimap.create();
47 m_behaviors = HashMultimap.create();
48 m_methodImplementations = HashMultimap.create();
49 m_behaviorReferences = HashMultimap.create();
50 m_fieldReferences = HashMultimap.create();
51 m_innerClassesByOuter = HashMultimap.create();
52 m_outerClassesByInner = Maps.newHashMap();
53 m_anonymousClasses = Maps.newHashMap();
54 m_bridgedMethods = Maps.newHashMap();
55 }
56
57 public void indexJar(JarFile jar, boolean buildInnerClasses) {
58
59 // step 1: read the class names
60 for (ClassEntry classEntry : JarClassIterator.getClassEntries(jar)) {
61 if (classEntry.isInDefaultPackage()) {
62 // move out of default package
63 classEntry = new ClassEntry(Constants.NonePackage + "/" + classEntry.getName());
64 }
65 m_obfClassEntries.add(classEntry);
66 }
67
68 // step 2: index field/method/constructor access
69 for (CtClass c : JarClassIterator.classes(jar)) {
70 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
71 for (CtField field : c.getDeclaredFields()) {
72 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
73 m_access.put(fieldEntry, Access.get(field));
74 m_fields.put(fieldEntry.getClassEntry(), fieldEntry);
75 }
76 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
77 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
78 m_access.put(behaviorEntry, Access.get(behavior));
79 m_behaviors.put(behaviorEntry.getClassEntry(), behaviorEntry);
80 }
81 }
82
83 // step 3: index extends, implements, fields, and methods
84 for (CtClass c : JarClassIterator.classes(jar)) {
85 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
86 m_translationIndex.indexClass(c);
87 String className = Descriptor.toJvmName(c.getName());
88 for (String interfaceName : c.getClassFile().getInterfaces()) {
89 className = Descriptor.toJvmName(className);
90 interfaceName = Descriptor.toJvmName(interfaceName);
91 if (className.equals(interfaceName)) {
92 throw new IllegalArgumentException("Class cannot be its own interface! " + className);
93 }
94 }
95 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
96 indexBehavior(behavior);
97 }
98 }
99
100 // step 4: index field, method, constructor references
101 for (CtClass c : JarClassIterator.classes(jar)) {
102 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
103 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
104 indexBehaviorReferences(behavior);
105 }
106 }
107
108 if (buildInnerClasses) {
109
110 // step 5: index inner classes and anonymous classes
111 for (CtClass c : JarClassIterator.classes(jar)) {
112 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
113 ClassEntry innerClassEntry = EntryFactory.getClassEntry(c);
114 ClassEntry outerClassEntry = findOuterClass(c);
115 if (outerClassEntry != null) {
116 m_innerClassesByOuter.put(outerClassEntry, innerClassEntry);
117 boolean innerWasAdded = m_outerClassesByInner.put(innerClassEntry, outerClassEntry) == null;
118 assert (innerWasAdded);
119
120 BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassEntry);
121 if (enclosingBehavior != null) {
122 m_anonymousClasses.put(innerClassEntry, enclosingBehavior);
123
124 // DEBUG
125 //System.out.println("ANONYMOUS: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
126 } else {
127 // DEBUG
128 //System.out.println("INNER: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
129 }
130 }
131 }
132
133 // step 6: update other indices with inner class info
134 Map<String, String> renames = Maps.newHashMap();
135 for (ClassEntry innerClassEntry : m_innerClassesByOuter.values()) {
136 String newName = innerClassEntry.buildClassEntry(getObfClassChain(innerClassEntry)).getName();
137 if (!innerClassEntry.getName().equals(newName)) {
138 // DEBUG
139 //System.out.println("REPLACE: " + innerClassEntry.getName() + " WITH " + newName);
140 renames.put(innerClassEntry.getName(), newName);
141 }
142 }
143 EntryRenamer.renameClassesInSet(renames, m_obfClassEntries);
144 m_translationIndex.renameClasses(renames);
145 EntryRenamer.renameClassesInMultimap(renames, m_methodImplementations);
146 EntryRenamer.renameClassesInMultimap(renames, m_behaviorReferences);
147 EntryRenamer.renameClassesInMultimap(renames, m_fieldReferences);
148 EntryRenamer.renameClassesInMap(renames, m_access);
149 }
150 }
151
152 private void indexBehavior(CtBehavior behavior) {
153 // get the behavior entry
154 final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
155 if (behaviorEntry instanceof MethodEntry) {
156 MethodEntry methodEntry = (MethodEntry) behaviorEntry;
157
158 // index implementation
159 m_methodImplementations.put(behaviorEntry.getClassName(), methodEntry);
160
161 // look for bridge and bridged methods
162 CtMethod bridgedMethod = getBridgedMethod((CtMethod) behavior);
163 if (bridgedMethod != null) {
164 m_bridgedMethods.put(methodEntry, EntryFactory.getMethodEntry(bridgedMethod));
165 }
166 }
167 // looks like we don't care about constructors here
168 }
169
170 private void indexBehaviorReferences(CtBehavior behavior) {
171 // index method calls
172 final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
173 try {
174 behavior.instrument(new ExprEditor() {
175 @Override
176 public void edit(MethodCall call) {
177 MethodEntry calledMethodEntry = EntryFactory.getMethodEntry(call);
178 ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledMethodEntry);
179 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) {
180 calledMethodEntry = new MethodEntry(
181 resolvedClassEntry,
182 calledMethodEntry.getName(),
183 calledMethodEntry.getSignature()
184 );
185 }
186 EntryReference<BehaviorEntry, BehaviorEntry> reference = new EntryReference<BehaviorEntry, BehaviorEntry>(
187 calledMethodEntry,
188 call.getMethodName(),
189 behaviorEntry
190 );
191 m_behaviorReferences.put(calledMethodEntry, reference);
192 }
193
194 @Override
195 public void edit(FieldAccess call) {
196 FieldEntry calledFieldEntry = EntryFactory.getFieldEntry(call);
197 ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledFieldEntry);
198 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) {
199 calledFieldEntry = new FieldEntry(calledFieldEntry, resolvedClassEntry);
200 }
201 EntryReference<FieldEntry, BehaviorEntry> reference = new EntryReference<FieldEntry, BehaviorEntry>(
202 calledFieldEntry,
203 call.getFieldName(),
204 behaviorEntry
205 );
206 m_fieldReferences.put(calledFieldEntry, reference);
207 }
208
209 @Override
210 public void edit(ConstructorCall call) {
211 ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
212 EntryReference<BehaviorEntry, BehaviorEntry> reference = new EntryReference<BehaviorEntry, BehaviorEntry>(
213 calledConstructorEntry,
214 call.getMethodName(),
215 behaviorEntry
216 );
217 m_behaviorReferences.put(calledConstructorEntry, reference);
218 }
219
220 @Override
221 public void edit(NewExpr call) {
222 ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
223 EntryReference<BehaviorEntry, BehaviorEntry> reference = new EntryReference<BehaviorEntry, BehaviorEntry>(
224 calledConstructorEntry,
225 call.getClassName(),
226 behaviorEntry
227 );
228 m_behaviorReferences.put(calledConstructorEntry, reference);
229 }
230 });
231 } catch (CannotCompileException ex) {
232 throw new Error(ex);
233 }
234 }
235
236 private CtMethod getBridgedMethod(CtMethod method) {
237
238 // bridge methods just call another method, cast it to the return type, and return the result
239 // let's see if we can detect this scenario
240
241 // skip non-synthetic methods
242 if ((method.getModifiers() & AccessFlag.SYNTHETIC) == 0) {
243 return null;
244 }
245
246 // get all the called methods
247 final List<MethodCall> methodCalls = Lists.newArrayList();
248 try {
249 method.instrument(new ExprEditor() {
250 @Override
251 public void edit(MethodCall call) {
252 methodCalls.add(call);
253 }
254 });
255 } catch (CannotCompileException ex) {
256 // this is stupid... we're not even compiling anything
257 throw new Error(ex);
258 }
259
260 // is there just one?
261 if (methodCalls.size() != 1) {
262 return null;
263 }
264 MethodCall call = methodCalls.get(0);
265
266 try {
267 // we have a bridge method!
268 return call.getMethod();
269 } catch (NotFoundException ex) {
270 // can't find the type? not a bridge method
271 return null;
272 }
273 }
274
275 private ClassEntry findOuterClass(CtClass c) {
276
277 ClassEntry classEntry = EntryFactory.getClassEntry(c);
278
279 // does this class already have an outer class?
280 if (classEntry.isInnerClass()) {
281 return classEntry.getOuterClassEntry();
282 }
283
284 // inner classes:
285 // have constructors that can (illegally) set synthetic fields
286 // the outer class is the only class that calls constructors
287
288 // use the synthetic fields to find the synthetic constructors
289 for (CtConstructor constructor : c.getDeclaredConstructors()) {
290 Set<String> syntheticFieldTypes = Sets.newHashSet();
291 if (!isIllegalConstructor(syntheticFieldTypes, constructor)) {
292 continue;
293 }
294
295 ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
296
297 // gather the classes from the illegally-set synthetic fields
298 Set<ClassEntry> illegallySetClasses = Sets.newHashSet();
299 for (String type : syntheticFieldTypes) {
300 if (type.startsWith("L")) {
301 ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1));
302 if (isSaneOuterClass(outerClassEntry, classEntry)) {
303 illegallySetClasses.add(outerClassEntry);
304 }
305 }
306 }
307
308 // who calls this constructor?
309 Set<ClassEntry> callerClasses = Sets.newHashSet();
310 for (EntryReference<BehaviorEntry, BehaviorEntry> reference : getBehaviorReferences(constructorEntry)) {
311
312 // make sure it's not a call to super
313 if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) {
314
315 // is the entry a superclass of the context?
316 ClassEntry calledClassEntry = reference.entry.getClassEntry();
317 ClassEntry superclassEntry = m_translationIndex.getSuperclass(reference.context.getClassEntry());
318 if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) {
319 // it's a super call, skip
320 continue;
321 }
322 }
323
324 if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) {
325 callerClasses.add(reference.context.getClassEntry());
326 }
327 }
328
329 // do we have an answer yet?
330 if (callerClasses.isEmpty()) {
331 if (illegallySetClasses.size() == 1) {
332 return illegallySetClasses.iterator().next();
333 } else {
334 System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry));
335 }
336 } else {
337 if (callerClasses.size() == 1) {
338 return callerClasses.iterator().next();
339 } else {
340 // multiple callers, do the illegally set classes narrow it down?
341 Set<ClassEntry> intersection = Sets.newHashSet(callerClasses);
342 intersection.retainAll(illegallySetClasses);
343 if (intersection.size() == 1) {
344 return intersection.iterator().next();
345 } else {
346 System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses));
347 }
348 }
349 }
350 }
351
352 return null;
353 }
354
355 private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) {
356
357 // clearly this would be silly
358 if (outerClassEntry.equals(innerClassEntry)) {
359 return false;
360 }
361
362 // is the outer class in the jar?
363 return m_obfClassEntries.contains(outerClassEntry);
364
365 }
366
367 @SuppressWarnings("unchecked")
368 private boolean isIllegalConstructor(Set<String> syntheticFieldTypes, CtConstructor constructor) {
369
370 // illegal constructors only set synthetic member fields, then call super()
371 String className = constructor.getDeclaringClass().getName();
372
373 // collect all the field accesses, constructor calls, and method calls
374 final List<FieldAccess> illegalFieldWrites = Lists.newArrayList();
375 final List<ConstructorCall> constructorCalls = Lists.newArrayList();
376 try {
377 constructor.instrument(new ExprEditor() {
378 @Override
379 public void edit(FieldAccess fieldAccess) {
380 if (fieldAccess.isWriter() && constructorCalls.isEmpty()) {
381 illegalFieldWrites.add(fieldAccess);
382 }
383 }
384
385 @Override
386 public void edit(ConstructorCall constructorCall) {
387 constructorCalls.add(constructorCall);
388 }
389 });
390 } catch (CannotCompileException ex) {
391 // we're not compiling anything... this is stupid
392 throw new Error(ex);
393 }
394
395 // are there any illegal field writes?
396 if (illegalFieldWrites.isEmpty()) {
397 return false;
398 }
399
400 // are all the writes to synthetic fields?
401 for (FieldAccess fieldWrite : illegalFieldWrites) {
402
403 // all illegal writes have to be to the local class
404 if (!fieldWrite.getClassName().equals(className)) {
405 System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName()));
406 return false;
407 }
408
409 // find the field
410 FieldInfo fieldInfo = null;
411 for (FieldInfo info : (List<FieldInfo>) constructor.getDeclaringClass().getClassFile().getFields()) {
412 if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) {
413 fieldInfo = info;
414 break;
415 }
416 }
417 if (fieldInfo == null) {
418 // field is in a superclass or something, can't be a local synthetic member
419 return false;
420 }
421
422 // is this field synthetic?
423 boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0;
424 if (isSynthetic) {
425 syntheticFieldTypes.add(fieldInfo.getDescriptor());
426 } else {
427 System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName()));
428 return false;
429 }
430 }
431
432 // we passed all the tests!
433 return true;
434 }
435
436 private BehaviorEntry isAnonymousClass(CtClass c, ClassEntry outerClassEntry) {
437
438 // is this class already marked anonymous?
439 EnclosingMethodAttribute enclosingMethodAttribute = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag);
440 if (enclosingMethodAttribute != null) {
441 if (enclosingMethodAttribute.methodIndex() > 0) {
442 return EntryFactory.getBehaviorEntry(
443 Descriptor.toJvmName(enclosingMethodAttribute.className()),
444 enclosingMethodAttribute.methodName(),
445 enclosingMethodAttribute.methodDescriptor()
446 );
447 } else {
448 // an attribute but no method? assume not anonymous
449 return null;
450 }
451 }
452
453 // if there's an inner class attribute, but not an enclosing method attribute, then it's not anonymous
454 InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
455 if (innerClassesAttribute != null) {
456 return null;
457 }
458
459 ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
460
461 // anonymous classes:
462 // can't be abstract
463 // have only one constructor
464 // it's called exactly once by the outer class
465 // the type the instance is assigned to can't be this type
466
467 // is abstract?
468 if (Modifier.isAbstract(c.getModifiers())) {
469 return null;
470 }
471
472 // is there exactly one constructor?
473 if (c.getDeclaredConstructors().length != 1) {
474 return null;
475 }
476 CtConstructor constructor = c.getDeclaredConstructors()[0];
477
478 // is this constructor called exactly once?
479 ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
480 Collection<EntryReference<BehaviorEntry, BehaviorEntry>> references = getBehaviorReferences(constructorEntry);
481 if (references.size() != 1) {
482 return null;
483 }
484
485 // does the caller use this type?
486 BehaviorEntry caller = references.iterator().next().context;
487 for (FieldEntry fieldEntry : getReferencedFields(caller)) {
488 if (fieldEntry.getType().hasClass() && fieldEntry.getType().getClassEntry().equals(innerClassEntry)) {
489 // caller references this type, so it can't be anonymous
490 return null;
491 }
492 }
493 for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) {
494 if (behaviorEntry.getSignature().hasClass(innerClassEntry)) {
495 return null;
496 }
497 }
498
499 return caller;
500 }
501
502 public Set<ClassEntry> getObfClassEntries() {
503 return m_obfClassEntries;
504 }
505
506 public Collection<FieldEntry> getObfFieldEntries() {
507 return m_fields.values();
508 }
509
510 public Collection<FieldEntry> getObfFieldEntries(ClassEntry classEntry) {
511 return m_fields.get(classEntry);
512 }
513
514 public Collection<BehaviorEntry> getObfBehaviorEntries() {
515 return m_behaviors.values();
516 }
517
518 public Collection<BehaviorEntry> getObfBehaviorEntries(ClassEntry classEntry) {
519 return m_behaviors.get(classEntry);
520 }
521
522 public TranslationIndex getTranslationIndex() {
523 return m_translationIndex;
524 }
525
526 public Access getAccess(Entry entry) {
527 return m_access.get(entry);
528 }
529
530 public ClassInheritanceTreeNode getClassInheritance(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
531
532 // get the root node
533 List<String> ancestry = Lists.newArrayList();
534 ancestry.add(obfClassEntry.getName());
535 for (ClassEntry classEntry : m_translationIndex.getAncestry(obfClassEntry)) {
536 if (containsObfClass(classEntry)) {
537 ancestry.add(classEntry.getName());
538 }
539 }
540 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(
541 deobfuscatingTranslator,
542 ancestry.get(ancestry.size() - 1)
543 );
544
545 // expand all children recursively
546 rootNode.load(m_translationIndex, true);
547
548 return rootNode;
549 }
550
551 public ClassImplementationsTreeNode getClassImplementations(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
552
553 // is this even an interface?
554 if (isInterface(obfClassEntry.getClassName())) {
555 ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(deobfuscatingTranslator, obfClassEntry);
556 node.load(this);
557 return node;
558 }
559 return null;
560 }
561
562 public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
563
564 // travel to the ancestor implementation
565 ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry();
566 for (ClassEntry ancestorClassEntry : m_translationIndex.getAncestry(obfMethodEntry.getClassEntry())) {
567 MethodEntry ancestorMethodEntry = new MethodEntry(
568 new ClassEntry(ancestorClassEntry),
569 obfMethodEntry.getName(),
570 obfMethodEntry.getSignature()
571 );
572 if (containsObfBehavior(ancestorMethodEntry)) {
573 baseImplementationClassEntry = ancestorClassEntry;
574 }
575 }
576
577 // make a root node at the base
578 MethodEntry methodEntry = new MethodEntry(
579 baseImplementationClassEntry,
580 obfMethodEntry.getName(),
581 obfMethodEntry.getSignature()
582 );
583 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode(
584 deobfuscatingTranslator,
585 methodEntry,
586 containsObfBehavior(methodEntry)
587 );
588
589 // expand the full tree
590 rootNode.load(this, true);
591
592 return rootNode;
593 }
594
595 public List<MethodImplementationsTreeNode> getMethodImplementations(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
596
597 List<MethodEntry> interfaceMethodEntries = Lists.newArrayList();
598
599 // is this method on an interface?
600 if (isInterface(obfMethodEntry.getClassName())) {
601 interfaceMethodEntries.add(obfMethodEntry);
602 } else {
603 // get the interface class
604 for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) {
605
606 // is this method defined in this interface?
607 MethodEntry methodInterface = new MethodEntry(
608 interfaceEntry,
609 obfMethodEntry.getName(),
610 obfMethodEntry.getSignature()
611 );
612 if (containsObfBehavior(methodInterface)) {
613 interfaceMethodEntries.add(methodInterface);
614 }
615 }
616 }
617
618 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
619 if (!interfaceMethodEntries.isEmpty()) {
620 for (MethodEntry interfaceMethodEntry : interfaceMethodEntries) {
621 MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(deobfuscatingTranslator, interfaceMethodEntry);
622 node.load(this);
623 nodes.add(node);
624 }
625 }
626 return nodes;
627 }
628
629 public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) {
630 Set<MethodEntry> methodEntries = Sets.newHashSet();
631 getRelatedMethodImplementations(methodEntries, getMethodInheritance(null, obfMethodEntry));
632 return methodEntries;
633 }
634
635 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) {
636 MethodEntry methodEntry = node.getMethodEntry();
637 if (containsObfBehavior(methodEntry)) {
638 // collect the entry
639 methodEntries.add(methodEntry);
640 }
641
642 // look at interface methods too
643 for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(null, methodEntry)) {
644 getRelatedMethodImplementations(methodEntries, implementationsNode);
645 }
646
647 // recurse
648 for (int i = 0; i < node.getChildCount(); i++) {
649 getRelatedMethodImplementations(methodEntries, (MethodInheritanceTreeNode) node.getChildAt(i));
650 }
651 }
652
653 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) {
654 MethodEntry methodEntry = node.getMethodEntry();
655 if (containsObfBehavior(methodEntry)) {
656 // collect the entry
657 methodEntries.add(methodEntry);
658 }
659
660 // recurse
661 for (int i = 0; i < node.getChildCount(); i++) {
662 getRelatedMethodImplementations(methodEntries, (MethodImplementationsTreeNode) node.getChildAt(i));
663 }
664 }
665
666 public Collection<EntryReference<FieldEntry, BehaviorEntry>> getFieldReferences(FieldEntry fieldEntry) {
667 return m_fieldReferences.get(fieldEntry);
668 }
669
670 public Collection<FieldEntry> getReferencedFields(BehaviorEntry behaviorEntry) {
671 // linear search is fast enough for now
672 Set<FieldEntry> fieldEntries = Sets.newHashSet();
673 for (EntryReference<FieldEntry, BehaviorEntry> reference : m_fieldReferences.values()) {
674 if (reference.context == behaviorEntry) {
675 fieldEntries.add(reference.entry);
676 }
677 }
678 return fieldEntries;
679 }
680
681 public Collection<EntryReference<BehaviorEntry, BehaviorEntry>> getBehaviorReferences(BehaviorEntry behaviorEntry) {
682 return m_behaviorReferences.get(behaviorEntry);
683 }
684
685 public Collection<BehaviorEntry> getReferencedBehaviors(BehaviorEntry behaviorEntry) {
686 // linear search is fast enough for now
687 Set<BehaviorEntry> behaviorEntries = Sets.newHashSet();
688 for (EntryReference<BehaviorEntry, BehaviorEntry> reference : m_behaviorReferences.values()) {
689 if (reference.context == behaviorEntry) {
690 behaviorEntries.add(reference.entry);
691 }
692 }
693 return behaviorEntries;
694 }
695
696 public Collection<ClassEntry> getInnerClasses(ClassEntry obfOuterClassEntry) {
697 return m_innerClassesByOuter.get(obfOuterClassEntry);
698 }
699
700 public ClassEntry getOuterClass(ClassEntry obfInnerClassEntry) {
701 return m_outerClassesByInner.get(obfInnerClassEntry);
702 }
703
704 public boolean isAnonymousClass(ClassEntry obfInnerClassEntry) {
705 return m_anonymousClasses.containsKey(obfInnerClassEntry);
706 }
707
708 public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName) {
709 return m_anonymousClasses.get(obfInnerClassName);
710 }
711
712 public Set<ClassEntry> getInterfaces(String className) {
713 ClassEntry classEntry = new ClassEntry(className);
714 Set<ClassEntry> interfaces = new HashSet<ClassEntry>();
715 interfaces.addAll(m_translationIndex.getInterfaces(classEntry));
716 for (ClassEntry ancestor : m_translationIndex.getAncestry(classEntry)) {
717 interfaces.addAll(m_translationIndex.getInterfaces(ancestor));
718 }
719 return interfaces;
720 }
721
722 public Set<String> getImplementingClasses(String targetInterfaceName) {
723
724 // linear search is fast enough for now
725 Set<String> classNames = Sets.newHashSet();
726 for (Map.Entry<ClassEntry, ClassEntry> entry : m_translationIndex.getClassInterfaces()) {
727 ClassEntry classEntry = entry.getKey();
728 ClassEntry interfaceEntry = entry.getValue();
729 if (interfaceEntry.getName().equals(targetInterfaceName)) {
730 classNames.add(classEntry.getClassName());
731 m_translationIndex.getSubclassNamesRecursively(classNames, classEntry);
732 }
733 }
734 return classNames;
735 }
736
737 public boolean isInterface(String className) {
738 return m_translationIndex.isInterface(new ClassEntry(className));
739 }
740
741 public boolean containsObfClass(ClassEntry obfClassEntry) {
742 return m_obfClassEntries.contains(obfClassEntry);
743 }
744
745 public boolean containsObfField(FieldEntry obfFieldEntry) {
746 return m_access.containsKey(obfFieldEntry);
747 }
748
749 public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) {
750 return m_access.containsKey(obfBehaviorEntry);
751 }
752
753 public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) {
754 // check the behavior
755 if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) {
756 return false;
757 }
758
759 // check the argument
760 return obfArgumentEntry.getIndex() < obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size();
761
762 }
763
764 public boolean containsObfEntry(Entry obfEntry) {
765 if (obfEntry instanceof ClassEntry) {
766 return containsObfClass((ClassEntry) obfEntry);
767 } else if (obfEntry instanceof FieldEntry) {
768 return containsObfField((FieldEntry) obfEntry);
769 } else if (obfEntry instanceof BehaviorEntry) {
770 return containsObfBehavior((BehaviorEntry) obfEntry);
771 } else if (obfEntry instanceof ArgumentEntry) {
772 return containsObfArgument((ArgumentEntry) obfEntry);
773 } else {
774 throw new Error("Entry type not supported: " + obfEntry.getClass().getName());
775 }
776 }
777
778 public MethodEntry getBridgedMethod(MethodEntry bridgeMethodEntry) {
779 return m_bridgedMethods.get(bridgeMethodEntry);
780 }
781
782 public List<ClassEntry> getObfClassChain(ClassEntry obfClassEntry) {
783
784 // build class chain in inner-to-outer order
785 List<ClassEntry> obfClassChain = Lists.newArrayList(obfClassEntry);
786 ClassEntry checkClassEntry = obfClassEntry;
787 while (true) {
788 ClassEntry obfOuterClassEntry = getOuterClass(checkClassEntry);
789 if (obfOuterClassEntry != null) {
790 obfClassChain.add(obfOuterClassEntry);
791 checkClassEntry = obfOuterClassEntry;
792 } else {
793 break;
794 }
795 }
796
797 // switch to outer-to-inner order
798 Collections.reverse(obfClassChain);
799
800 return obfClassChain;
801 }
802}
diff --git a/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java
new file mode 100644
index 00000000..2ee3ec1f
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java
@@ -0,0 +1,101 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.Lists;
14
15import java.util.List;
16
17import javax.swing.tree.DefaultMutableTreeNode;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.MethodEntry;
21import cuchaz.enigma.mapping.Translator;
22
23public class MethodImplementationsTreeNode extends DefaultMutableTreeNode {
24
25 private static final long serialVersionUID = 3781080657461899915L;
26
27 private Translator m_deobfuscatingTranslator;
28 private MethodEntry m_entry;
29
30 public MethodImplementationsTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) {
31 if (entry == null) {
32 throw new IllegalArgumentException("entry cannot be null!");
33 }
34
35 m_deobfuscatingTranslator = deobfuscatingTranslator;
36 m_entry = entry;
37 }
38
39 public MethodEntry getMethodEntry() {
40 return m_entry;
41 }
42
43 public String getDeobfClassName() {
44 return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
45 }
46
47 public String getDeobfMethodName() {
48 return m_deobfuscatingTranslator.translate(m_entry);
49 }
50
51 @Override
52 public String toString() {
53 String className = getDeobfClassName();
54 if (className == null) {
55 className = m_entry.getClassName();
56 }
57
58 String methodName = getDeobfMethodName();
59 if (methodName == null) {
60 methodName = m_entry.getName();
61 }
62 return className + "." + methodName + "()";
63 }
64
65 public void load(JarIndex index) {
66
67 // get all method implementations
68 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
69 for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) {
70 MethodEntry methodEntry = new MethodEntry(
71 new ClassEntry(implementingClassName),
72 m_entry.getName(),
73 m_entry.getSignature()
74 );
75 if (index.containsObfBehavior(methodEntry)) {
76 nodes.add(new MethodImplementationsTreeNode(m_deobfuscatingTranslator, methodEntry));
77 }
78 }
79
80 // add them to this node
81 for (MethodImplementationsTreeNode node : nodes) {
82 this.add(node);
83 }
84 }
85
86 public static MethodImplementationsTreeNode findNode(MethodImplementationsTreeNode node, MethodEntry entry) {
87 // is this the node?
88 if (node.getMethodEntry().equals(entry)) {
89 return node;
90 }
91
92 // recurse
93 for (int i = 0; i < node.getChildCount(); i++) {
94 MethodImplementationsTreeNode foundNode = findNode((MethodImplementationsTreeNode) node.getChildAt(i), entry);
95 if (foundNode != null) {
96 return foundNode;
97 }
98 }
99 return null;
100 }
101}
diff --git a/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java
new file mode 100644
index 00000000..cf42ac77
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java
@@ -0,0 +1,114 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.Lists;
14
15import java.util.List;
16
17import javax.swing.tree.DefaultMutableTreeNode;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.MethodEntry;
21import cuchaz.enigma.mapping.Translator;
22
23public class MethodInheritanceTreeNode extends DefaultMutableTreeNode {
24
25 private static final long serialVersionUID = 1096677030991810007L;
26
27 private Translator m_deobfuscatingTranslator;
28 private MethodEntry m_entry;
29 private boolean m_isImplemented;
30
31 public MethodInheritanceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry, boolean isImplemented) {
32 m_deobfuscatingTranslator = deobfuscatingTranslator;
33 m_entry = entry;
34 m_isImplemented = isImplemented;
35 }
36
37 public MethodEntry getMethodEntry() {
38 return m_entry;
39 }
40
41 public String getDeobfClassName() {
42 return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
43 }
44
45 public String getDeobfMethodName() {
46 return m_deobfuscatingTranslator.translate(m_entry);
47 }
48
49 public boolean isImplemented() {
50 return m_isImplemented;
51 }
52
53 @Override
54 public String toString() {
55 String className = getDeobfClassName();
56 if (className == null) {
57 className = m_entry.getClassName();
58 }
59
60 if (!m_isImplemented) {
61 return className;
62 } else {
63 String methodName = getDeobfMethodName();
64 if (methodName == null) {
65 methodName = m_entry.getName();
66 }
67 return className + "." + methodName + "()";
68 }
69 }
70
71 public void load(JarIndex index, boolean recurse) {
72 // get all the child nodes
73 List<MethodInheritanceTreeNode> nodes = Lists.newArrayList();
74 for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(m_entry.getClassEntry())) {
75 MethodEntry methodEntry = new MethodEntry(
76 subclassEntry,
77 m_entry.getName(),
78 m_entry.getSignature()
79 );
80 nodes.add(new MethodInheritanceTreeNode(
81 m_deobfuscatingTranslator,
82 methodEntry,
83 index.containsObfBehavior(methodEntry)
84 ));
85 }
86
87 // add them to this node
88 for (MethodInheritanceTreeNode node : nodes) {
89 this.add(node);
90 }
91
92 if (recurse) {
93 for (MethodInheritanceTreeNode node : nodes) {
94 node.load(index, true);
95 }
96 }
97 }
98
99 public static MethodInheritanceTreeNode findNode(MethodInheritanceTreeNode node, MethodEntry entry) {
100 // is this the node?
101 if (node.getMethodEntry().equals(entry)) {
102 return node;
103 }
104
105 // recurse
106 for (int i = 0; i < node.getChildCount(); i++) {
107 MethodInheritanceTreeNode foundNode = findNode((MethodInheritanceTreeNode) node.getChildAt(i), entry);
108 if (foundNode != null) {
109 return foundNode;
110 }
111 }
112 return null;
113 }
114}
diff --git a/src/cuchaz/enigma/analysis/ReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java
index 4d81bf1c..93923467 100644
--- a/src/cuchaz/enigma/analysis/ReferenceTreeNode.java
+++ b/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java
@@ -4,15 +4,16 @@
4 * are made available under the terms of the GNU Lesser General Public 4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at 5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html 6 * http://www.gnu.org/licenses/lgpl.html
7 * 7 * <p>
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.analysis; 11package cuchaz.enigma.analysis;
12 12
13import cuchaz.enigma.mapping.Entry; 13import cuchaz.enigma.mapping.Entry;
14 14
15public interface ReferenceTreeNode<E extends Entry,C extends Entry> { 15public interface ReferenceTreeNode<E extends Entry, C extends Entry> {
16 E getEntry(); 16 E getEntry();
17 EntryReference<E,C> getReference(); 17
18 EntryReference<E, C> getReference();
18} 19}
diff --git a/src/main/java/cuchaz/enigma/analysis/RelatedMethodChecker.java b/src/main/java/cuchaz/enigma/analysis/RelatedMethodChecker.java
new file mode 100644
index 00000000..08e2dbf0
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/RelatedMethodChecker.java
@@ -0,0 +1,104 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.Maps;
14import com.google.common.collect.Sets;
15
16import java.util.Map;
17import java.util.Set;
18
19import cuchaz.enigma.mapping.*;
20
21public class RelatedMethodChecker {
22
23 private JarIndex m_jarIndex;
24 private Map<Set<MethodEntry>, String> m_deobfNamesByGroup;
25 private Map<MethodEntry, String> m_deobfNamesByObfMethod;
26 private Map<MethodEntry, Set<MethodEntry>> m_groupsByObfMethod;
27 private Set<Set<MethodEntry>> m_inconsistentGroups;
28
29 public RelatedMethodChecker(JarIndex jarIndex) {
30 m_jarIndex = jarIndex;
31 m_deobfNamesByGroup = Maps.newHashMap();
32 m_deobfNamesByObfMethod = Maps.newHashMap();
33 m_groupsByObfMethod = Maps.newHashMap();
34 m_inconsistentGroups = Sets.newHashSet();
35 }
36
37 public void checkMethod(ClassEntry classEntry, MethodMapping methodMapping) {
38
39 // TEMP: disable the expensive check for now, maybe we can optimize it later, or just use it for debugging
40 if (true) {
41 return;
42 }
43
44 BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping);
45 if (!(obfBehaviorEntry instanceof MethodEntry)) {
46 // only methods have related implementations
47 return;
48 }
49 MethodEntry obfMethodEntry = (MethodEntry) obfBehaviorEntry;
50 String deobfName = methodMapping.getDeobfName();
51 m_deobfNamesByObfMethod.put(obfMethodEntry, deobfName);
52
53 // have we seen this method's group before?
54 Set<MethodEntry> group = m_groupsByObfMethod.get(obfMethodEntry);
55 if (group == null) {
56
57 // no, compute the group and save the name
58 group = m_jarIndex.getRelatedMethodImplementations(obfMethodEntry);
59 m_deobfNamesByGroup.put(group, deobfName);
60
61 assert (group.contains(obfMethodEntry));
62 for (MethodEntry relatedMethodEntry : group) {
63 m_groupsByObfMethod.put(relatedMethodEntry, group);
64 }
65 }
66
67 // check the name
68 if (!sameName(m_deobfNamesByGroup.get(group), deobfName)) {
69 m_inconsistentGroups.add(group);
70 }
71 }
72
73 private boolean sameName(String a, String b) {
74 if (a == null && b == null) {
75 return true;
76 } else if (a != null && b != null) {
77 return a.equals(b);
78 }
79 return false;
80 }
81
82 public boolean hasProblems() {
83 return m_inconsistentGroups.size() > 0;
84 }
85
86 public String getReport() {
87 StringBuilder buf = new StringBuilder();
88 buf.append(m_inconsistentGroups.size());
89 buf.append(" groups of methods related by inheritance and/or interfaces have different deobf names!\n");
90 for (Set<MethodEntry> group : m_inconsistentGroups) {
91 buf.append("\tGroup with ");
92 buf.append(group.size());
93 buf.append(" methods:\n");
94 for (MethodEntry methodEntry : group) {
95 buf.append("\t\t");
96 buf.append(methodEntry.toString());
97 buf.append(" => ");
98 buf.append(m_deobfNamesByObfMethod.get(methodEntry));
99 buf.append("\n");
100 }
101 }
102 return buf.toString();
103 }
104}
diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java
new file mode 100644
index 00000000..a20fbb44
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java
@@ -0,0 +1,185 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.HashMultimap;
14import com.google.common.collect.Lists;
15import com.google.common.collect.Maps;
16import com.google.common.collect.Multimap;
17
18import com.strobel.decompiler.languages.Region;
19import com.strobel.decompiler.languages.java.ast.AstNode;
20import com.strobel.decompiler.languages.java.ast.Identifier;
21
22import java.util.Collection;
23import java.util.List;
24import java.util.Map;
25import java.util.TreeMap;
26
27import cuchaz.enigma.mapping.Entry;
28
29public class SourceIndex {
30
31 private String m_source;
32 private TreeMap<Token, EntryReference<Entry, Entry>> m_tokenToReference;
33 private Multimap<EntryReference<Entry, Entry>, Token> m_referenceToTokens;
34 private Map<Entry, Token> m_declarationToToken;
35 private List<Integer> m_lineOffsets;
36 private boolean m_ignoreBadTokens;
37
38 public SourceIndex(String source) {
39 this(source, true);
40 }
41
42 public SourceIndex(String source, boolean ignoreBadTokens) {
43 m_source = source;
44 m_ignoreBadTokens = ignoreBadTokens;
45 m_tokenToReference = Maps.newTreeMap();
46 m_referenceToTokens = HashMultimap.create();
47 m_declarationToToken = Maps.newHashMap();
48 m_lineOffsets = Lists.newArrayList();
49
50 // count the lines
51 m_lineOffsets.add(0);
52 for (int i = 0; i < source.length(); i++) {
53 if (source.charAt(i) == '\n') {
54 m_lineOffsets.add(i + 1);
55 }
56 }
57 }
58
59 public String getSource() {
60 return m_source;
61 }
62
63 public Token getToken(AstNode node) {
64
65 // get the text of the node
66 String name = "";
67 if (node instanceof Identifier) {
68 name = ((Identifier) node).getName();
69 }
70
71 // get a token for this node's region
72 Region region = node.getRegion();
73 if (region.getBeginLine() == 0 || region.getEndLine() == 0) {
74 // DEBUG
75 System.err.println(String.format("WARNING: %s \"%s\" has invalid region: %s", node.getNodeType(), name, region));
76 return null;
77 }
78 Token token = new Token(
79 toPos(region.getBeginLine(), region.getBeginColumn()),
80 toPos(region.getEndLine(), region.getEndColumn()),
81 m_source
82 );
83 if (token.start == 0) {
84 // DEBUG
85 System.err.println(String.format("WARNING: %s \"%s\" has invalid start: %s", node.getNodeType(), name, region));
86 return null;
87 }
88
89 // DEBUG
90 // System.out.println( String.format( "%s \"%s\" region: %s", node.getNodeType(), name, region ) );
91
92 // if the token has a $ in it, something's wrong. Ignore this token
93 if (name.lastIndexOf('$') >= 0 && m_ignoreBadTokens) {
94 // DEBUG
95 System.err.println(String.format("WARNING: %s \"%s\" is probably a bad token. It was ignored", node.getNodeType(), name));
96 return null;
97 }
98
99 return token;
100 }
101
102 public void addReference(AstNode node, Entry deobfEntry, Entry deobfContext) {
103 Token token = getToken(node);
104 if (token != null) {
105 EntryReference<Entry, Entry> deobfReference = new EntryReference<Entry, Entry>(deobfEntry, token.text, deobfContext);
106 m_tokenToReference.put(token, deobfReference);
107 m_referenceToTokens.put(deobfReference, token);
108 }
109 }
110
111 public void addDeclaration(AstNode node, Entry deobfEntry) {
112 Token token = getToken(node);
113 if (token != null) {
114 EntryReference<Entry, Entry> reference = new EntryReference<Entry, Entry>(deobfEntry, token.text);
115 m_tokenToReference.put(token, reference);
116 m_referenceToTokens.put(reference, token);
117 m_declarationToToken.put(deobfEntry, token);
118 }
119 }
120
121 public Token getReferenceToken(int pos) {
122 Token token = m_tokenToReference.floorKey(new Token(pos, pos, null));
123 if (token != null && token.contains(pos)) {
124 return token;
125 }
126 return null;
127 }
128
129 public Collection<Token> getReferenceTokens(EntryReference<Entry, Entry> deobfReference) {
130 return m_referenceToTokens.get(deobfReference);
131 }
132
133 public EntryReference<Entry, Entry> getDeobfReference(Token token) {
134 if (token == null) {
135 return null;
136 }
137 return m_tokenToReference.get(token);
138 }
139
140 public void replaceDeobfReference(Token token, EntryReference<Entry, Entry> newDeobfReference) {
141 EntryReference<Entry, Entry> oldDeobfReference = m_tokenToReference.get(token);
142 m_tokenToReference.put(token, newDeobfReference);
143 Collection<Token> tokens = m_referenceToTokens.get(oldDeobfReference);
144 m_referenceToTokens.removeAll(oldDeobfReference);
145 m_referenceToTokens.putAll(newDeobfReference, tokens);
146 }
147
148 public Iterable<Token> referenceTokens() {
149 return m_tokenToReference.keySet();
150 }
151
152 public Iterable<Token> declarationTokens() {
153 return m_declarationToToken.values();
154 }
155
156 public Iterable<Entry> declarations() {
157 return m_declarationToToken.keySet();
158 }
159
160 public Token getDeclarationToken(Entry deobfEntry) {
161 return m_declarationToToken.get(deobfEntry);
162 }
163
164 public int getLineNumber(int pos) {
165 // line number is 1-based
166 int line = 0;
167 for (Integer offset : m_lineOffsets) {
168 if (offset > pos) {
169 break;
170 }
171 line++;
172 }
173 return line;
174 }
175
176 public int getColumnNumber(int pos) {
177 // column number is 1-based
178 return pos - m_lineOffsets.get(getLineNumber(pos) - 1) + 1;
179 }
180
181 private int toPos(int line, int col) {
182 // line and col are 1-based
183 return m_lineOffsets.get(line - 1) + col - 1;
184 }
185}
diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java
new file mode 100644
index 00000000..e2b567e8
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java
@@ -0,0 +1,131 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.assembler.metadata.*;
14import com.strobel.decompiler.languages.TextLocation;
15import com.strobel.decompiler.languages.java.ast.*;
16
17import cuchaz.enigma.mapping.*;
18
19import java.lang.Error;
20
21public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
22
23 private BehaviorEntry m_behaviorEntry;
24
25 public SourceIndexBehaviorVisitor(BehaviorEntry behaviorEntry) {
26 m_behaviorEntry = behaviorEntry;
27 }
28
29 @Override
30 public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) {
31 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
32
33 // get the behavior entry
34 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
35 BehaviorEntry behaviorEntry = null;
36 if (ref instanceof MethodReference) {
37 MethodReference methodRef = (MethodReference) ref;
38 if (methodRef.isConstructor()) {
39 behaviorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature()));
40 } else if (methodRef.isTypeInitializer()) {
41 behaviorEntry = new ConstructorEntry(classEntry);
42 } else {
43 behaviorEntry = new MethodEntry(classEntry, ref.getName(), new Signature(ref.getErasedSignature()));
44 }
45 }
46 if (behaviorEntry != null) {
47 // get the node for the token
48 AstNode tokenNode = null;
49 if (node.getTarget() instanceof MemberReferenceExpression) {
50 tokenNode = ((MemberReferenceExpression) node.getTarget()).getMemberNameToken();
51 } else if (node.getTarget() instanceof SuperReferenceExpression) {
52 tokenNode = node.getTarget();
53 } else if (node.getTarget() instanceof ThisReferenceExpression) {
54 tokenNode = node.getTarget();
55 }
56 if (tokenNode != null) {
57 index.addReference(tokenNode, behaviorEntry, m_behaviorEntry);
58 }
59 }
60
61 return recurse(node, index);
62 }
63
64 @Override
65 public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) {
66 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
67 if (ref != null) {
68 // make sure this is actually a field
69 if (ref.getErasedSignature().indexOf('(') >= 0) {
70 throw new Error("Expected a field here! got " + ref);
71 }
72
73 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
74 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature()));
75 index.addReference(node.getMemberNameToken(), fieldEntry, m_behaviorEntry);
76 }
77
78 return recurse(node, index);
79 }
80
81 @Override
82 public Void visitSimpleType(SimpleType node, SourceIndex index) {
83 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
84 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
85 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
86 index.addReference(node.getIdentifierToken(), classEntry, m_behaviorEntry);
87 }
88
89 return recurse(node, index);
90 }
91
92 @Override
93 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
94 ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION);
95 if (def.getMethod() instanceof MethodDefinition) {
96 MethodDefinition methodDef = (MethodDefinition) def.getMethod();
97 BehaviorEntry behaviorEntry = ProcyonEntryFactory.getBehaviorEntry(methodDef);
98 ArgumentEntry argumentEntry = new ArgumentEntry(behaviorEntry, def.getPosition(), node.getName());
99 index.addDeclaration(node.getNameToken(), argumentEntry);
100 }
101
102 return recurse(node, index);
103 }
104
105 @Override
106 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
107 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
108 if (ref != null) {
109 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
110 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature()));
111 index.addReference(node.getIdentifierToken(), fieldEntry, m_behaviorEntry);
112 }
113
114 return recurse(node, index);
115 }
116
117 @Override
118 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
119 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
120 if (ref != null) {
121 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
122 ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature()));
123 if (node.getType() instanceof SimpleType) {
124 SimpleType simpleTypeNode = (SimpleType) node.getType();
125 index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, m_behaviorEntry);
126 }
127 }
128
129 return recurse(node, index);
130 }
131}
diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
new file mode 100644
index 00000000..0a3bad51
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
@@ -0,0 +1,100 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.assembler.metadata.FieldDefinition;
14import com.strobel.assembler.metadata.MethodDefinition;
15import com.strobel.assembler.metadata.TypeDefinition;
16import com.strobel.assembler.metadata.TypeReference;
17import com.strobel.decompiler.languages.TextLocation;
18import com.strobel.decompiler.languages.java.ast.*;
19
20import cuchaz.enigma.mapping.*;
21
22public class SourceIndexClassVisitor extends SourceIndexVisitor {
23
24 private ClassEntry m_classEntry;
25
26 public SourceIndexClassVisitor(ClassEntry classEntry) {
27 m_classEntry = classEntry;
28 }
29
30 @Override
31 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
32 // is this this class, or a subtype?
33 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
34 ClassEntry classEntry = new ClassEntry(def.getInternalName());
35 if (!classEntry.equals(m_classEntry)) {
36 // it's a sub-type, recurse
37 index.addDeclaration(node.getNameToken(), classEntry);
38 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
39 }
40
41 return recurse(node, index);
42 }
43
44 @Override
45 public Void visitSimpleType(SimpleType node, SourceIndex index) {
46 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
47 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
48 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
49 index.addReference(node.getIdentifierToken(), classEntry, m_classEntry);
50 }
51
52 return recurse(node, index);
53 }
54
55 @Override
56 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
57 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
58 BehaviorEntry behaviorEntry = ProcyonEntryFactory.getBehaviorEntry(def);
59 AstNode tokenNode = node.getNameToken();
60
61 if (behaviorEntry instanceof ConstructorEntry) {
62 ConstructorEntry constructorEntry = (ConstructorEntry) behaviorEntry;
63 if (constructorEntry.isStatic()) {
64 // for static initializers, check elsewhere for the token node
65 tokenNode = node.getModifiers().firstOrNullObject();
66 }
67 }
68 index.addDeclaration(tokenNode, behaviorEntry);
69 return node.acceptVisitor(new SourceIndexBehaviorVisitor(behaviorEntry), index);
70 }
71
72 @Override
73 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
74 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
75 ConstructorEntry constructorEntry = ProcyonEntryFactory.getConstructorEntry(def);
76 index.addDeclaration(node.getNameToken(), constructorEntry);
77 return node.acceptVisitor(new SourceIndexBehaviorVisitor(constructorEntry), index);
78 }
79
80 @Override
81 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
82 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
83 FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def);
84 assert (node.getVariables().size() == 1);
85 VariableInitializer variable = node.getVariables().firstOrNullObject();
86 index.addDeclaration(variable.getNameToken(), fieldEntry);
87
88 return recurse(node, index);
89 }
90
91 @Override
92 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
93 // treat enum declarations as field declarations
94 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
95 FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def);
96 index.addDeclaration(node.getNameToken(), fieldEntry);
97
98 return recurse(node, index);
99 }
100}
diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java
new file mode 100644
index 00000000..40381f43
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java
@@ -0,0 +1,381 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.assembler.metadata.TypeDefinition;
14import com.strobel.decompiler.languages.java.ast.*;
15import com.strobel.decompiler.patterns.Pattern;
16
17import cuchaz.enigma.mapping.ClassEntry;
18
19public class SourceIndexVisitor implements IAstVisitor<SourceIndex, Void> {
20
21 @Override
22 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
23 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
24 ClassEntry classEntry = new ClassEntry(def.getInternalName());
25 index.addDeclaration(node.getNameToken(), classEntry);
26
27 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
28 }
29
30 protected Void recurse(AstNode node, SourceIndex index) {
31 for (final AstNode child : node.getChildren()) {
32 child.acceptVisitor(this, index);
33 }
34 return null;
35 }
36
37 @Override
38 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
39 return recurse(node, index);
40 }
41
42 @Override
43 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
44 return recurse(node, index);
45 }
46
47 @Override
48 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
49 return recurse(node, index);
50 }
51
52 @Override
53 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
54 return recurse(node, index);
55 }
56
57 @Override
58 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
59 return recurse(node, index);
60 }
61
62 @Override
63 public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) {
64 return recurse(node, index);
65 }
66
67 @Override
68 public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) {
69 return recurse(node, index);
70 }
71
72 @Override
73 public Void visitSimpleType(SimpleType node, SourceIndex index) {
74 return recurse(node, index);
75 }
76
77 @Override
78 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
79 return recurse(node, index);
80 }
81
82 @Override
83 public Void visitComment(Comment node, SourceIndex index) {
84 return recurse(node, index);
85 }
86
87 @Override
88 public Void visitPatternPlaceholder(AstNode node, Pattern pattern, SourceIndex index) {
89 return recurse(node, index);
90 }
91
92 @Override
93 public Void visitTypeReference(TypeReferenceExpression node, SourceIndex index) {
94 return recurse(node, index);
95 }
96
97 @Override
98 public Void visitJavaTokenNode(JavaTokenNode node, SourceIndex index) {
99 return recurse(node, index);
100 }
101
102 @Override
103 public Void visitIdentifier(Identifier node, SourceIndex index) {
104 return recurse(node, index);
105 }
106
107 @Override
108 public Void visitNullReferenceExpression(NullReferenceExpression node, SourceIndex index) {
109 return recurse(node, index);
110 }
111
112 @Override
113 public Void visitThisReferenceExpression(ThisReferenceExpression node, SourceIndex index) {
114 return recurse(node, index);
115 }
116
117 @Override
118 public Void visitSuperReferenceExpression(SuperReferenceExpression node, SourceIndex index) {
119 return recurse(node, index);
120 }
121
122 @Override
123 public Void visitClassOfExpression(ClassOfExpression node, SourceIndex index) {
124 return recurse(node, index);
125 }
126
127 @Override
128 public Void visitBlockStatement(BlockStatement node, SourceIndex index) {
129 return recurse(node, index);
130 }
131
132 @Override
133 public Void visitExpressionStatement(ExpressionStatement node, SourceIndex index) {
134 return recurse(node, index);
135 }
136
137 @Override
138 public Void visitBreakStatement(BreakStatement node, SourceIndex index) {
139 return recurse(node, index);
140 }
141
142 @Override
143 public Void visitContinueStatement(ContinueStatement node, SourceIndex index) {
144 return recurse(node, index);
145 }
146
147 @Override
148 public Void visitDoWhileStatement(DoWhileStatement node, SourceIndex index) {
149 return recurse(node, index);
150 }
151
152 @Override
153 public Void visitEmptyStatement(EmptyStatement node, SourceIndex index) {
154 return recurse(node, index);
155 }
156
157 @Override
158 public Void visitIfElseStatement(IfElseStatement node, SourceIndex index) {
159 return recurse(node, index);
160 }
161
162 @Override
163 public Void visitLabelStatement(LabelStatement node, SourceIndex index) {
164 return recurse(node, index);
165 }
166
167 @Override
168 public Void visitLabeledStatement(LabeledStatement node, SourceIndex index) {
169 return recurse(node, index);
170 }
171
172 @Override
173 public Void visitReturnStatement(ReturnStatement node, SourceIndex index) {
174 return recurse(node, index);
175 }
176
177 @Override
178 public Void visitSwitchStatement(SwitchStatement node, SourceIndex index) {
179 return recurse(node, index);
180 }
181
182 @Override
183 public Void visitSwitchSection(SwitchSection node, SourceIndex index) {
184 return recurse(node, index);
185 }
186
187 @Override
188 public Void visitCaseLabel(CaseLabel node, SourceIndex index) {
189 return recurse(node, index);
190 }
191
192 @Override
193 public Void visitThrowStatement(ThrowStatement node, SourceIndex index) {
194 return recurse(node, index);
195 }
196
197 @Override
198 public Void visitCatchClause(CatchClause node, SourceIndex index) {
199 return recurse(node, index);
200 }
201
202 @Override
203 public Void visitAnnotation(Annotation node, SourceIndex index) {
204 return recurse(node, index);
205 }
206
207 @Override
208 public Void visitNewLine(NewLineNode node, SourceIndex index) {
209 return recurse(node, index);
210 }
211
212 @Override
213 public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) {
214 return recurse(node, index);
215 }
216
217 @Override
218 public Void visitVariableInitializer(VariableInitializer node, SourceIndex index) {
219 return recurse(node, index);
220 }
221
222 @Override
223 public Void visitText(TextNode node, SourceIndex index) {
224 return recurse(node, index);
225 }
226
227 @Override
228 public Void visitImportDeclaration(ImportDeclaration node, SourceIndex index) {
229 return recurse(node, index);
230 }
231
232 @Override
233 public Void visitInitializerBlock(InstanceInitializer node, SourceIndex index) {
234 return recurse(node, index);
235 }
236
237 @Override
238 public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, SourceIndex index) {
239 return recurse(node, index);
240 }
241
242 @Override
243 public Void visitCompilationUnit(CompilationUnit node, SourceIndex index) {
244 return recurse(node, index);
245 }
246
247 @Override
248 public Void visitPackageDeclaration(PackageDeclaration node, SourceIndex index) {
249 return recurse(node, index);
250 }
251
252 @Override
253 public Void visitArraySpecifier(ArraySpecifier node, SourceIndex index) {
254 return recurse(node, index);
255 }
256
257 @Override
258 public Void visitComposedType(ComposedType node, SourceIndex index) {
259 return recurse(node, index);
260 }
261
262 @Override
263 public Void visitWhileStatement(WhileStatement node, SourceIndex index) {
264 return recurse(node, index);
265 }
266
267 @Override
268 public Void visitPrimitiveExpression(PrimitiveExpression node, SourceIndex index) {
269 return recurse(node, index);
270 }
271
272 @Override
273 public Void visitCastExpression(CastExpression node, SourceIndex index) {
274 return recurse(node, index);
275 }
276
277 @Override
278 public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, SourceIndex index) {
279 return recurse(node, index);
280 }
281
282 @Override
283 public Void visitInstanceOfExpression(InstanceOfExpression node, SourceIndex index) {
284 return recurse(node, index);
285 }
286
287 @Override
288 public Void visitIndexerExpression(IndexerExpression node, SourceIndex index) {
289 return recurse(node, index);
290 }
291
292 @Override
293 public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, SourceIndex index) {
294 return recurse(node, index);
295 }
296
297 @Override
298 public Void visitConditionalExpression(ConditionalExpression node, SourceIndex index) {
299 return recurse(node, index);
300 }
301
302 @Override
303 public Void visitArrayInitializerExpression(ArrayInitializerExpression node, SourceIndex index) {
304 return recurse(node, index);
305 }
306
307 @Override
308 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
309 return recurse(node, index);
310 }
311
312 @Override
313 public Void visitArrayCreationExpression(ArrayCreationExpression node, SourceIndex index) {
314 return recurse(node, index);
315 }
316
317 @Override
318 public Void visitAssignmentExpression(AssignmentExpression node, SourceIndex index) {
319 return recurse(node, index);
320 }
321
322 @Override
323 public Void visitForStatement(ForStatement node, SourceIndex index) {
324 return recurse(node, index);
325 }
326
327 @Override
328 public Void visitForEachStatement(ForEachStatement node, SourceIndex index) {
329 return recurse(node, index);
330 }
331
332 @Override
333 public Void visitTryCatchStatement(TryCatchStatement node, SourceIndex index) {
334 return recurse(node, index);
335 }
336
337 @Override
338 public Void visitGotoStatement(GotoStatement node, SourceIndex index) {
339 return recurse(node, index);
340 }
341
342 @Override
343 public Void visitParenthesizedExpression(ParenthesizedExpression node, SourceIndex index) {
344 return recurse(node, index);
345 }
346
347 @Override
348 public Void visitSynchronizedStatement(SynchronizedStatement node, SourceIndex index) {
349 return recurse(node, index);
350 }
351
352 @Override
353 public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, SourceIndex index) {
354 return recurse(node, index);
355 }
356
357 @Override
358 public Void visitWildcardType(WildcardType node, SourceIndex index) {
359 return recurse(node, index);
360 }
361
362 @Override
363 public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) {
364 return recurse(node, index);
365 }
366
367 @Override
368 public Void visitAssertStatement(AssertStatement node, SourceIndex index) {
369 return recurse(node, index);
370 }
371
372 @Override
373 public Void visitLambdaExpression(LambdaExpression node, SourceIndex index) {
374 return recurse(node, index);
375 }
376
377 @Override
378 public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, SourceIndex index) {
379 return recurse(node, index);
380 }
381}
diff --git a/src/main/java/cuchaz/enigma/analysis/Token.java b/src/main/java/cuchaz/enigma/analysis/Token.java
new file mode 100644
index 00000000..0103df2e
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/Token.java
@@ -0,0 +1,56 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13public class Token implements Comparable<Token> {
14
15 public int start;
16 public int end;
17 public String text;
18
19 public Token(int start, int end) {
20 this(start, end, null);
21 }
22
23 public Token(int start, int end, String source) {
24 this.start = start;
25 this.end = end;
26 if (source != null) {
27 this.text = source.substring(start, end);
28 }
29 }
30
31 public boolean contains(int pos) {
32 return pos >= start && pos <= end;
33 }
34
35 @Override
36 public int compareTo(Token other) {
37 return start - other.start;
38 }
39
40 @Override
41 public boolean equals(Object other) {
42 if (other instanceof Token) {
43 return equals((Token) other);
44 }
45 return false;
46 }
47
48 public boolean equals(Token other) {
49 return start == other.start && end == other.end;
50 }
51
52 @Override
53 public String toString() {
54 return String.format("[%d,%d]", start, end);
55 }
56}
diff --git a/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java b/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java
new file mode 100644
index 00000000..0261a96a
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java
@@ -0,0 +1,282 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.google.common.collect.HashMultimap;
14import com.google.common.collect.Lists;
15import com.google.common.collect.Maps;
16import com.google.common.collect.Multimap;
17
18import java.io.*;
19import java.util.*;
20import java.util.zip.GZIPInputStream;
21import java.util.zip.GZIPOutputStream;
22
23import cuchaz.enigma.mapping.*;
24import javassist.CtBehavior;
25import javassist.CtClass;
26import javassist.CtField;
27import javassist.bytecode.Descriptor;
28
29public class TranslationIndex implements Serializable {
30
31 private static final long serialVersionUID = 738687982126844179L;
32
33 private Map<ClassEntry, ClassEntry> m_superclasses;
34 private Multimap<ClassEntry, FieldEntry> m_fieldEntries;
35 private Multimap<ClassEntry, BehaviorEntry> m_behaviorEntries;
36 private Multimap<ClassEntry, ClassEntry> m_interfaces;
37
38 public TranslationIndex() {
39 m_superclasses = Maps.newHashMap();
40 m_fieldEntries = HashMultimap.create();
41 m_behaviorEntries = HashMultimap.create();
42 m_interfaces = HashMultimap.create();
43 }
44
45 public TranslationIndex(TranslationIndex other, Translator translator) {
46
47 // translate the superclasses
48 m_superclasses = Maps.newHashMap();
49 for (Map.Entry<ClassEntry, ClassEntry> mapEntry : other.m_superclasses.entrySet()) {
50 m_superclasses.put(
51 translator.translateEntry(mapEntry.getKey()),
52 translator.translateEntry(mapEntry.getValue())
53 );
54 }
55
56 // translate the interfaces
57 m_interfaces = HashMultimap.create();
58 for (Map.Entry<ClassEntry, ClassEntry> mapEntry : other.m_interfaces.entries()) {
59 m_interfaces.put(
60 translator.translateEntry(mapEntry.getKey()),
61 translator.translateEntry(mapEntry.getValue())
62 );
63 }
64
65 // translate the fields
66 m_fieldEntries = HashMultimap.create();
67 for (Map.Entry<ClassEntry, FieldEntry> mapEntry : other.m_fieldEntries.entries()) {
68 m_fieldEntries.put(
69 translator.translateEntry(mapEntry.getKey()),
70 translator.translateEntry(mapEntry.getValue())
71 );
72 }
73
74 m_behaviorEntries = HashMultimap.create();
75 for (Map.Entry<ClassEntry, BehaviorEntry> mapEntry : other.m_behaviorEntries.entries()) {
76 m_behaviorEntries.put(
77 translator.translateEntry(mapEntry.getKey()),
78 translator.translateEntry(mapEntry.getValue())
79 );
80 }
81 }
82
83 public void indexClass(CtClass c) {
84 indexClass(c, true);
85 }
86
87 public void indexClass(CtClass c, boolean indexMembers) {
88
89 ClassEntry classEntry = EntryFactory.getClassEntry(c);
90 if (isJre(classEntry)) {
91 return;
92 }
93
94 // add the superclass
95 ClassEntry superclassEntry = EntryFactory.getSuperclassEntry(c);
96 if (superclassEntry != null) {
97 m_superclasses.put(classEntry, superclassEntry);
98 }
99
100 // add the interfaces
101 for (String interfaceClassName : c.getClassFile().getInterfaces()) {
102 ClassEntry interfaceClassEntry = new ClassEntry(Descriptor.toJvmName(interfaceClassName));
103 if (!isJre(interfaceClassEntry)) {
104 m_interfaces.put(classEntry, interfaceClassEntry);
105 }
106 }
107
108 if (indexMembers) {
109 // add fields
110 for (CtField field : c.getDeclaredFields()) {
111 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
112 m_fieldEntries.put(fieldEntry.getClassEntry(), fieldEntry);
113 }
114
115 // add behaviors
116 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
117 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
118 m_behaviorEntries.put(behaviorEntry.getClassEntry(), behaviorEntry);
119 }
120 }
121 }
122
123 public void renameClasses(Map<String, String> renames) {
124 EntryRenamer.renameClassesInMap(renames, m_superclasses);
125 EntryRenamer.renameClassesInMultimap(renames, m_fieldEntries);
126 EntryRenamer.renameClassesInMultimap(renames, m_behaviorEntries);
127 }
128
129 public ClassEntry getSuperclass(ClassEntry classEntry) {
130 return m_superclasses.get(classEntry);
131 }
132
133 public List<ClassEntry> getAncestry(ClassEntry classEntry) {
134 List<ClassEntry> ancestors = Lists.newArrayList();
135 while (classEntry != null) {
136 classEntry = getSuperclass(classEntry);
137 if (classEntry != null) {
138 ancestors.add(classEntry);
139 }
140 }
141 return ancestors;
142 }
143
144 public List<ClassEntry> getSubclass(ClassEntry classEntry) {
145
146 // linear search is fast enough for now
147 List<ClassEntry> subclasses = Lists.newArrayList();
148 for (Map.Entry<ClassEntry, ClassEntry> entry : m_superclasses.entrySet()) {
149 ClassEntry subclass = entry.getKey();
150 ClassEntry superclass = entry.getValue();
151 if (classEntry.equals(superclass)) {
152 subclasses.add(subclass);
153 }
154 }
155 return subclasses;
156 }
157
158 public void getSubclassesRecursively(Set<ClassEntry> out, ClassEntry classEntry) {
159 for (ClassEntry subclassEntry : getSubclass(classEntry)) {
160 out.add(subclassEntry);
161 getSubclassesRecursively(out, subclassEntry);
162 }
163 }
164
165 public void getSubclassNamesRecursively(Set<String> out, ClassEntry classEntry) {
166 for (ClassEntry subclassEntry : getSubclass(classEntry)) {
167 out.add(subclassEntry.getName());
168 getSubclassNamesRecursively(out, subclassEntry);
169 }
170 }
171
172 public Collection<Map.Entry<ClassEntry, ClassEntry>> getClassInterfaces() {
173 return m_interfaces.entries();
174 }
175
176 public Collection<ClassEntry> getInterfaces(ClassEntry classEntry) {
177 return m_interfaces.get(classEntry);
178 }
179
180 public boolean isInterface(ClassEntry classEntry) {
181 return m_interfaces.containsValue(classEntry);
182 }
183
184 public boolean entryExists(Entry entry) {
185 if (entry instanceof FieldEntry) {
186 return fieldExists((FieldEntry) entry);
187 } else if (entry instanceof BehaviorEntry) {
188 return behaviorExists((BehaviorEntry) entry);
189 } else if (entry instanceof ArgumentEntry) {
190 return behaviorExists(((ArgumentEntry) entry).getBehaviorEntry());
191 }
192 throw new IllegalArgumentException("Cannot check existence for " + entry.getClass());
193 }
194
195 public boolean fieldExists(FieldEntry fieldEntry) {
196 return m_fieldEntries.containsEntry(fieldEntry.getClassEntry(), fieldEntry);
197 }
198
199 public boolean behaviorExists(BehaviorEntry behaviorEntry) {
200 return m_behaviorEntries.containsEntry(behaviorEntry.getClassEntry(), behaviorEntry);
201 }
202
203 public ClassEntry resolveEntryClass(Entry entry) {
204
205 if (entry instanceof ClassEntry) {
206 return (ClassEntry) entry;
207 }
208
209 ClassEntry superclassEntry = resolveSuperclass(entry);
210 if (superclassEntry != null) {
211 return superclassEntry;
212 }
213
214 ClassEntry interfaceEntry = resolveInterface(entry);
215 if (interfaceEntry != null) {
216 return interfaceEntry;
217 }
218
219 return null;
220 }
221
222 public ClassEntry resolveSuperclass(Entry entry) {
223
224 // this entry could refer to a method on a class where the method is not actually implemented
225 // travel up the inheritance tree to find the closest implementation
226 while (!entryExists(entry)) {
227
228 // is there a parent class?
229 ClassEntry superclassEntry = getSuperclass(entry.getClassEntry());
230 if (superclassEntry == null) {
231 // this is probably a method from a class in a library
232 // we can't trace the implementation up any higher unless we index the library
233 return null;
234 }
235
236 // move up to the parent class
237 entry = entry.cloneToNewClass(superclassEntry);
238 }
239 return entry.getClassEntry();
240 }
241
242 public ClassEntry resolveInterface(Entry entry) {
243
244 // the interfaces for any class is a forest
245 // so let's look at all the trees
246 for (ClassEntry interfaceEntry : m_interfaces.get(entry.getClassEntry())) {
247 ClassEntry resolvedClassEntry = resolveSuperclass(entry.cloneToNewClass(interfaceEntry));
248 if (resolvedClassEntry != null) {
249 return resolvedClassEntry;
250 }
251 }
252 return null;
253 }
254
255 private boolean isJre(ClassEntry classEntry) {
256 String packageName = classEntry.getPackageName();
257 return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax"));
258 }
259
260 public void write(OutputStream out)
261 throws IOException {
262 GZIPOutputStream gzipout = new GZIPOutputStream(out);
263 ObjectOutputStream oout = new ObjectOutputStream(gzipout);
264 oout.writeObject(m_superclasses);
265 oout.writeObject(m_fieldEntries);
266 oout.writeObject(m_behaviorEntries);
267 gzipout.finish();
268 }
269
270 @SuppressWarnings("unchecked")
271 public void read(InputStream in)
272 throws IOException {
273 try {
274 ObjectInputStream oin = new ObjectInputStream(new GZIPInputStream(in));
275 m_superclasses = (HashMap<ClassEntry, ClassEntry>) oin.readObject();
276 m_fieldEntries = (HashMultimap<ClassEntry, FieldEntry>) oin.readObject();
277 m_behaviorEntries = (HashMultimap<ClassEntry, BehaviorEntry>) oin.readObject();
278 } catch (ClassNotFoundException ex) {
279 throw new Error(ex);
280 }
281 }
282}
diff --git a/src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java b/src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java
new file mode 100644
index 00000000..ef8a190c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java
@@ -0,0 +1,441 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.componentmodel.Key;
14import com.strobel.decompiler.languages.java.ast.*;
15import com.strobel.decompiler.patterns.Pattern;
16
17import java.io.File;
18import java.io.FileWriter;
19import java.io.IOException;
20import java.io.Writer;
21
22public class TreeDumpVisitor implements IAstVisitor<Void, Void> {
23
24 private File m_file;
25 private Writer m_out;
26
27 public TreeDumpVisitor(File file) {
28 m_file = file;
29 m_out = null;
30 }
31
32 @Override
33 public Void visitCompilationUnit(CompilationUnit node, Void ignored) {
34 try {
35 m_out = new FileWriter(m_file);
36 recurse(node, ignored);
37 m_out.close();
38 return null;
39 } catch (IOException ex) {
40 throw new Error(ex);
41 }
42 }
43
44 private Void recurse(AstNode node, Void ignored) {
45 // show the tree
46 try {
47 m_out.write(getIndent(node) + node.getClass().getSimpleName() + " " + getText(node) + " " + dumpUserData(node) + " " + node.getRegion() + "\n");
48 } catch (IOException ex) {
49 throw new Error(ex);
50 }
51
52 // recurse
53 for (final AstNode child : node.getChildren()) {
54 child.acceptVisitor(this, ignored);
55 }
56 return null;
57 }
58
59 private String getText(AstNode node) {
60 if (node instanceof Identifier) {
61 return "\"" + ((Identifier) node).getName() + "\"";
62 }
63 return "";
64 }
65
66 private String dumpUserData(AstNode node) {
67 StringBuilder buf = new StringBuilder();
68 for (Key<?> key : Keys.ALL_KEYS) {
69 Object val = node.getUserData(key);
70 if (val != null) {
71 buf.append(String.format(" [%s=%s]", key, val));
72 }
73 }
74 return buf.toString();
75 }
76
77 private String getIndent(AstNode node) {
78 StringBuilder buf = new StringBuilder();
79 int depth = getDepth(node);
80 for (int i = 0; i < depth; i++) {
81 buf.append("\t");
82 }
83 return buf.toString();
84 }
85
86 private int getDepth(AstNode node) {
87 int depth = -1;
88 while (node != null) {
89 depth++;
90 node = node.getParent();
91 }
92 return depth;
93 }
94
95 // OVERRIDES WE DON'T CARE ABOUT
96
97 @Override
98 public Void visitInvocationExpression(InvocationExpression node, Void ignored) {
99 return recurse(node, ignored);
100 }
101
102 @Override
103 public Void visitMemberReferenceExpression(MemberReferenceExpression node, Void ignored) {
104 return recurse(node, ignored);
105 }
106
107 @Override
108 public Void visitSimpleType(SimpleType node, Void ignored) {
109 return recurse(node, ignored);
110 }
111
112 @Override
113 public Void visitMethodDeclaration(MethodDeclaration node, Void ignored) {
114 return recurse(node, ignored);
115 }
116
117 @Override
118 public Void visitConstructorDeclaration(ConstructorDeclaration node, Void ignored) {
119 return recurse(node, ignored);
120 }
121
122 @Override
123 public Void visitParameterDeclaration(ParameterDeclaration node, Void ignored) {
124 return recurse(node, ignored);
125 }
126
127 @Override
128 public Void visitFieldDeclaration(FieldDeclaration node, Void ignored) {
129 return recurse(node, ignored);
130 }
131
132 @Override
133 public Void visitTypeDeclaration(TypeDeclaration node, Void ignored) {
134 return recurse(node, ignored);
135 }
136
137 @Override
138 public Void visitComment(Comment node, Void ignored) {
139 return recurse(node, ignored);
140 }
141
142 @Override
143 public Void visitPatternPlaceholder(AstNode node, Pattern pattern, Void ignored) {
144 return recurse(node, ignored);
145 }
146
147 @Override
148 public Void visitTypeReference(TypeReferenceExpression node, Void ignored) {
149 return recurse(node, ignored);
150 }
151
152 @Override
153 public Void visitJavaTokenNode(JavaTokenNode node, Void ignored) {
154 return recurse(node, ignored);
155 }
156
157 @Override
158 public Void visitIdentifier(Identifier node, Void ignored) {
159 return recurse(node, ignored);
160 }
161
162 @Override
163 public Void visitNullReferenceExpression(NullReferenceExpression node, Void ignored) {
164 return recurse(node, ignored);
165 }
166
167 @Override
168 public Void visitThisReferenceExpression(ThisReferenceExpression node, Void ignored) {
169 return recurse(node, ignored);
170 }
171
172 @Override
173 public Void visitSuperReferenceExpression(SuperReferenceExpression node, Void ignored) {
174 return recurse(node, ignored);
175 }
176
177 @Override
178 public Void visitClassOfExpression(ClassOfExpression node, Void ignored) {
179 return recurse(node, ignored);
180 }
181
182 @Override
183 public Void visitBlockStatement(BlockStatement node, Void ignored) {
184 return recurse(node, ignored);
185 }
186
187 @Override
188 public Void visitExpressionStatement(ExpressionStatement node, Void ignored) {
189 return recurse(node, ignored);
190 }
191
192 @Override
193 public Void visitBreakStatement(BreakStatement node, Void ignored) {
194 return recurse(node, ignored);
195 }
196
197 @Override
198 public Void visitContinueStatement(ContinueStatement node, Void ignored) {
199 return recurse(node, ignored);
200 }
201
202 @Override
203 public Void visitDoWhileStatement(DoWhileStatement node, Void ignored) {
204 return recurse(node, ignored);
205 }
206
207 @Override
208 public Void visitEmptyStatement(EmptyStatement node, Void ignored) {
209 return recurse(node, ignored);
210 }
211
212 @Override
213 public Void visitIfElseStatement(IfElseStatement node, Void ignored) {
214 return recurse(node, ignored);
215 }
216
217 @Override
218 public Void visitLabelStatement(LabelStatement node, Void ignored) {
219 return recurse(node, ignored);
220 }
221
222 @Override
223 public Void visitLabeledStatement(LabeledStatement node, Void ignored) {
224 return recurse(node, ignored);
225 }
226
227 @Override
228 public Void visitReturnStatement(ReturnStatement node, Void ignored) {
229 return recurse(node, ignored);
230 }
231
232 @Override
233 public Void visitSwitchStatement(SwitchStatement node, Void ignored) {
234 return recurse(node, ignored);
235 }
236
237 @Override
238 public Void visitSwitchSection(SwitchSection node, Void ignored) {
239 return recurse(node, ignored);
240 }
241
242 @Override
243 public Void visitCaseLabel(CaseLabel node, Void ignored) {
244 return recurse(node, ignored);
245 }
246
247 @Override
248 public Void visitThrowStatement(ThrowStatement node, Void ignored) {
249 return recurse(node, ignored);
250 }
251
252 @Override
253 public Void visitCatchClause(CatchClause node, Void ignored) {
254 return recurse(node, ignored);
255 }
256
257 @Override
258 public Void visitAnnotation(Annotation node, Void ignored) {
259 return recurse(node, ignored);
260 }
261
262 @Override
263 public Void visitNewLine(NewLineNode node, Void ignored) {
264 return recurse(node, ignored);
265 }
266
267 @Override
268 public Void visitVariableDeclaration(VariableDeclarationStatement node, Void ignored) {
269 return recurse(node, ignored);
270 }
271
272 @Override
273 public Void visitVariableInitializer(VariableInitializer node, Void ignored) {
274 return recurse(node, ignored);
275 }
276
277 @Override
278 public Void visitText(TextNode node, Void ignored) {
279 return recurse(node, ignored);
280 }
281
282 @Override
283 public Void visitImportDeclaration(ImportDeclaration node, Void ignored) {
284 return recurse(node, ignored);
285 }
286
287 @Override
288 public Void visitInitializerBlock(InstanceInitializer node, Void ignored) {
289 return recurse(node, ignored);
290 }
291
292 @Override
293 public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, Void ignored) {
294 return recurse(node, ignored);
295 }
296
297 @Override
298 public Void visitPackageDeclaration(PackageDeclaration node, Void ignored) {
299 return recurse(node, ignored);
300 }
301
302 @Override
303 public Void visitArraySpecifier(ArraySpecifier node, Void ignored) {
304 return recurse(node, ignored);
305 }
306
307 @Override
308 public Void visitComposedType(ComposedType node, Void ignored) {
309 return recurse(node, ignored);
310 }
311
312 @Override
313 public Void visitWhileStatement(WhileStatement node, Void ignored) {
314 return recurse(node, ignored);
315 }
316
317 @Override
318 public Void visitPrimitiveExpression(PrimitiveExpression node, Void ignored) {
319 return recurse(node, ignored);
320 }
321
322 @Override
323 public Void visitCastExpression(CastExpression node, Void ignored) {
324 return recurse(node, ignored);
325 }
326
327 @Override
328 public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, Void ignored) {
329 return recurse(node, ignored);
330 }
331
332 @Override
333 public Void visitInstanceOfExpression(InstanceOfExpression node, Void ignored) {
334 return recurse(node, ignored);
335 }
336
337 @Override
338 public Void visitIndexerExpression(IndexerExpression node, Void ignored) {
339 return recurse(node, ignored);
340 }
341
342 @Override
343 public Void visitIdentifierExpression(IdentifierExpression node, Void ignored) {
344 return recurse(node, ignored);
345 }
346
347 @Override
348 public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, Void ignored) {
349 return recurse(node, ignored);
350 }
351
352 @Override
353 public Void visitConditionalExpression(ConditionalExpression node, Void ignored) {
354 return recurse(node, ignored);
355 }
356
357 @Override
358 public Void visitArrayInitializerExpression(ArrayInitializerExpression node, Void ignored) {
359 return recurse(node, ignored);
360 }
361
362 @Override
363 public Void visitObjectCreationExpression(ObjectCreationExpression node, Void ignored) {
364 return recurse(node, ignored);
365 }
366
367 @Override
368 public Void visitArrayCreationExpression(ArrayCreationExpression node, Void ignored) {
369 return recurse(node, ignored);
370 }
371
372 @Override
373 public Void visitAssignmentExpression(AssignmentExpression node, Void ignored) {
374 return recurse(node, ignored);
375 }
376
377 @Override
378 public Void visitForStatement(ForStatement node, Void ignored) {
379 return recurse(node, ignored);
380 }
381
382 @Override
383 public Void visitForEachStatement(ForEachStatement node, Void ignored) {
384 return recurse(node, ignored);
385 }
386
387 @Override
388 public Void visitTryCatchStatement(TryCatchStatement node, Void ignored) {
389 return recurse(node, ignored);
390 }
391
392 @Override
393 public Void visitGotoStatement(GotoStatement node, Void ignored) {
394 return recurse(node, ignored);
395 }
396
397 @Override
398 public Void visitParenthesizedExpression(ParenthesizedExpression node, Void ignored) {
399 return recurse(node, ignored);
400 }
401
402 @Override
403 public Void visitSynchronizedStatement(SynchronizedStatement node, Void ignored) {
404 return recurse(node, ignored);
405 }
406
407 @Override
408 public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, Void ignored) {
409 return recurse(node, ignored);
410 }
411
412 @Override
413 public Void visitWildcardType(WildcardType node, Void ignored) {
414 return recurse(node, ignored);
415 }
416
417 @Override
418 public Void visitMethodGroupExpression(MethodGroupExpression node, Void ignored) {
419 return recurse(node, ignored);
420 }
421
422 @Override
423 public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void ignored) {
424 return recurse(node, ignored);
425 }
426
427 @Override
428 public Void visitAssertStatement(AssertStatement node, Void ignored) {
429 return recurse(node, ignored);
430 }
431
432 @Override
433 public Void visitLambdaExpression(LambdaExpression node, Void ignored) {
434 return recurse(node, ignored);
435 }
436
437 @Override
438 public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, Void ignored) {
439 return recurse(node, ignored);
440 }
441}
diff --git a/src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java b/src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java
new file mode 100644
index 00000000..8058d0ea
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/CheckCastIterator.java
@@ -0,0 +1,117 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.Iterator;
14
15import cuchaz.enigma.bytecode.CheckCastIterator.CheckCast;
16import cuchaz.enigma.mapping.ClassEntry;
17import cuchaz.enigma.mapping.MethodEntry;
18import cuchaz.enigma.mapping.Signature;
19import javassist.bytecode.*;
20
21public class CheckCastIterator implements Iterator<CheckCast> {
22
23 public static class CheckCast {
24
25 public String className;
26 public MethodEntry prevMethodEntry;
27
28 public CheckCast(String className, MethodEntry prevMethodEntry) {
29 this.className = className;
30 this.prevMethodEntry = prevMethodEntry;
31 }
32 }
33
34 private ConstPool m_constants;
35 private CodeAttribute m_attribute;
36 private CodeIterator m_iter;
37 private CheckCast m_next;
38
39 public CheckCastIterator(CodeAttribute codeAttribute) throws BadBytecode {
40 m_constants = codeAttribute.getConstPool();
41 m_attribute = codeAttribute;
42 m_iter = m_attribute.iterator();
43
44 m_next = getNext();
45 }
46
47 @Override
48 public boolean hasNext() {
49 return m_next != null;
50 }
51
52 @Override
53 public CheckCast next() {
54 CheckCast out = m_next;
55 try {
56 m_next = getNext();
57 } catch (BadBytecode ex) {
58 throw new Error(ex);
59 }
60 return out;
61 }
62
63 @Override
64 public void remove() {
65 throw new UnsupportedOperationException();
66 }
67
68 private CheckCast getNext() throws BadBytecode {
69 int prevPos = 0;
70 while (m_iter.hasNext()) {
71 int pos = m_iter.next();
72 int opcode = m_iter.byteAt(pos);
73 switch (opcode) {
74 case Opcode.CHECKCAST:
75
76 // get the type of this op code (next two bytes are a classinfo index)
77 MethodEntry prevMethodEntry = getMethodEntry(prevPos);
78 if (prevMethodEntry != null) {
79 return new CheckCast(m_constants.getClassInfo(m_iter.s16bitAt(pos + 1)), prevMethodEntry);
80 }
81 break;
82 }
83 prevPos = pos;
84 }
85 return null;
86 }
87
88 private MethodEntry getMethodEntry(int pos) {
89 switch (m_iter.byteAt(pos)) {
90 case Opcode.INVOKEVIRTUAL:
91 case Opcode.INVOKESTATIC:
92 case Opcode.INVOKEDYNAMIC:
93 case Opcode.INVOKESPECIAL: {
94 int index = m_iter.s16bitAt(pos + 1);
95 return new MethodEntry(
96 new ClassEntry(Descriptor.toJvmName(m_constants.getMethodrefClassName(index))),
97 m_constants.getMethodrefName(index),
98 new Signature(m_constants.getMethodrefType(index))
99 );
100 }
101
102 case Opcode.INVOKEINTERFACE: {
103 int index = m_iter.s16bitAt(pos + 1);
104 return new MethodEntry(
105 new ClassEntry(Descriptor.toJvmName(m_constants.getInterfaceMethodrefClassName(index))),
106 m_constants.getInterfaceMethodrefName(index),
107 new Signature(m_constants.getInterfaceMethodrefType(index))
108 );
109 }
110 }
111 return null;
112 }
113
114 public Iterable<CheckCast> casts() {
115 return () -> CheckCastIterator.this;
116 }
117}
diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java b/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java
new file mode 100644
index 00000000..ad5bab0c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java
@@ -0,0 +1,51 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.CtField;
16import javassist.bytecode.AccessFlag;
17import javassist.bytecode.InnerClassesAttribute;
18
19
20public class ClassProtectifier {
21
22 public static CtClass protectify(CtClass c) {
23
24 // protectify all the fields
25 for (CtField field : c.getDeclaredFields()) {
26 field.setModifiers(protectify(field.getModifiers()));
27 }
28
29 // protectify all the methods and constructors
30 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
31 behavior.setModifiers(protectify(behavior.getModifiers()));
32 }
33
34 // protectify all the inner classes
35 InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
36 if (attr != null) {
37 for (int i = 0; i < attr.tableLength(); i++) {
38 attr.setAccessFlags(i, protectify(attr.accessFlags(i)));
39 }
40 }
41
42 return c;
43 }
44
45 private static int protectify(int flags) {
46 if (AccessFlag.isPrivate(flags)) {
47 flags = AccessFlag.setProtected(flags);
48 }
49 return flags;
50 }
51}
diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java b/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java
new file mode 100644
index 00000000..da86b2b1
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java
@@ -0,0 +1,51 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.CtField;
16import javassist.bytecode.AccessFlag;
17import javassist.bytecode.InnerClassesAttribute;
18
19
20public class ClassPublifier {
21
22 public static CtClass publify(CtClass c) {
23
24 // publify all the fields
25 for (CtField field : c.getDeclaredFields()) {
26 field.setModifiers(publify(field.getModifiers()));
27 }
28
29 // publify all the methods and constructors
30 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
31 behavior.setModifiers(publify(behavior.getModifiers()));
32 }
33
34 // publify all the inner classes
35 InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
36 if (attr != null) {
37 for (int i = 0; i < attr.tableLength(); i++) {
38 attr.setAccessFlags(i, publify(attr.accessFlags(i)));
39 }
40 }
41
42 return c;
43 }
44
45 private static int publify(int flags) {
46 if (AccessFlag.isPrivate(flags) || AccessFlag.isProtected(flags)) {
47 flags = AccessFlag.setPublic(flags);
48 }
49 return flags;
50 }
51}
diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java b/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java
new file mode 100644
index 00000000..548bea75
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java
@@ -0,0 +1,514 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.lang.reflect.InvocationTargetException;
14import java.lang.reflect.Method;
15import java.util.Arrays;
16import java.util.HashMap;
17import java.util.List;
18import java.util.Map;
19
20import cuchaz.enigma.mapping.ClassEntry;
21import cuchaz.enigma.mapping.ClassNameReplacer;
22import cuchaz.enigma.mapping.Translator;
23import javassist.CtClass;
24import javassist.bytecode.*;
25import javassist.bytecode.SignatureAttribute.*;
26
27public class ClassRenamer {
28
29 private enum SignatureType {
30 Class {
31 @Override
32 public String rename(String signature, ReplacerClassMap map) {
33 return renameClassSignature(signature, map);
34 }
35 },
36 Field {
37 @Override
38 public String rename(String signature, ReplacerClassMap map) {
39 return renameFieldSignature(signature, map);
40 }
41 },
42 Method {
43 @Override
44 public String rename(String signature, ReplacerClassMap map) {
45 return renameMethodSignature(signature, map);
46 }
47 };
48
49 public abstract String rename(String signature, ReplacerClassMap map);
50 }
51
52 private static class ReplacerClassMap extends HashMap<String, String> {
53
54 private static final long serialVersionUID = 317915213205066168L;
55
56 private ClassNameReplacer m_replacer;
57
58 public ReplacerClassMap(ClassNameReplacer replacer) {
59 m_replacer = replacer;
60 }
61
62 @Override
63 public String get(Object obj) {
64 if (obj instanceof String) {
65 return get((String) obj);
66 }
67 return null;
68 }
69
70 public String get(String className) {
71 return m_replacer.replace(className);
72 }
73 }
74
75 public static void renameClasses(CtClass c, final Translator translator) {
76 renameClasses(c, className -> {
77 ClassEntry entry = translator.translateEntry(new ClassEntry(className));
78 if (entry != null) {
79 return entry.getName();
80 }
81 return null;
82 });
83 }
84
85 public static void moveAllClassesOutOfDefaultPackage(CtClass c, final String newPackageName) {
86 renameClasses(c, className -> {
87 ClassEntry entry = new ClassEntry(className);
88 if (entry.isInDefaultPackage()) {
89 return newPackageName + "/" + entry.getName();
90 }
91 return null;
92 });
93 }
94
95 public static void moveAllClassesIntoDefaultPackage(CtClass c, final String oldPackageName) {
96 renameClasses(c, className -> {
97 ClassEntry entry = new ClassEntry(className);
98 if (entry.getPackageName().equals(oldPackageName)) {
99 return entry.getSimpleName();
100 }
101 return null;
102 });
103 }
104
105 @SuppressWarnings("unchecked")
106 public static void renameClasses(CtClass c, ClassNameReplacer replacer) {
107
108 // sadly, we can't use CtClass.renameClass() because SignatureAttribute.renameClass() is extremely buggy =(
109
110 ReplacerClassMap map = new ReplacerClassMap(replacer);
111 ClassFile classFile = c.getClassFile();
112
113 // rename the constant pool (covers ClassInfo, MethodTypeInfo, and NameAndTypeInfo)
114 ConstPool constPool = c.getClassFile().getConstPool();
115 constPool.renameClass(map);
116
117 // rename class attributes
118 renameAttributes(classFile.getAttributes(), map, SignatureType.Class);
119
120 // rename methods
121 for (MethodInfo methodInfo : (List<MethodInfo>) classFile.getMethods()) {
122 methodInfo.setDescriptor(Descriptor.rename(methodInfo.getDescriptor(), map));
123 renameAttributes(methodInfo.getAttributes(), map, SignatureType.Method);
124 }
125
126 // rename fields
127 for (FieldInfo fieldInfo : (List<FieldInfo>) classFile.getFields()) {
128 fieldInfo.setDescriptor(Descriptor.rename(fieldInfo.getDescriptor(), map));
129 renameAttributes(fieldInfo.getAttributes(), map, SignatureType.Field);
130 }
131
132 // rename the class name itself last
133 // NOTE: don't use the map here, because setName() calls the buggy SignatureAttribute.renameClass()
134 // we only want to replace exactly this class name
135 String newName = renameClassName(c.getName(), map);
136 if (newName != null) {
137 c.setName(newName);
138 }
139
140 // replace simple names in the InnerClasses attribute too
141 InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
142 if (attr != null) {
143 for (int i = 0; i < attr.tableLength(); i++) {
144
145 // get the inner class full name (which has already been translated)
146 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(attr.innerClass(i)));
147
148 if (attr.innerNameIndex(i) != 0) {
149 // update the inner name
150 attr.setInnerNameIndex(i, constPool.addUtf8Info(classEntry.getInnermostClassName()));
151 }
152
153 /* DEBUG
154 System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i)));
155 */
156 }
157 }
158 }
159
160 @SuppressWarnings("unchecked")
161 private static void renameAttributes(List<AttributeInfo> attributes, ReplacerClassMap map, SignatureType type) {
162 try {
163
164 // make the rename class method accessible
165 Method renameClassMethod = AttributeInfo.class.getDeclaredMethod("renameClass", Map.class);
166 renameClassMethod.setAccessible(true);
167
168 for (AttributeInfo attribute : attributes) {
169 if (attribute instanceof SignatureAttribute) {
170 // this has to be handled specially because SignatureAttribute.renameClass() is buggy as hell
171 SignatureAttribute signatureAttribute = (SignatureAttribute) attribute;
172 String newSignature = type.rename(signatureAttribute.getSignature(), map);
173 if (newSignature != null) {
174 signatureAttribute.setSignature(newSignature);
175 }
176 } else if (attribute instanceof CodeAttribute) {
177 // code attributes have signature attributes too (indirectly)
178 CodeAttribute codeAttribute = (CodeAttribute) attribute;
179 renameAttributes(codeAttribute.getAttributes(), map, type);
180 } else if (attribute instanceof LocalVariableTypeAttribute) {
181 // lvt attributes have signature attributes too
182 LocalVariableTypeAttribute localVariableAttribute = (LocalVariableTypeAttribute) attribute;
183 renameLocalVariableTypeAttribute(localVariableAttribute, map);
184 } else {
185 renameClassMethod.invoke(attribute, map);
186 }
187 }
188
189 } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
190 throw new Error("Unable to call javassist methods by reflection!", ex);
191 }
192 }
193
194 private static void renameLocalVariableTypeAttribute(LocalVariableTypeAttribute attribute, ReplacerClassMap map) {
195
196 // adapted from LocalVariableAttribute.renameClass()
197 ConstPool cp = attribute.getConstPool();
198 int n = attribute.tableLength();
199 byte[] info = attribute.get();
200 for (int i = 0; i < n; ++i) {
201 int pos = i * 10 + 2;
202 int index = ByteArray.readU16bit(info, pos + 6);
203 if (index != 0) {
204 String signature = cp.getUtf8Info(index);
205 String newSignature = renameLocalVariableSignature(signature, map);
206 if (newSignature != null) {
207 ByteArray.write16bit(cp.addUtf8Info(newSignature), info, pos + 6);
208 }
209 }
210 }
211 }
212
213 private static String renameLocalVariableSignature(String signature, ReplacerClassMap map) {
214
215 // for some reason, signatures with . in them don't count as field signatures
216 // looks like anonymous classes delimit with . in stead of $
217 // convert the . to $, but keep track of how many we replace
218 // we need to put them back after we translate
219 int start = signature.lastIndexOf('$') + 1;
220 int numConverted = 0;
221 StringBuilder buf = new StringBuilder(signature);
222 for (int i = buf.length() - 1; i >= start; i--) {
223 char c = buf.charAt(i);
224 if (c == '.') {
225 buf.setCharAt(i, '$');
226 numConverted++;
227 }
228 }
229 signature = buf.toString();
230
231 // translate
232 String newSignature = renameFieldSignature(signature, map);
233 if (newSignature != null) {
234
235 // put the delimiters back
236 buf = new StringBuilder(newSignature);
237 for (int i = buf.length() - 1; i >= 0 && numConverted > 0; i--) {
238 char c = buf.charAt(i);
239 if (c == '$') {
240 buf.setCharAt(i, '.');
241 numConverted--;
242 }
243 }
244 assert (numConverted == 0);
245 newSignature = buf.toString();
246
247 return newSignature;
248 }
249
250 return null;
251 }
252
253 private static String renameClassSignature(String signature, ReplacerClassMap map) {
254 try {
255 ClassSignature type = renameType(SignatureAttribute.toClassSignature(signature), map);
256 if (type != null) {
257 return type.encode();
258 }
259 return null;
260 } catch (BadBytecode ex) {
261 throw new Error("Can't parse field signature: " + signature);
262 }
263 }
264
265 private static String renameFieldSignature(String signature, ReplacerClassMap map) {
266 try {
267 ObjectType type = renameType(SignatureAttribute.toFieldSignature(signature), map);
268 if (type != null) {
269 return type.encode();
270 }
271 return null;
272 } catch (BadBytecode ex) {
273 throw new Error("Can't parse class signature: " + signature);
274 }
275 }
276
277 private static String renameMethodSignature(String signature, ReplacerClassMap map) {
278 try {
279 MethodSignature type = renameType(SignatureAttribute.toMethodSignature(signature), map);
280 if (type != null) {
281 return type.encode();
282 }
283 return null;
284 } catch (BadBytecode ex) {
285 throw new Error("Can't parse method signature: " + signature);
286 }
287 }
288
289 private static ClassSignature renameType(ClassSignature type, ReplacerClassMap map) {
290
291 TypeParameter[] typeParamTypes = type.getParameters();
292 if (typeParamTypes != null) {
293 typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length);
294 for (int i = 0; i < typeParamTypes.length; i++) {
295 TypeParameter newParamType = renameType(typeParamTypes[i], map);
296 if (newParamType != null) {
297 typeParamTypes[i] = newParamType;
298 }
299 }
300 }
301
302 ClassType superclassType = type.getSuperClass();
303 if (superclassType != ClassType.OBJECT) {
304 ClassType newSuperclassType = renameType(superclassType, map);
305 if (newSuperclassType != null) {
306 superclassType = newSuperclassType;
307 }
308 }
309
310 ClassType[] interfaceTypes = type.getInterfaces();
311 if (interfaceTypes != null) {
312 interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length);
313 for (int i = 0; i < interfaceTypes.length; i++) {
314 ClassType newInterfaceType = renameType(interfaceTypes[i], map);
315 if (newInterfaceType != null) {
316 interfaceTypes[i] = newInterfaceType;
317 }
318 }
319 }
320
321 return new ClassSignature(typeParamTypes, superclassType, interfaceTypes);
322 }
323
324 private static MethodSignature renameType(MethodSignature type, ReplacerClassMap map) {
325
326 TypeParameter[] typeParamTypes = type.getTypeParameters();
327 if (typeParamTypes != null) {
328 typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length);
329 for (int i = 0; i < typeParamTypes.length; i++) {
330 TypeParameter newParamType = renameType(typeParamTypes[i], map);
331 if (newParamType != null) {
332 typeParamTypes[i] = newParamType;
333 }
334 }
335 }
336
337 Type[] paramTypes = type.getParameterTypes();
338 if (paramTypes != null) {
339 paramTypes = Arrays.copyOf(paramTypes, paramTypes.length);
340 for (int i = 0; i < paramTypes.length; i++) {
341 Type newParamType = renameType(paramTypes[i], map);
342 if (newParamType != null) {
343 paramTypes[i] = newParamType;
344 }
345 }
346 }
347
348 Type returnType = type.getReturnType();
349 if (returnType != null) {
350 Type newReturnType = renameType(returnType, map);
351 if (newReturnType != null) {
352 returnType = newReturnType;
353 }
354 }
355
356 ObjectType[] exceptionTypes = type.getExceptionTypes();
357 if (exceptionTypes != null) {
358 exceptionTypes = Arrays.copyOf(exceptionTypes, exceptionTypes.length);
359 for (int i = 0; i < exceptionTypes.length; i++) {
360 ObjectType newExceptionType = renameType(exceptionTypes[i], map);
361 if (newExceptionType != null) {
362 exceptionTypes[i] = newExceptionType;
363 }
364 }
365 }
366
367 return new MethodSignature(typeParamTypes, paramTypes, returnType, exceptionTypes);
368 }
369
370 private static Type renameType(Type type, ReplacerClassMap map) {
371 if (type instanceof ObjectType) {
372 return renameType((ObjectType) type, map);
373 } else if (type instanceof BaseType) {
374 return renameType((BaseType) type, map);
375 } else {
376 throw new Error("Don't know how to rename type " + type.getClass());
377 }
378 }
379
380 private static ObjectType renameType(ObjectType type, ReplacerClassMap map) {
381 if (type instanceof ArrayType) {
382 return renameType((ArrayType) type, map);
383 } else if (type instanceof ClassType) {
384 return renameType((ClassType) type, map);
385 } else if (type instanceof TypeVariable) {
386 return renameType((TypeVariable) type, map);
387 } else {
388 throw new Error("Don't know how to rename type " + type.getClass());
389 }
390 }
391
392 private static BaseType renameType(BaseType type, ReplacerClassMap map) {
393 // don't have to rename primitives
394 return null;
395 }
396
397 private static TypeVariable renameType(TypeVariable type, ReplacerClassMap map) {
398 // don't have to rename template args
399 return null;
400 }
401
402 private static ClassType renameType(ClassType type, ReplacerClassMap map) {
403
404 // translate type args
405 TypeArgument[] args = type.getTypeArguments();
406 if (args != null) {
407 args = Arrays.copyOf(args, args.length);
408 for (int i = 0; i < args.length; i++) {
409 TypeArgument newType = renameType(args[i], map);
410 if (newType != null) {
411 args[i] = newType;
412 }
413 }
414 }
415
416 if (type instanceof NestedClassType) {
417 NestedClassType nestedType = (NestedClassType) type;
418
419 // translate the name
420 String name = getClassName(type);
421 String newName = map.get(name);
422 if (newName != null) {
423 name = new ClassEntry(newName).getInnermostClassName();
424 }
425
426 // translate the parent class too
427 ClassType parent = renameType(nestedType.getDeclaringClass(), map);
428 if (parent == null) {
429 parent = nestedType.getDeclaringClass();
430 }
431
432 return new NestedClassType(parent, name, args);
433 } else {
434
435 // translate the name
436 String name = type.getName();
437 String newName = renameClassName(name, map);
438 if (newName != null) {
439 name = newName;
440 }
441
442 return new ClassType(name, args);
443 }
444 }
445
446 private static String getClassName(ClassType type) {
447 if (type instanceof NestedClassType) {
448 NestedClassType nestedType = (NestedClassType) type;
449 return getClassName(nestedType.getDeclaringClass()) + "$" + Descriptor.toJvmName(type.getName().replace('.', '$'));
450 } else {
451 return Descriptor.toJvmName(type.getName());
452 }
453 }
454
455 private static String renameClassName(String name, ReplacerClassMap map) {
456 String newName = map.get(Descriptor.toJvmName(name));
457 if (newName != null) {
458 return Descriptor.toJavaName(newName);
459 }
460 return null;
461 }
462
463 private static TypeArgument renameType(TypeArgument type, ReplacerClassMap map) {
464 ObjectType subType = type.getType();
465 if (subType != null) {
466 ObjectType newSubType = renameType(subType, map);
467 if (newSubType != null) {
468 switch (type.getKind()) {
469 case ' ':
470 return new TypeArgument(newSubType);
471 case '+':
472 return TypeArgument.subclassOf(newSubType);
473 case '-':
474 return TypeArgument.superOf(newSubType);
475 default:
476 throw new Error("Unknown type kind: " + type.getKind());
477 }
478 }
479 }
480 return null;
481 }
482
483 private static ArrayType renameType(ArrayType type, ReplacerClassMap map) {
484 Type newSubType = renameType(type.getComponentType(), map);
485 if (newSubType != null) {
486 return new ArrayType(type.getDimension(), newSubType);
487 }
488 return null;
489 }
490
491 private static TypeParameter renameType(TypeParameter type, ReplacerClassMap map) {
492
493 ObjectType superclassType = type.getClassBound();
494 if (superclassType != null) {
495 ObjectType newSuperclassType = renameType(superclassType, map);
496 if (newSuperclassType != null) {
497 superclassType = newSuperclassType;
498 }
499 }
500
501 ObjectType[] interfaceTypes = type.getInterfaceBound();
502 if (interfaceTypes != null) {
503 interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length);
504 for (int i = 0; i < interfaceTypes.length; i++) {
505 ObjectType newInterfaceType = renameType(interfaceTypes[i], map);
506 if (newInterfaceType != null) {
507 interfaceTypes[i] = newInterfaceType;
508 }
509 }
510 }
511
512 return new TypeParameter(type.getName(), superclassType, interfaceTypes);
513 }
514}
diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java b/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java
new file mode 100644
index 00000000..ef197cbe
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/ClassTranslator.java
@@ -0,0 +1,151 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import cuchaz.enigma.mapping.*;
14import javassist.CtBehavior;
15import javassist.CtClass;
16import javassist.CtField;
17import javassist.CtMethod;
18import javassist.bytecode.ConstPool;
19import javassist.bytecode.Descriptor;
20import javassist.bytecode.EnclosingMethodAttribute;
21import javassist.bytecode.SourceFileAttribute;
22
23public class ClassTranslator {
24
25 private Translator m_translator;
26
27 public ClassTranslator(Translator translator) {
28 m_translator = translator;
29 }
30
31 public void translate(CtClass c) {
32
33 // NOTE: the order of these translations is very important
34
35 // translate all the field and method references in the code by editing the constant pool
36 ConstPool constants = c.getClassFile().getConstPool();
37 ConstPoolEditor editor = new ConstPoolEditor(constants);
38 for (int i = 1; i < constants.getSize(); i++) {
39 switch (constants.getTag(i)) {
40
41 case ConstPool.CONST_Fieldref: {
42
43 // translate the name and type
44 FieldEntry entry = EntryFactory.getFieldEntry(
45 Descriptor.toJvmName(constants.getFieldrefClassName(i)),
46 constants.getFieldrefName(i),
47 constants.getFieldrefType(i)
48 );
49 FieldEntry translatedEntry = m_translator.translateEntry(entry);
50 if (!entry.equals(translatedEntry)) {
51 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getType().toString());
52 }
53 }
54 break;
55
56 case ConstPool.CONST_Methodref:
57 case ConstPool.CONST_InterfaceMethodref: {
58
59 // translate the name and type (ie signature)
60 BehaviorEntry entry = EntryFactory.getBehaviorEntry(
61 Descriptor.toJvmName(editor.getMemberrefClassname(i)),
62 editor.getMemberrefName(i),
63 editor.getMemberrefType(i)
64 );
65 BehaviorEntry translatedEntry = m_translator.translateEntry(entry);
66 if (!entry.equals(translatedEntry)) {
67 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString());
68 }
69 }
70 break;
71 }
72 }
73
74 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
75
76 // translate all the fields
77 for (CtField field : c.getDeclaredFields()) {
78
79 // translate the name
80 FieldEntry entry = EntryFactory.getFieldEntry(field);
81 String translatedName = m_translator.translate(entry);
82 if (translatedName != null) {
83 field.setName(translatedName);
84 }
85
86 // translate the type
87 Type translatedType = m_translator.translateType(entry.getType());
88 field.getFieldInfo().setDescriptor(translatedType.toString());
89 }
90
91 // translate all the methods and constructors
92 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
93
94 BehaviorEntry entry = EntryFactory.getBehaviorEntry(behavior);
95
96 if (behavior instanceof CtMethod) {
97 CtMethod method = (CtMethod) behavior;
98
99 // translate the name
100 String translatedName = m_translator.translate(entry);
101 if (translatedName != null) {
102 method.setName(translatedName);
103 }
104 }
105
106 if (entry.getSignature() != null) {
107 // translate the signature
108 Signature translatedSignature = m_translator.translateSignature(entry.getSignature());
109 behavior.getMethodInfo().setDescriptor(translatedSignature.toString());
110 }
111 }
112
113 // translate the EnclosingMethod attribute
114 EnclosingMethodAttribute enclosingMethodAttr = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag);
115 if (enclosingMethodAttr != null) {
116
117 if (enclosingMethodAttr.methodIndex() == 0) {
118 BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(Descriptor.toJvmName(enclosingMethodAttr.className()));
119 BehaviorEntry deobfBehaviorEntry = m_translator.translateEntry(obfBehaviorEntry);
120 c.getClassFile().addAttribute(new EnclosingMethodAttribute(
121 constants,
122 deobfBehaviorEntry.getClassName()
123 ));
124 } else {
125 BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(
126 Descriptor.toJvmName(enclosingMethodAttr.className()),
127 enclosingMethodAttr.methodName(),
128 enclosingMethodAttr.methodDescriptor()
129 );
130 BehaviorEntry deobfBehaviorEntry = m_translator.translateEntry(obfBehaviorEntry);
131 c.getClassFile().addAttribute(new EnclosingMethodAttribute(
132 constants,
133 deobfBehaviorEntry.getClassName(),
134 deobfBehaviorEntry.getName(),
135 deobfBehaviorEntry.getSignature().toString()
136 ));
137 }
138 }
139
140 // translate all the class names referenced in the code
141 // the above code only changed method/field/reference names and types, but not the rest of the class references
142 ClassRenamer.renameClasses(c, m_translator);
143
144 // translate the source file attribute too
145 ClassEntry deobfClassEntry = m_translator.translateEntry(classEntry);
146 if (deobfClassEntry != null) {
147 String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOutermostClassEntry().getSimpleName()) + ".java";
148 c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile));
149 }
150 }
151}
diff --git a/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java b/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java
new file mode 100644
index 00000000..0082a72c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java
@@ -0,0 +1,263 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.io.DataInputStream;
14import java.io.DataOutputStream;
15import java.lang.reflect.Constructor;
16import java.lang.reflect.Field;
17import java.lang.reflect.Method;
18import java.util.HashMap;
19
20import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor;
21import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
22import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor;
23import javassist.bytecode.ConstPool;
24import javassist.bytecode.Descriptor;
25
26public class ConstPoolEditor {
27
28 private static Method m_getItem;
29 private static Method m_addItem;
30 private static Method m_addItem0;
31 private static Field m_items;
32 private static Field m_cache;
33 private static Field m_numItems;
34 private static Field m_objects;
35 private static Field m_elements;
36 private static Method m_methodWritePool;
37 private static Constructor<ConstPool> m_constructorPool;
38
39 static {
40 try {
41 m_getItem = ConstPool.class.getDeclaredMethod("getItem", int.class);
42 m_getItem.setAccessible(true);
43
44 m_addItem = ConstPool.class.getDeclaredMethod("addItem", Class.forName("javassist.bytecode.ConstInfo"));
45 m_addItem.setAccessible(true);
46
47 m_addItem0 = ConstPool.class.getDeclaredMethod("addItem0", Class.forName("javassist.bytecode.ConstInfo"));
48 m_addItem0.setAccessible(true);
49
50 m_items = ConstPool.class.getDeclaredField("items");
51 m_items.setAccessible(true);
52
53 m_cache = ConstPool.class.getDeclaredField("itemsCache");
54 m_cache.setAccessible(true);
55
56 m_numItems = ConstPool.class.getDeclaredField("numOfItems");
57 m_numItems.setAccessible(true);
58
59 m_objects = Class.forName("javassist.bytecode.LongVector").getDeclaredField("objects");
60 m_objects.setAccessible(true);
61
62 m_elements = Class.forName("javassist.bytecode.LongVector").getDeclaredField("elements");
63 m_elements.setAccessible(true);
64
65 m_methodWritePool = ConstPool.class.getDeclaredMethod("write", DataOutputStream.class);
66 m_methodWritePool.setAccessible(true);
67
68 m_constructorPool = ConstPool.class.getDeclaredConstructor(DataInputStream.class);
69 m_constructorPool.setAccessible(true);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74
75 private ConstPool m_pool;
76
77 public ConstPoolEditor(ConstPool pool) {
78 m_pool = pool;
79 }
80
81 public void writePool(DataOutputStream out) {
82 try {
83 m_methodWritePool.invoke(m_pool, out);
84 } catch (Exception ex) {
85 throw new Error(ex);
86 }
87 }
88
89 public static ConstPool readPool(DataInputStream in) {
90 try {
91 return m_constructorPool.newInstance(in);
92 } catch (Exception ex) {
93 throw new Error(ex);
94 }
95 }
96
97 public String getMemberrefClassname(int memberrefIndex) {
98 return Descriptor.toJvmName(m_pool.getClassInfo(m_pool.getMemberClass(memberrefIndex)));
99 }
100
101 public String getMemberrefName(int memberrefIndex) {
102 return m_pool.getUtf8Info(m_pool.getNameAndTypeName(m_pool.getMemberNameAndType(memberrefIndex)));
103 }
104
105 public String getMemberrefType(int memberrefIndex) {
106 return m_pool.getUtf8Info(m_pool.getNameAndTypeDescriptor(m_pool.getMemberNameAndType(memberrefIndex)));
107 }
108
109 public ConstInfoAccessor getItem(int index) {
110 try {
111 Object entry = m_getItem.invoke(m_pool, index);
112 if (entry == null) {
113 return null;
114 }
115 return new ConstInfoAccessor(entry);
116 } catch (Exception ex) {
117 throw new Error(ex);
118 }
119 }
120
121 public int addItem(Object item) {
122 try {
123 return (Integer) m_addItem.invoke(m_pool, item);
124 } catch (Exception ex) {
125 throw new Error(ex);
126 }
127 }
128
129 public int addItemForceNew(Object item) {
130 try {
131 return (Integer) m_addItem0.invoke(m_pool, item);
132 } catch (Exception ex) {
133 throw new Error(ex);
134 }
135 }
136
137 @SuppressWarnings("rawtypes")
138 public void removeLastItem() {
139 try {
140 // remove the item from the cache
141 HashMap cache = getCache();
142 if (cache != null) {
143 Object item = getItem(m_pool.getSize() - 1);
144 cache.remove(item);
145 }
146
147 // remove the actual item
148 // based off of LongVector.addElement()
149 Object items = m_items.get(m_pool);
150 Object[][] objects = (Object[][]) m_objects.get(items);
151 int numElements = (Integer) m_elements.get(items) - 1;
152 int nth = numElements >> 7;
153 int offset = numElements & (128 - 1);
154 objects[nth][offset] = null;
155
156 // decrement the number of items
157 m_elements.set(items, numElements);
158 m_numItems.set(m_pool, (Integer) m_numItems.get(m_pool) - 1);
159 } catch (Exception ex) {
160 throw new Error(ex);
161 }
162 }
163
164 @SuppressWarnings("rawtypes")
165 public HashMap getCache() {
166 try {
167 return (HashMap) m_cache.get(m_pool);
168 } catch (Exception ex) {
169 throw new Error(ex);
170 }
171 }
172
173 @SuppressWarnings({"rawtypes", "unchecked"})
174 public void changeMemberrefNameAndType(int memberrefIndex, String newName, String newType) {
175 // NOTE: when changing values, we always need to copy-on-write
176 try {
177 // get the memberref item
178 Object item = getItem(memberrefIndex).getItem();
179
180 // update the cache
181 HashMap cache = getCache();
182 if (cache != null) {
183 cache.remove(item);
184 }
185
186 new MemberRefInfoAccessor(item).setNameAndTypeIndex(m_pool.addNameAndTypeInfo(newName, newType));
187
188 // update the cache
189 if (cache != null) {
190 cache.put(item, item);
191 }
192 } catch (Exception ex) {
193 throw new Error(ex);
194 }
195
196 // make sure the change worked
197 assert (newName.equals(getMemberrefName(memberrefIndex)));
198 assert (newType.equals(getMemberrefType(memberrefIndex)));
199 }
200
201 @SuppressWarnings({"rawtypes", "unchecked"})
202 public void changeClassName(int classNameIndex, String newName) {
203 // NOTE: when changing values, we always need to copy-on-write
204 try {
205 // get the class item
206 Object item = getItem(classNameIndex).getItem();
207
208 // update the cache
209 HashMap cache = getCache();
210 if (cache != null) {
211 cache.remove(item);
212 }
213
214 // add the new name and repoint the name-and-type to it
215 new ClassInfoAccessor(item).setNameIndex(m_pool.addUtf8Info(newName));
216
217 // update the cache
218 if (cache != null) {
219 cache.put(item, item);
220 }
221 } catch (Exception ex) {
222 throw new Error(ex);
223 }
224 }
225
226 public static ConstPool newConstPool() {
227 // const pool expects the name of a class to initialize itself
228 // but we want an empty pool
229 // so give it a bogus name, and then clear the entries afterwards
230 ConstPool pool = new ConstPool("a");
231
232 ConstPoolEditor editor = new ConstPoolEditor(pool);
233 int size = pool.getSize();
234 for (int i = 0; i < size - 1; i++) {
235 editor.removeLastItem();
236 }
237
238 // make sure the pool is actually empty
239 // although, in this case "empty" means one thing in it
240 // the JVM spec says index 0 should be reserved
241 assert (pool.getSize() == 1);
242 assert (editor.getItem(0) == null);
243 assert (editor.getItem(1) == null);
244 assert (editor.getItem(2) == null);
245 assert (editor.getItem(3) == null);
246
247 // also, clear the cache
248 editor.getCache().clear();
249
250 return pool;
251 }
252
253 public String dump() {
254 StringBuilder buf = new StringBuilder();
255 for (int i = 1; i < m_pool.getSize(); i++) {
256 buf.append(String.format("%4d", i));
257 buf.append(" ");
258 buf.append(getItem(i).toString());
259 buf.append("\n");
260 }
261 return buf.toString();
262 }
263}
diff --git a/src/main/java/cuchaz/enigma/bytecode/InfoType.java b/src/main/java/cuchaz/enigma/bytecode/InfoType.java
new file mode 100644
index 00000000..89940d9e
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/InfoType.java
@@ -0,0 +1,301 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import com.google.common.collect.Lists;
14import com.google.common.collect.Maps;
15
16import java.util.Collection;
17import java.util.List;
18import java.util.Map;
19
20import cuchaz.enigma.bytecode.accessors.*;
21
22public enum InfoType {
23
24 Utf8Info(1, 0),
25 IntegerInfo(3, 0),
26 FloatInfo(4, 0),
27 LongInfo(5, 0),
28 DoubleInfo(6, 0),
29 ClassInfo(7, 1) {
30 @Override
31 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
32 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
33 gatherIndexTree(indices, editor, accessor.getNameIndex());
34 }
35
36 @Override
37 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
38 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
39 accessor.setNameIndex(remapIndex(map, accessor.getNameIndex()));
40 }
41
42 @Override
43 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
44 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
45 ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex());
46 return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag();
47 }
48 },
49 StringInfo(8, 1) {
50 @Override
51 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
52 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
53 gatherIndexTree(indices, editor, accessor.getStringIndex());
54 }
55
56 @Override
57 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
58 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
59 accessor.setStringIndex(remapIndex(map, accessor.getStringIndex()));
60 }
61
62 @Override
63 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
64 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
65 ConstInfoAccessor stringEntry = pool.getItem(accessor.getStringIndex());
66 return stringEntry != null && stringEntry.getTag() == Utf8Info.getTag();
67 }
68 },
69 FieldRefInfo(9, 2) {
70 @Override
71 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
72 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
73 gatherIndexTree(indices, editor, accessor.getClassIndex());
74 gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex());
75 }
76
77 @Override
78 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
79 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
80 accessor.setClassIndex(remapIndex(map, accessor.getClassIndex()));
81 accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex()));
82 }
83
84 @Override
85 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
86 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
87 ConstInfoAccessor classEntry = pool.getItem(accessor.getClassIndex());
88 ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex());
89 return classEntry != null && classEntry.getTag() == ClassInfo.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag();
90 }
91 },
92 // same as FieldRefInfo
93 MethodRefInfo(10, 2) {
94 @Override
95 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
96 FieldRefInfo.gatherIndexTree(indices, editor, entry);
97 }
98
99 @Override
100 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
101 FieldRefInfo.remapIndices(map, entry);
102 }
103
104 @Override
105 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
106 return FieldRefInfo.subIndicesAreValid(entry, pool);
107 }
108 },
109 // same as FieldRefInfo
110 InterfaceMethodRefInfo(11, 2) {
111 @Override
112 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
113 FieldRefInfo.gatherIndexTree(indices, editor, entry);
114 }
115
116 @Override
117 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
118 FieldRefInfo.remapIndices(map, entry);
119 }
120
121 @Override
122 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
123 return FieldRefInfo.subIndicesAreValid(entry, pool);
124 }
125 },
126 NameAndTypeInfo(12, 1) {
127 @Override
128 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
129 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
130 gatherIndexTree(indices, editor, accessor.getNameIndex());
131 gatherIndexTree(indices, editor, accessor.getTypeIndex());
132 }
133
134 @Override
135 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
136 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
137 accessor.setNameIndex(remapIndex(map, accessor.getNameIndex()));
138 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
139 }
140
141 @Override
142 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
143 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
144 ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex());
145 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
146 return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag() && typeEntry != null && typeEntry.getTag() == Utf8Info.getTag();
147 }
148 },
149 MethodHandleInfo(15, 3) {
150 @Override
151 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
152 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
153 gatherIndexTree(indices, editor, accessor.getTypeIndex());
154 gatherIndexTree(indices, editor, accessor.getMethodRefIndex());
155 }
156
157 @Override
158 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
159 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
160 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
161 accessor.setMethodRefIndex(remapIndex(map, accessor.getMethodRefIndex()));
162 }
163
164 @Override
165 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
166 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
167 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
168 ConstInfoAccessor methodRefEntry = pool.getItem(accessor.getMethodRefIndex());
169 return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag() && methodRefEntry != null && methodRefEntry.getTag() == MethodRefInfo.getTag();
170 }
171 },
172 MethodTypeInfo(16, 1) {
173 @Override
174 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
175 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
176 gatherIndexTree(indices, editor, accessor.getTypeIndex());
177 }
178
179 @Override
180 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
181 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
182 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
183 }
184
185 @Override
186 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
187 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
188 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
189 return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag();
190 }
191 },
192 InvokeDynamicInfo(18, 2) {
193 @Override
194 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
195 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
196 gatherIndexTree(indices, editor, accessor.getBootstrapIndex());
197 gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex());
198 }
199
200 @Override
201 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
202 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
203 accessor.setBootstrapIndex(remapIndex(map, accessor.getBootstrapIndex()));
204 accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex()));
205 }
206
207 @Override
208 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
209 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
210 ConstInfoAccessor bootstrapEntry = pool.getItem(accessor.getBootstrapIndex());
211 ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex());
212 return bootstrapEntry != null && bootstrapEntry.getTag() == Utf8Info.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag();
213 }
214 };
215
216 private static Map<Integer, InfoType> m_types;
217
218 static {
219 m_types = Maps.newTreeMap();
220 for (InfoType type : values()) {
221 m_types.put(type.getTag(), type);
222 }
223 }
224
225 private int m_tag;
226 private int m_level;
227
228 InfoType(int tag, int level) {
229 m_tag = tag;
230 m_level = level;
231 }
232
233 public int getTag() {
234 return m_tag;
235 }
236
237 public int getLevel() {
238 return m_level;
239 }
240
241 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
242 // by default, do nothing
243 }
244
245 public void remapIndices(Map<Integer, Integer> map, ConstInfoAccessor entry) {
246 // by default, do nothing
247 }
248
249 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
250 // by default, everything is good
251 return true;
252 }
253
254 public boolean selfIndexIsValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
255 ConstInfoAccessor entryCheck = pool.getItem(entry.getIndex());
256 if (entryCheck == null) {
257 return false;
258 }
259 return entryCheck.getItem().equals(entry.getItem());
260 }
261
262 public static InfoType getByTag(int tag) {
263 return m_types.get(tag);
264 }
265
266 public static List<InfoType> getByLevel(int level) {
267 List<InfoType> types = Lists.newArrayList();
268 for (InfoType type : values()) {
269 if (type.getLevel() == level) {
270 types.add(type);
271 }
272 }
273 return types;
274 }
275
276 public static List<InfoType> getSortedByLevel() {
277 List<InfoType> types = Lists.newArrayList();
278 types.addAll(getByLevel(0));
279 types.addAll(getByLevel(1));
280 types.addAll(getByLevel(2));
281 types.addAll(getByLevel(3));
282 return types;
283 }
284
285 public static void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, int index) {
286 // add own index
287 indices.add(index);
288
289 // recurse
290 ConstInfoAccessor entry = editor.getItem(index);
291 entry.getType().gatherIndexTree(indices, editor, entry);
292 }
293
294 private static int remapIndex(Map<Integer, Integer> map, int index) {
295 Integer newIndex = map.get(index);
296 if (newIndex == null) {
297 newIndex = index;
298 }
299 return newIndex;
300 }
301}
diff --git a/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java
new file mode 100644
index 00000000..25ac7d67
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/InnerClassWriter.java
@@ -0,0 +1,132 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import com.google.common.collect.Lists;
14
15import java.util.Collection;
16import java.util.List;
17
18import cuchaz.enigma.analysis.JarIndex;
19import cuchaz.enigma.mapping.BehaviorEntry;
20import cuchaz.enigma.mapping.ClassEntry;
21import cuchaz.enigma.mapping.EntryFactory;
22import javassist.CtClass;
23import javassist.bytecode.AccessFlag;
24import javassist.bytecode.ConstPool;
25import javassist.bytecode.EnclosingMethodAttribute;
26import javassist.bytecode.InnerClassesAttribute;
27
28public class InnerClassWriter {
29
30 private JarIndex m_index;
31
32 public InnerClassWriter(JarIndex index) {
33 m_index = index;
34 }
35
36 public void write(CtClass c) {
37
38 // don't change anything if there's already an attribute there
39 InnerClassesAttribute oldAttr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag);
40 if (oldAttr != null) {
41 // bail!
42 return;
43 }
44
45 ClassEntry obfClassEntry = EntryFactory.getClassEntry(c);
46 List<ClassEntry> obfClassChain = m_index.getObfClassChain(obfClassEntry);
47
48 boolean isInnerClass = obfClassChain.size() > 1;
49 if (isInnerClass) {
50
51 // it's an inner class, rename it to the fully qualified name
52 c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName());
53
54 BehaviorEntry caller = m_index.getAnonymousClassCaller(obfClassEntry);
55 if (caller != null) {
56
57 // write the enclosing method attribute
58 if (caller.getName().equals("<clinit>")) {
59 c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName()));
60 } else {
61 c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature().toString()));
62 }
63 }
64 }
65
66 // does this class have any inner classes?
67 Collection<ClassEntry> obfInnerClassEntries = m_index.getInnerClasses(obfClassEntry);
68
69 if (isInnerClass || !obfInnerClassEntries.isEmpty()) {
70
71 // create an inner class attribute
72 InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool());
73 c.getClassFile().addAttribute(attr);
74
75 // write the ancestry, but not the outermost class
76 for (int i = 1; i < obfClassChain.size(); i++) {
77 ClassEntry obfInnerClassEntry = obfClassChain.get(i);
78 writeInnerClass(attr, obfClassChain, obfInnerClassEntry);
79
80 // update references to use the fully qualified inner class name
81 c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(obfClassChain).getName());
82 }
83
84 // write the inner classes
85 for (ClassEntry obfInnerClassEntry : obfInnerClassEntries) {
86
87 // extend the class chain
88 List<ClassEntry> extendedObfClassChain = Lists.newArrayList(obfClassChain);
89 extendedObfClassChain.add(obfInnerClassEntry);
90
91 writeInnerClass(attr, extendedObfClassChain, obfInnerClassEntry);
92
93 // update references to use the fully qualified inner class name
94 c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName());
95 }
96 }
97 }
98
99 private void writeInnerClass(InnerClassesAttribute attr, List<ClassEntry> obfClassChain, ClassEntry obfClassEntry) {
100
101 // get the new inner class name
102 ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain);
103 ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry();
104
105 // here's what the JVM spec says about the InnerClasses attribute
106 // append(inner, parent, 0 if anonymous else simple name, flags);
107
108 // update the attribute with this inner class
109 ConstPool constPool = attr.getConstPool();
110 int innerClassIndex = constPool.addClassInfo(obfInnerClassEntry.getName());
111 int parentClassIndex = constPool.addClassInfo(obfOuterClassEntry.getName());
112 int innerClassNameIndex = 0;
113 int accessFlags = AccessFlag.PUBLIC;
114 // TODO: need to figure out if we can put static or not
115 if (!m_index.isAnonymousClass(obfClassEntry)) {
116 innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnermostClassName());
117 }
118
119 attr.append(innerClassIndex, parentClassIndex, innerClassNameIndex, accessFlags);
120
121 /* DEBUG
122 System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)",
123 obfClassEntry,
124 attr.innerClass(attr.tableLength() - 1),
125 attr.outerClass(attr.tableLength() - 1),
126 attr.innerName(attr.tableLength() - 1),
127 Constants.NonePackage + "/" + obfInnerClassName,
128 obfClassEntry.getName()
129 ));
130 */
131 }
132}
diff --git a/src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java b/src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java
new file mode 100644
index 00000000..d0ce107f
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/LocalVariableRenamer.java
@@ -0,0 +1,119 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import cuchaz.enigma.mapping.ArgumentEntry;
14import cuchaz.enigma.mapping.BehaviorEntry;
15import cuchaz.enigma.mapping.EntryFactory;
16import cuchaz.enigma.mapping.Translator;
17import javassist.CtBehavior;
18import javassist.CtClass;
19import javassist.bytecode.*;
20
21
22public class LocalVariableRenamer {
23
24 private Translator m_translator;
25
26 public LocalVariableRenamer(Translator translator) {
27 m_translator = translator;
28 }
29
30 public void rename(CtClass c) {
31 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
32
33 // if there's a local variable table, just rename everything to v1, v2, v3, ... for now
34 CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute();
35 if (codeAttribute == null) {
36 continue;
37 }
38
39 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
40 ConstPool constants = c.getClassFile().getConstPool();
41
42 LocalVariableAttribute table = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
43 if (table != null) {
44 renameLVT(behaviorEntry, constants, table);
45 }
46
47 LocalVariableTypeAttribute typeTable = (LocalVariableTypeAttribute) codeAttribute.getAttribute(LocalVariableAttribute.typeTag);
48 if (typeTable != null) {
49 renameLVTT(typeTable, table);
50 }
51 }
52 }
53
54 // DEBUG
55 @SuppressWarnings("unused")
56 private void dumpTable(LocalVariableAttribute table) {
57 for (int i = 0; i < table.tableLength(); i++) {
58 System.out.println(String.format("\t%d (%d): %s %s",
59 i, table.index(i), table.variableName(i), table.descriptor(i)
60 ));
61 }
62 }
63
64 private void renameLVT(BehaviorEntry behaviorEntry, ConstPool constants, LocalVariableAttribute table) {
65
66 // skip empty tables
67 if (table.tableLength() <= 0) {
68 return;
69 }
70
71 // where do we start counting variables?
72 int starti = 0;
73 if (table.variableName(0).equals("this")) {
74 // skip the "this" variable
75 starti = 1;
76 }
77
78 // rename method arguments first
79 int numArgs = 0;
80 if (behaviorEntry.getSignature() != null) {
81 numArgs = behaviorEntry.getSignature().getArgumentTypes().size();
82 for (int i = starti; i < starti + numArgs && i < table.tableLength(); i++) {
83 int argi = i - starti;
84 String argName = m_translator.translate(new ArgumentEntry(behaviorEntry, argi, ""));
85 if (argName == null) {
86 argName = "a" + (argi + 1);
87 }
88 renameVariable(table, i, constants.addUtf8Info(argName));
89 }
90 }
91
92 // then rename the rest of the args, if any
93 for (int i = starti + numArgs; i < table.tableLength(); i++) {
94 int firstIndex = table.index(starti + numArgs);
95 renameVariable(table, i, constants.addUtf8Info("v" + (table.index(i) - firstIndex + 1)));
96 }
97 }
98
99 private void renameLVTT(LocalVariableTypeAttribute typeTable, LocalVariableAttribute table) {
100 // rename args to the same names as in the LVT
101 for (int i = 0; i < typeTable.tableLength(); i++) {
102 renameVariable(typeTable, i, getNameIndex(table, typeTable.index(i)));
103 }
104 }
105
106 private void renameVariable(LocalVariableAttribute table, int i, int stringId) {
107 // based off of LocalVariableAttribute.nameIndex()
108 ByteArray.write16bit(stringId, table.get(), i * 10 + 6);
109 }
110
111 private int getNameIndex(LocalVariableAttribute table, int index) {
112 for (int i = 0; i < table.tableLength(); i++) {
113 if (table.index(i) == index) {
114 return table.nameIndex(i);
115 }
116 }
117 return 0;
118 }
119}
diff --git a/src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java b/src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java
new file mode 100644
index 00000000..e53e8e70
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/MethodParameterWriter.java
@@ -0,0 +1,66 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.ArrayList;
14import java.util.List;
15
16import cuchaz.enigma.mapping.*;
17import javassist.CtBehavior;
18import javassist.CtClass;
19import javassist.bytecode.CodeAttribute;
20import javassist.bytecode.LocalVariableAttribute;
21
22public class MethodParameterWriter {
23
24 private Translator m_translator;
25
26 public MethodParameterWriter(Translator translator) {
27 m_translator = translator;
28 }
29
30 public void writeMethodArguments(CtClass c) {
31
32 // Procyon will read method arguments from the "MethodParameters" attribute, so write those
33 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
34
35 // if there's a local variable table here, don't write a MethodParameters attribute
36 // let the local variable writer deal with it instead
37 // procyon starts doing really weird things if we give it both attributes
38 CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute();
39 if (codeAttribute != null && codeAttribute.getAttribute(LocalVariableAttribute.tag) != null) {
40 continue;
41 }
42
43 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
44
45 // get the number of arguments
46 Signature signature = behaviorEntry.getSignature();
47 if (signature == null) {
48 // static initializers have no signatures, or arguments
49 continue;
50 }
51 int numParams = signature.getArgumentTypes().size();
52 if (numParams <= 0) {
53 continue;
54 }
55
56 // get the list of argument names
57 List<String> names = new ArrayList<String>(numParams);
58 for (int i = 0; i < numParams; i++) {
59 names.add(m_translator.translate(new ArgumentEntry(behaviorEntry, i, "")));
60 }
61
62 // save the mappings to the class
63 MethodParametersAttribute.updateClass(behavior.getMethodInfo(), names);
64 }
65 }
66}
diff --git a/src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java b/src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java
new file mode 100644
index 00000000..ee7ed874
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java
@@ -0,0 +1,86 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.io.ByteArrayOutputStream;
14import java.io.DataOutputStream;
15import java.io.IOException;
16import java.util.ArrayList;
17import java.util.List;
18
19import javassist.bytecode.AttributeInfo;
20import javassist.bytecode.ConstPool;
21import javassist.bytecode.MethodInfo;
22
23public class MethodParametersAttribute extends AttributeInfo {
24
25 private MethodParametersAttribute(ConstPool pool, List<Integer> parameterNameIndices) {
26 super(pool, "MethodParameters", writeStruct(parameterNameIndices));
27 }
28
29 public static void updateClass(MethodInfo info, List<String> names) {
30
31 // add the names to the class const pool
32 ConstPool constPool = info.getConstPool();
33 List<Integer> parameterNameIndices = new ArrayList<Integer>();
34 for (String name : names) {
35 if (name != null) {
36 parameterNameIndices.add(constPool.addUtf8Info(name));
37 } else {
38 parameterNameIndices.add(0);
39 }
40 }
41
42 // add the attribute to the method
43 info.addAttribute(new MethodParametersAttribute(constPool, parameterNameIndices));
44 }
45
46 private static byte[] writeStruct(List<Integer> parameterNameIndices) {
47 // JVM 8 Spec says the struct looks like this:
48 // http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.24
49 // uint8 num_params
50 // for each param:
51 // uint16 name_index -> points to UTF8 entry in constant pool, or 0 for no entry
52 // uint16 access_flags -> don't care, just set to 0
53
54 ByteArrayOutputStream buf = new ByteArrayOutputStream();
55 DataOutputStream out = new DataOutputStream(buf);
56
57 // NOTE: java hates unsigned integers, so we have to be careful here
58 // the writeShort(), writeByte() methods will read 16,8 low-order bits from the int argument
59 // as long as the int argument is in range of the unsigned short/byte type, it will be written as an unsigned short/byte
60 // if the int is out of range, the byte stream won't look the way we want and weird things will happen
61 final int SIZEOF_UINT8 = 1;
62 final int SIZEOF_UINT16 = 2;
63 final int MAX_UINT8 = (1 << 8) - 1;
64 final int MAX_UINT16 = (1 << 16) - 1;
65
66 try {
67 assert (parameterNameIndices.size() >= 0 && parameterNameIndices.size() <= MAX_UINT8);
68 out.writeByte(parameterNameIndices.size());
69
70 for (Integer index : parameterNameIndices) {
71 assert (index >= 0 && index <= MAX_UINT16);
72 out.writeShort(index);
73
74 // just write 0 for the access flags
75 out.writeShort(0);
76 }
77
78 out.close();
79 byte[] data = buf.toByteArray();
80 assert (data.length == SIZEOF_UINT8 + parameterNameIndices.size() * (SIZEOF_UINT16 + SIZEOF_UINT16));
81 return data;
82 } catch (IOException ex) {
83 throw new Error(ex);
84 }
85 }
86}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
new file mode 100644
index 00000000..fd987f53
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class ClassInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_nameIndex;
19
20 static {
21 try {
22 m_class = Class.forName("javassist.bytecode.ClassInfo");
23 m_nameIndex = m_class.getDeclaredField("name");
24 m_nameIndex.setAccessible(true);
25 } catch (Exception ex) {
26 throw new Error(ex);
27 }
28 }
29
30 public static boolean isType(ConstInfoAccessor accessor) {
31 return m_class.isAssignableFrom(accessor.getItem().getClass());
32 }
33
34 private Object m_item;
35
36 public ClassInfoAccessor(Object item) {
37 m_item = item;
38 }
39
40 public int getNameIndex() {
41 try {
42 return (Integer) m_nameIndex.get(m_item);
43 } catch (Exception ex) {
44 throw new Error(ex);
45 }
46 }
47
48 public void setNameIndex(int val) {
49 try {
50 m_nameIndex.set(m_item, val);
51 } catch (Exception ex) {
52 throw new Error(ex);
53 }
54 }
55}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java
new file mode 100644
index 00000000..2692c069
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java
@@ -0,0 +1,151 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.io.*;
14import java.lang.reflect.Constructor;
15import java.lang.reflect.Field;
16import java.lang.reflect.Method;
17
18import cuchaz.enigma.bytecode.InfoType;
19
20public class ConstInfoAccessor {
21
22 private static Class<?> m_class;
23 private static Field m_index;
24 private static Method m_getTag;
25
26 static {
27 try {
28 m_class = Class.forName("javassist.bytecode.ConstInfo");
29 m_index = m_class.getDeclaredField("index");
30 m_index.setAccessible(true);
31 m_getTag = m_class.getMethod("getTag");
32 m_getTag.setAccessible(true);
33 } catch (Exception ex) {
34 throw new Error(ex);
35 }
36 }
37
38 private Object m_item;
39
40 public ConstInfoAccessor(Object item) {
41 if (item == null) {
42 throw new IllegalArgumentException("item cannot be null!");
43 }
44 m_item = item;
45 }
46
47 public ConstInfoAccessor(DataInputStream in) throws IOException {
48 try {
49 // read the entry
50 String className = in.readUTF();
51 int oldIndex = in.readInt();
52
53 // NOTE: ConstInfo instances write a type id (a "tag"), but they don't read it back
54 // so we have to read it here
55 in.readByte();
56
57 Constructor<?> constructor = Class.forName(className).getConstructor(DataInputStream.class, int.class);
58 constructor.setAccessible(true);
59 m_item = constructor.newInstance(in, oldIndex);
60 } catch (IOException ex) {
61 throw ex;
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public Object getItem() {
68 return m_item;
69 }
70
71 public int getIndex() {
72 try {
73 return (Integer) m_index.get(m_item);
74 } catch (Exception ex) {
75 throw new Error(ex);
76 }
77 }
78
79 public void setIndex(int val) {
80 try {
81 m_index.set(m_item, val);
82 } catch (Exception ex) {
83 throw new Error(ex);
84 }
85 }
86
87 public int getTag() {
88 try {
89 return (Integer) m_getTag.invoke(m_item);
90 } catch (Exception ex) {
91 throw new Error(ex);
92 }
93 }
94
95 public ConstInfoAccessor copy() {
96 return new ConstInfoAccessor(copyItem());
97 }
98
99 public Object copyItem() {
100 // I don't know of a simpler way to copy one of these silly things...
101 try {
102 // serialize the item
103 ByteArrayOutputStream buf = new ByteArrayOutputStream();
104 DataOutputStream out = new DataOutputStream(buf);
105 write(out);
106
107 // deserialize the item
108 DataInputStream in = new DataInputStream(new ByteArrayInputStream(buf.toByteArray()));
109 Object item = new ConstInfoAccessor(in).getItem();
110 in.close();
111
112 return item;
113 } catch (Exception ex) {
114 throw new Error(ex);
115 }
116 }
117
118 public void write(DataOutputStream out) throws IOException {
119 try {
120 out.writeUTF(m_item.getClass().getName());
121 out.writeInt(getIndex());
122
123 Method method = m_item.getClass().getMethod("write", DataOutputStream.class);
124 method.setAccessible(true);
125 method.invoke(m_item, out);
126 } catch (IOException ex) {
127 throw ex;
128 } catch (Exception ex) {
129 throw new Error(ex);
130 }
131 }
132
133 @Override
134 public String toString() {
135 try {
136 ByteArrayOutputStream buf = new ByteArrayOutputStream();
137 PrintWriter out = new PrintWriter(buf);
138 Method print = m_item.getClass().getMethod("print", PrintWriter.class);
139 print.setAccessible(true);
140 print.invoke(m_item, out);
141 out.close();
142 return buf.toString().replace("\n", "");
143 } catch (Exception ex) {
144 throw new Error(ex);
145 }
146 }
147
148 public InfoType getType() {
149 return InfoType.getByTag(getTag());
150 }
151}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java
new file mode 100644
index 00000000..0ca82b78
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class InvokeDynamicInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_bootstrapIndex;
19 private static Field m_nameAndTypeIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.InvokeDynamicInfo");
24 m_bootstrapIndex = m_class.getDeclaredField("bootstrap");
25 m_bootstrapIndex.setAccessible(true);
26 m_nameAndTypeIndex = m_class.getDeclaredField("nameAndType");
27 m_nameAndTypeIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public InvokeDynamicInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getBootstrapIndex() {
44 try {
45 return (Integer) m_bootstrapIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setBootstrapIndex(int val) {
52 try {
53 m_bootstrapIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getNameAndTypeIndex() {
60 try {
61 return (Integer) m_nameAndTypeIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setNameAndTypeIndex(int val) {
68 try {
69 m_nameAndTypeIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java
new file mode 100644
index 00000000..bb9d16bc
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class MemberRefInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_classIndex;
19 private static Field m_nameAndTypeIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.MemberrefInfo");
24 m_classIndex = m_class.getDeclaredField("classIndex");
25 m_classIndex.setAccessible(true);
26 m_nameAndTypeIndex = m_class.getDeclaredField("nameAndTypeIndex");
27 m_nameAndTypeIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public MemberRefInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getClassIndex() {
44 try {
45 return (Integer) m_classIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setClassIndex(int val) {
52 try {
53 m_classIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getNameAndTypeIndex() {
60 try {
61 return (Integer) m_nameAndTypeIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setNameAndTypeIndex(int val) {
68 try {
69 m_nameAndTypeIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java
new file mode 100644
index 00000000..88e42f46
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class MethodHandleInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_kindIndex;
19 private static Field m_indexIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.MethodHandleInfo");
24 m_kindIndex = m_class.getDeclaredField("refKind");
25 m_kindIndex.setAccessible(true);
26 m_indexIndex = m_class.getDeclaredField("refIndex");
27 m_indexIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public MethodHandleInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getTypeIndex() {
44 try {
45 return (Integer) m_kindIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setTypeIndex(int val) {
52 try {
53 m_kindIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getMethodRefIndex() {
60 try {
61 return (Integer) m_indexIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setMethodRefIndex(int val) {
68 try {
69 m_indexIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java
new file mode 100644
index 00000000..1d039f6c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class MethodTypeInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_descriptorIndex;
19
20 static {
21 try {
22 m_class = Class.forName("javassist.bytecode.MethodTypeInfo");
23 m_descriptorIndex = m_class.getDeclaredField("descriptor");
24 m_descriptorIndex.setAccessible(true);
25 } catch (Exception ex) {
26 throw new Error(ex);
27 }
28 }
29
30 public static boolean isType(ConstInfoAccessor accessor) {
31 return m_class.isAssignableFrom(accessor.getItem().getClass());
32 }
33
34 private Object m_item;
35
36 public MethodTypeInfoAccessor(Object item) {
37 m_item = item;
38 }
39
40 public int getTypeIndex() {
41 try {
42 return (Integer) m_descriptorIndex.get(m_item);
43 } catch (Exception ex) {
44 throw new Error(ex);
45 }
46 }
47
48 public void setTypeIndex(int val) {
49 try {
50 m_descriptorIndex.set(m_item, val);
51 } catch (Exception ex) {
52 throw new Error(ex);
53 }
54 }
55}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java
new file mode 100644
index 00000000..acba7799
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class NameAndTypeInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_nameIndex;
19 private static Field m_typeIndex;
20
21 static {
22 try {
23 m_class = Class.forName("javassist.bytecode.NameAndTypeInfo");
24 m_nameIndex = m_class.getDeclaredField("memberName");
25 m_nameIndex.setAccessible(true);
26 m_typeIndex = m_class.getDeclaredField("typeDescriptor");
27 m_typeIndex.setAccessible(true);
28 } catch (Exception ex) {
29 throw new Error(ex);
30 }
31 }
32
33 public static boolean isType(ConstInfoAccessor accessor) {
34 return m_class.isAssignableFrom(accessor.getItem().getClass());
35 }
36
37 private Object m_item;
38
39 public NameAndTypeInfoAccessor(Object item) {
40 m_item = item;
41 }
42
43 public int getNameIndex() {
44 try {
45 return (Integer) m_nameIndex.get(m_item);
46 } catch (Exception ex) {
47 throw new Error(ex);
48 }
49 }
50
51 public void setNameIndex(int val) {
52 try {
53 m_nameIndex.set(m_item, val);
54 } catch (Exception ex) {
55 throw new Error(ex);
56 }
57 }
58
59 public int getTypeIndex() {
60 try {
61 return (Integer) m_typeIndex.get(m_item);
62 } catch (Exception ex) {
63 throw new Error(ex);
64 }
65 }
66
67 public void setTypeIndex(int val) {
68 try {
69 m_typeIndex.set(m_item, val);
70 } catch (Exception ex) {
71 throw new Error(ex);
72 }
73 }
74}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java
new file mode 100644
index 00000000..b40e0eb2
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.lang.reflect.Field;
14
15public class StringInfoAccessor {
16
17 private static Class<?> m_class;
18 private static Field m_stringIndex;
19
20 static {
21 try {
22 m_class = Class.forName("javassist.bytecode.StringInfo");
23 m_stringIndex = m_class.getDeclaredField("string");
24 m_stringIndex.setAccessible(true);
25 } catch (Exception ex) {
26 throw new Error(ex);
27 }
28 }
29
30 public static boolean isType(ConstInfoAccessor accessor) {
31 return m_class.isAssignableFrom(accessor.getItem().getClass());
32 }
33
34 private Object m_item;
35
36 public StringInfoAccessor(Object item) {
37 m_item = item;
38 }
39
40 public int getStringIndex() {
41 try {
42 return (Integer) m_stringIndex.get(m_item);
43 } catch (Exception ex) {
44 throw new Error(ex);
45 }
46 }
47
48 public void setStringIndex(int val) {
49 try {
50 m_stringIndex.set(m_item, val);
51 } catch (Exception ex) {
52 throw new Error(ex);
53 }
54 }
55}
diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java
new file mode 100644
index 00000000..9303b41a
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java
@@ -0,0 +1,28 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13public class Utf8InfoAccessor {
14
15 private static Class<?> m_class;
16
17 static {
18 try {
19 m_class = Class.forName("javassist.bytecode.Utf8Info");
20 } catch (Exception ex) {
21 throw new Error(ex);
22 }
23 }
24
25 public static boolean isType(ConstInfoAccessor accessor) {
26 return m_class.isAssignableFrom(accessor.getItem().getClass());
27 }
28}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassForest.java b/src/main/java/cuchaz/enigma/convert/ClassForest.java
new file mode 100644
index 00000000..7123bbfb
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassForest.java
@@ -0,0 +1,60 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.HashMultimap;
14import com.google.common.collect.Multimap;
15
16import java.util.Collection;
17
18import cuchaz.enigma.mapping.ClassEntry;
19
20
21public class ClassForest {
22
23 private ClassIdentifier m_identifier;
24 private Multimap<ClassIdentity, ClassEntry> m_forest;
25
26 public ClassForest(ClassIdentifier identifier) {
27 m_identifier = identifier;
28 m_forest = HashMultimap.create();
29 }
30
31 public void addAll(Iterable<ClassEntry> entries) {
32 for (ClassEntry entry : entries) {
33 add(entry);
34 }
35 }
36
37 public void add(ClassEntry entry) {
38 try {
39 m_forest.put(m_identifier.identify(entry), entry);
40 } catch (ClassNotFoundException ex) {
41 throw new Error("Unable to find class " + entry.getName());
42 }
43 }
44
45 public Collection<ClassIdentity> identities() {
46 return m_forest.keySet();
47 }
48
49 public Collection<ClassEntry> classes() {
50 return m_forest.values();
51 }
52
53 public Collection<ClassEntry> getClasses(ClassIdentity identity) {
54 return m_forest.get(identity);
55 }
56
57 public boolean containsIdentity(ClassIdentity identity) {
58 return m_forest.containsKey(identity);
59 }
60}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java b/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java
new file mode 100644
index 00000000..e1153a6d
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java
@@ -0,0 +1,54 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.Maps;
14
15import java.util.Map;
16import java.util.jar.JarFile;
17
18import cuchaz.enigma.TranslatingTypeLoader;
19import cuchaz.enigma.analysis.JarIndex;
20import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
21import cuchaz.enigma.mapping.ClassEntry;
22import javassist.CtClass;
23
24
25public class ClassIdentifier {
26
27 private JarIndex m_index;
28 private SidedClassNamer m_namer;
29 private boolean m_useReferences;
30 private TranslatingTypeLoader m_loader;
31 private Map<ClassEntry, ClassIdentity> m_cache;
32
33 public ClassIdentifier(JarFile jar, JarIndex index, SidedClassNamer namer, boolean useReferences) {
34 m_index = index;
35 m_namer = namer;
36 m_useReferences = useReferences;
37 m_loader = new TranslatingTypeLoader(jar, index);
38 m_cache = Maps.newHashMap();
39 }
40
41 public ClassIdentity identify(ClassEntry classEntry)
42 throws ClassNotFoundException {
43 ClassIdentity identity = m_cache.get(classEntry);
44 if (identity == null) {
45 CtClass c = m_loader.loadClass(classEntry.getName());
46 if (c == null) {
47 throw new ClassNotFoundException(classEntry.getName());
48 }
49 identity = new ClassIdentity(c, m_namer, m_index, m_useReferences);
50 m_cache.put(classEntry, identity);
51 }
52 return identity;
53 }
54}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentity.java b/src/main/java/cuchaz/enigma/convert/ClassIdentity.java
new file mode 100644
index 00000000..76c48ab6
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassIdentity.java
@@ -0,0 +1,444 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.*;
14
15import java.io.UnsupportedEncodingException;
16import java.security.MessageDigest;
17import java.security.NoSuchAlgorithmException;
18import java.util.Enumeration;
19import java.util.List;
20import java.util.Map;
21import java.util.Set;
22
23import cuchaz.enigma.Constants;
24import cuchaz.enigma.Util;
25import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
26import cuchaz.enigma.analysis.EntryReference;
27import cuchaz.enigma.analysis.JarIndex;
28import cuchaz.enigma.bytecode.ConstPoolEditor;
29import cuchaz.enigma.bytecode.InfoType;
30import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
31import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
32import cuchaz.enigma.mapping.*;
33import javassist.*;
34import javassist.bytecode.*;
35import javassist.expr.*;
36
37public class ClassIdentity {
38
39 private ClassEntry m_classEntry;
40 private SidedClassNamer m_namer;
41 private Multiset<String> m_fields;
42 private Multiset<String> m_methods;
43 private Multiset<String> m_constructors;
44 private String m_staticInitializer;
45 private String m_extends;
46 private Multiset<String> m_implements;
47 private Set<String> m_stringLiterals;
48 private Multiset<String> m_implementations;
49 private Multiset<String> m_references;
50 private String m_outer;
51
52 private final ClassNameReplacer m_classNameReplacer = new ClassNameReplacer() {
53
54 private Map<String, String> m_classNames = Maps.newHashMap();
55
56 @Override
57 public String replace(String className) {
58
59 // classes not in the none package can be passed through
60 ClassEntry classEntry = new ClassEntry(className);
61 if (!classEntry.getPackageName().equals(Constants.NonePackage)) {
62 return className;
63 }
64
65 // is this class ourself?
66 if (className.equals(m_classEntry.getName())) {
67 return "CSelf";
68 }
69
70 // try the namer
71 if (m_namer != null) {
72 String newName = m_namer.getName(className);
73 if (newName != null) {
74 return newName;
75 }
76 }
77
78 // otherwise, use local naming
79 if (!m_classNames.containsKey(className)) {
80 m_classNames.put(className, getNewClassName());
81 }
82 return m_classNames.get(className);
83 }
84
85 private String getNewClassName() {
86 return String.format("C%03d", m_classNames.size());
87 }
88 };
89
90 public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) {
91 m_namer = namer;
92
93 // stuff from the bytecode
94
95 m_classEntry = EntryFactory.getClassEntry(c);
96 m_fields = HashMultiset.create();
97 for (CtField field : c.getDeclaredFields()) {
98 m_fields.add(scrubType(field.getSignature()));
99 }
100 m_methods = HashMultiset.create();
101 for (CtMethod method : c.getDeclaredMethods()) {
102 m_methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method));
103 }
104 m_constructors = HashMultiset.create();
105 for (CtConstructor constructor : c.getDeclaredConstructors()) {
106 m_constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor));
107 }
108 m_staticInitializer = "";
109 if (c.getClassInitializer() != null) {
110 m_staticInitializer = getBehaviorSignature(c.getClassInitializer());
111 }
112 m_extends = "";
113 if (c.getClassFile().getSuperclass() != null) {
114 m_extends = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
115 }
116 m_implements = HashMultiset.create();
117 for (String interfaceName : c.getClassFile().getInterfaces()) {
118 m_implements.add(scrubClassName(Descriptor.toJvmName(interfaceName)));
119 }
120
121 m_stringLiterals = Sets.newHashSet();
122 ConstPool constants = c.getClassFile().getConstPool();
123 for (int i = 1; i < constants.getSize(); i++) {
124 if (constants.getTag(i) == ConstPool.CONST_String) {
125 m_stringLiterals.add(constants.getStringInfo(i));
126 }
127 }
128
129 // stuff from the jar index
130
131 m_implementations = HashMultiset.create();
132 ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, m_classEntry);
133 if (implementationsNode != null) {
134 @SuppressWarnings("unchecked")
135 Enumeration<ClassImplementationsTreeNode> implementations = implementationsNode.children();
136 while (implementations.hasMoreElements()) {
137 ClassImplementationsTreeNode node = implementations.nextElement();
138 m_implementations.add(scrubClassName(node.getClassEntry().getName()));
139 }
140 }
141
142 m_references = HashMultiset.create();
143 if (useReferences) {
144 for (CtField field : c.getDeclaredFields()) {
145 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
146 index.getFieldReferences(fieldEntry).forEach(this::addReference);
147 }
148 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
149 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
150 index.getBehaviorReferences(behaviorEntry).forEach(this::addReference);
151 }
152 }
153
154 m_outer = null;
155 if (m_classEntry.isInnerClass()) {
156 m_outer = m_classEntry.getOuterClassName();
157 }
158 }
159
160 private void addReference(EntryReference<? extends Entry, BehaviorEntry> reference) {
161 if (reference.context.getSignature() != null) {
162 m_references.add(String.format("%s_%s",
163 scrubClassName(reference.context.getClassName()),
164 scrubSignature(reference.context.getSignature())
165 ));
166 } else {
167 m_references.add(String.format("%s_<clinit>",
168 scrubClassName(reference.context.getClassName())
169 ));
170 }
171 }
172
173 public ClassEntry getClassEntry() {
174 return m_classEntry;
175 }
176
177 @Override
178 public String toString() {
179 StringBuilder buf = new StringBuilder();
180 buf.append("class: ");
181 buf.append(m_classEntry.getName());
182 buf.append(" ");
183 buf.append(hashCode());
184 buf.append("\n");
185 for (String field : m_fields) {
186 buf.append("\tfield ");
187 buf.append(field);
188 buf.append("\n");
189 }
190 for (String method : m_methods) {
191 buf.append("\tmethod ");
192 buf.append(method);
193 buf.append("\n");
194 }
195 for (String constructor : m_constructors) {
196 buf.append("\tconstructor ");
197 buf.append(constructor);
198 buf.append("\n");
199 }
200 if (m_staticInitializer.length() > 0) {
201 buf.append("\tinitializer ");
202 buf.append(m_staticInitializer);
203 buf.append("\n");
204 }
205 if (m_extends.length() > 0) {
206 buf.append("\textends ");
207 buf.append(m_extends);
208 buf.append("\n");
209 }
210 for (String interfaceName : m_implements) {
211 buf.append("\timplements ");
212 buf.append(interfaceName);
213 buf.append("\n");
214 }
215 for (String implementation : m_implementations) {
216 buf.append("\timplemented by ");
217 buf.append(implementation);
218 buf.append("\n");
219 }
220 for (String reference : m_references) {
221 buf.append("\treference ");
222 buf.append(reference);
223 buf.append("\n");
224 }
225 buf.append("\touter ");
226 buf.append(m_outer);
227 buf.append("\n");
228 return buf.toString();
229 }
230
231 private String scrubClassName(String className) {
232 return m_classNameReplacer.replace(className);
233 }
234
235 private String scrubType(String typeName) {
236 return scrubType(new Type(typeName)).toString();
237 }
238
239 private Type scrubType(Type type) {
240 if (type.hasClass()) {
241 return new Type(type, m_classNameReplacer);
242 } else {
243 return type;
244 }
245 }
246
247 private String scrubSignature(String signature) {
248 return scrubSignature(new Signature(signature)).toString();
249 }
250
251 private Signature scrubSignature(Signature signature) {
252 return new Signature(signature, m_classNameReplacer);
253 }
254
255 private boolean isClassMatchedUniquely(String className) {
256 return m_namer != null && m_namer.getName(Descriptor.toJvmName(className)) != null;
257 }
258
259 private String getBehaviorSignature(CtBehavior behavior) {
260 try {
261 // does this method have an implementation?
262 if (behavior.getMethodInfo().getCodeAttribute() == null) {
263 return "(none)";
264 }
265
266 // compute the hash from the opcodes
267 ConstPool constants = behavior.getMethodInfo().getConstPool();
268 final MessageDigest digest = MessageDigest.getInstance("MD5");
269 CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator();
270 while (iter.hasNext()) {
271 int pos = iter.next();
272
273 // update the hash with the opcode
274 int opcode = iter.byteAt(pos);
275 digest.update((byte) opcode);
276
277 switch (opcode) {
278 case Opcode.LDC: {
279 int constIndex = iter.byteAt(pos + 1);
280 updateHashWithConstant(digest, constants, constIndex);
281 }
282 break;
283
284 case Opcode.LDC_W:
285 case Opcode.LDC2_W: {
286 int constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2);
287 updateHashWithConstant(digest, constants, constIndex);
288 }
289 break;
290 }
291 }
292
293 // update hash with method and field accesses
294 behavior.instrument(new ExprEditor() {
295 @Override
296 public void edit(MethodCall call) {
297 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
298 updateHashWithString(digest, scrubSignature(call.getSignature()));
299 if (isClassMatchedUniquely(call.getClassName())) {
300 updateHashWithString(digest, call.getMethodName());
301 }
302 }
303
304 @Override
305 public void edit(FieldAccess access) {
306 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName())));
307 updateHashWithString(digest, scrubType(access.getSignature()));
308 if (isClassMatchedUniquely(access.getClassName())) {
309 updateHashWithString(digest, access.getFieldName());
310 }
311 }
312
313 @Override
314 public void edit(ConstructorCall call) {
315 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
316 updateHashWithString(digest, scrubSignature(call.getSignature()));
317 }
318
319 @Override
320 public void edit(NewExpr expr) {
321 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName())));
322 }
323 });
324
325 // convert the hash to a hex string
326 return toHex(digest.digest());
327 } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) {
328 throw new Error(ex);
329 }
330 }
331
332 private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) {
333 ConstPoolEditor editor = new ConstPoolEditor(constants);
334 ConstInfoAccessor item = editor.getItem(index);
335 if (item.getType() == InfoType.StringInfo) {
336 updateHashWithString(digest, constants.getStringInfo(index));
337 }
338 // TODO: other constants
339 }
340
341 private void updateHashWithString(MessageDigest digest, String val) {
342 try {
343 digest.update(val.getBytes("UTF8"));
344 } catch (UnsupportedEncodingException ex) {
345 throw new Error(ex);
346 }
347 }
348
349 private String toHex(byte[] bytes) {
350 // function taken from:
351 // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java
352 final char[] hexArray = "0123456789ABCDEF".toCharArray();
353 char[] hexChars = new char[bytes.length * 2];
354 for (int j = 0; j < bytes.length; j++) {
355 int v = bytes[j] & 0xFF;
356 hexChars[j * 2] = hexArray[v >>> 4];
357 hexChars[j * 2 + 1] = hexArray[v & 0x0F];
358 }
359 return new String(hexChars);
360 }
361
362 @Override
363 public boolean equals(Object other) {
364 if (other instanceof ClassIdentity) {
365 return equals((ClassIdentity) other);
366 }
367 return false;
368 }
369
370 public boolean equals(ClassIdentity other) {
371 return m_fields.equals(other.m_fields)
372 && m_methods.equals(other.m_methods)
373 && m_constructors.equals(other.m_constructors)
374 && m_staticInitializer.equals(other.m_staticInitializer)
375 && m_extends.equals(other.m_extends)
376 && m_implements.equals(other.m_implements)
377 && m_implementations.equals(other.m_implementations)
378 && m_references.equals(other.m_references);
379 }
380
381 @Override
382 public int hashCode() {
383 List<Object> objs = Lists.newArrayList();
384 objs.addAll(m_fields);
385 objs.addAll(m_methods);
386 objs.addAll(m_constructors);
387 objs.add(m_staticInitializer);
388 objs.add(m_extends);
389 objs.addAll(m_implements);
390 objs.addAll(m_implementations);
391 objs.addAll(m_references);
392 return Util.combineHashesOrdered(objs);
393 }
394
395 public int getMatchScore(ClassIdentity other) {
396 return 2 * getNumMatches(m_extends, other.m_extends)
397 + 2 * getNumMatches(m_outer, other.m_outer)
398 + 2 * getNumMatches(m_implements, other.m_implements)
399 + getNumMatches(m_stringLiterals, other.m_stringLiterals)
400 + getNumMatches(m_fields, other.m_fields)
401 + getNumMatches(m_methods, other.m_methods)
402 + getNumMatches(m_constructors, other.m_constructors);
403 }
404
405 public int getMaxMatchScore() {
406 return 2 + 2 + 2 * m_implements.size() + m_stringLiterals.size() + m_fields.size() + m_methods.size() + m_constructors.size();
407 }
408
409 public boolean matches(CtClass c) {
410 // just compare declaration counts
411 return m_fields.size() == c.getDeclaredFields().length
412 && m_methods.size() == c.getDeclaredMethods().length
413 && m_constructors.size() == c.getDeclaredConstructors().length;
414 }
415
416 private int getNumMatches(Set<String> a, Set<String> b) {
417 int numMatches = 0;
418 for (String val : a) {
419 if (b.contains(val)) {
420 numMatches++;
421 }
422 }
423 return numMatches;
424 }
425
426 private int getNumMatches(Multiset<String> a, Multiset<String> b) {
427 int numMatches = 0;
428 for (String val : a) {
429 if (b.contains(val)) {
430 numMatches++;
431 }
432 }
433 return numMatches;
434 }
435
436 private int getNumMatches(String a, String b) {
437 if (a == null && b == null) {
438 return 1;
439 } else if (a != null && b != null && a.equals(b)) {
440 return 1;
441 }
442 return 0;
443 }
444}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatch.java b/src/main/java/cuchaz/enigma/convert/ClassMatch.java
new file mode 100644
index 00000000..f3530ed4
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassMatch.java
@@ -0,0 +1,88 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.Sets;
14
15import java.util.Collection;
16import java.util.Set;
17
18import cuchaz.enigma.Util;
19import cuchaz.enigma.mapping.ClassEntry;
20
21
22public class ClassMatch {
23
24 public Set<ClassEntry> sourceClasses;
25 public Set<ClassEntry> destClasses;
26
27 public ClassMatch(Collection<ClassEntry> sourceClasses, Collection<ClassEntry> destClasses) {
28 this.sourceClasses = Sets.newHashSet(sourceClasses);
29 this.destClasses = Sets.newHashSet(destClasses);
30 }
31
32 public ClassMatch(ClassEntry sourceClass, ClassEntry destClass) {
33 sourceClasses = Sets.newHashSet();
34 if (sourceClass != null) {
35 sourceClasses.add(sourceClass);
36 }
37 destClasses = Sets.newHashSet();
38 if (destClass != null) {
39 destClasses.add(destClass);
40 }
41 }
42
43 public boolean isMatched() {
44 return sourceClasses.size() > 0 && destClasses.size() > 0;
45 }
46
47 public boolean isAmbiguous() {
48 return sourceClasses.size() > 1 || destClasses.size() > 1;
49 }
50
51 public ClassEntry getUniqueSource() {
52 if (sourceClasses.size() != 1) {
53 throw new IllegalStateException("Match has ambiguous source!");
54 }
55 return sourceClasses.iterator().next();
56 }
57
58 public ClassEntry getUniqueDest() {
59 if (destClasses.size() != 1) {
60 throw new IllegalStateException("Match has ambiguous source!");
61 }
62 return destClasses.iterator().next();
63 }
64
65 public Set<ClassEntry> intersectSourceClasses(Set<ClassEntry> classes) {
66 Set<ClassEntry> intersection = Sets.newHashSet(sourceClasses);
67 intersection.retainAll(classes);
68 return intersection;
69 }
70
71 @Override
72 public int hashCode() {
73 return Util.combineHashesOrdered(sourceClasses, destClasses);
74 }
75
76 @Override
77 public boolean equals(Object other) {
78 if (other instanceof ClassMatch) {
79 return equals((ClassMatch) other);
80 }
81 return false;
82 }
83
84 public boolean equals(ClassMatch other) {
85 return this.sourceClasses.equals(other.sourceClasses)
86 && this.destClasses.equals(other.destClasses);
87 }
88}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatches.java b/src/main/java/cuchaz/enigma/convert/ClassMatches.java
new file mode 100644
index 00000000..2c5f6a57
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassMatches.java
@@ -0,0 +1,159 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.BiMap;
14import com.google.common.collect.HashBiMap;
15import com.google.common.collect.Maps;
16import com.google.common.collect.Sets;
17
18import java.util.*;
19
20import cuchaz.enigma.mapping.ClassEntry;
21
22
23public class ClassMatches implements Iterable<ClassMatch> {
24
25 Collection<ClassMatch> m_matches;
26 Map<ClassEntry, ClassMatch> m_matchesBySource;
27 Map<ClassEntry, ClassMatch> m_matchesByDest;
28 BiMap<ClassEntry, ClassEntry> m_uniqueMatches;
29 Map<ClassEntry, ClassMatch> m_ambiguousMatchesBySource;
30 Map<ClassEntry, ClassMatch> m_ambiguousMatchesByDest;
31 Set<ClassEntry> m_unmatchedSourceClasses;
32 Set<ClassEntry> m_unmatchedDestClasses;
33
34 public ClassMatches() {
35 this(new ArrayList<ClassMatch>());
36 }
37
38 public ClassMatches(Collection<ClassMatch> matches) {
39 m_matches = matches;
40 m_matchesBySource = Maps.newHashMap();
41 m_matchesByDest = Maps.newHashMap();
42 m_uniqueMatches = HashBiMap.create();
43 m_ambiguousMatchesBySource = Maps.newHashMap();
44 m_ambiguousMatchesByDest = Maps.newHashMap();
45 m_unmatchedSourceClasses = Sets.newHashSet();
46 m_unmatchedDestClasses = Sets.newHashSet();
47
48 for (ClassMatch match : matches) {
49 indexMatch(match);
50 }
51 }
52
53 public void add(ClassMatch match) {
54 m_matches.add(match);
55 indexMatch(match);
56 }
57
58 public void remove(ClassMatch match) {
59 for (ClassEntry sourceClass : match.sourceClasses) {
60 m_matchesBySource.remove(sourceClass);
61 m_uniqueMatches.remove(sourceClass);
62 m_ambiguousMatchesBySource.remove(sourceClass);
63 m_unmatchedSourceClasses.remove(sourceClass);
64 }
65 for (ClassEntry destClass : match.destClasses) {
66 m_matchesByDest.remove(destClass);
67 m_uniqueMatches.inverse().remove(destClass);
68 m_ambiguousMatchesByDest.remove(destClass);
69 m_unmatchedDestClasses.remove(destClass);
70 }
71 m_matches.remove(match);
72 }
73
74 public int size() {
75 return m_matches.size();
76 }
77
78 @Override
79 public Iterator<ClassMatch> iterator() {
80 return m_matches.iterator();
81 }
82
83 private void indexMatch(ClassMatch match) {
84 if (!match.isMatched()) {
85 // unmatched
86 m_unmatchedSourceClasses.addAll(match.sourceClasses);
87 m_unmatchedDestClasses.addAll(match.destClasses);
88 } else {
89 if (match.isAmbiguous()) {
90 // ambiguously matched
91 for (ClassEntry entry : match.sourceClasses) {
92 m_ambiguousMatchesBySource.put(entry, match);
93 }
94 for (ClassEntry entry : match.destClasses) {
95 m_ambiguousMatchesByDest.put(entry, match);
96 }
97 } else {
98 // uniquely matched
99 m_uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest());
100 }
101 }
102 for (ClassEntry entry : match.sourceClasses) {
103 m_matchesBySource.put(entry, match);
104 }
105 for (ClassEntry entry : match.destClasses) {
106 m_matchesByDest.put(entry, match);
107 }
108 }
109
110 public BiMap<ClassEntry, ClassEntry> getUniqueMatches() {
111 return m_uniqueMatches;
112 }
113
114 public Set<ClassEntry> getUnmatchedSourceClasses() {
115 return m_unmatchedSourceClasses;
116 }
117
118 public Set<ClassEntry> getUnmatchedDestClasses() {
119 return m_unmatchedDestClasses;
120 }
121
122 public Set<ClassEntry> getAmbiguouslyMatchedSourceClasses() {
123 return m_ambiguousMatchesBySource.keySet();
124 }
125
126 public ClassMatch getAmbiguousMatchBySource(ClassEntry sourceClass) {
127 return m_ambiguousMatchesBySource.get(sourceClass);
128 }
129
130 public ClassMatch getMatchBySource(ClassEntry sourceClass) {
131 return m_matchesBySource.get(sourceClass);
132 }
133
134 public ClassMatch getMatchByDest(ClassEntry destClass) {
135 return m_matchesByDest.get(destClass);
136 }
137
138 public void removeSource(ClassEntry sourceClass) {
139 ClassMatch match = m_matchesBySource.get(sourceClass);
140 if (match != null) {
141 remove(match);
142 match.sourceClasses.remove(sourceClass);
143 if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) {
144 add(match);
145 }
146 }
147 }
148
149 public void removeDest(ClassEntry destClass) {
150 ClassMatch match = m_matchesByDest.get(destClass);
151 if (match != null) {
152 remove(match);
153 match.destClasses.remove(destClass);
154 if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) {
155 add(match);
156 }
157 }
158 }
159}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatching.java b/src/main/java/cuchaz/enigma/convert/ClassMatching.java
new file mode 100644
index 00000000..14f8e2a3
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassMatching.java
@@ -0,0 +1,155 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.BiMap;
14import com.google.common.collect.HashBiMap;
15import com.google.common.collect.Lists;
16import com.google.common.collect.Sets;
17
18import java.util.ArrayList;
19import java.util.Collection;
20import java.util.List;
21import java.util.Map.Entry;
22import java.util.Set;
23
24import cuchaz.enigma.mapping.ClassEntry;
25
26public class ClassMatching {
27
28 private ClassForest m_sourceClasses;
29 private ClassForest m_destClasses;
30 private BiMap<ClassEntry, ClassEntry> m_knownMatches;
31
32 public ClassMatching(ClassIdentifier sourceIdentifier, ClassIdentifier destIdentifier) {
33 m_sourceClasses = new ClassForest(sourceIdentifier);
34 m_destClasses = new ClassForest(destIdentifier);
35 m_knownMatches = HashBiMap.create();
36 }
37
38 public void addKnownMatches(BiMap<ClassEntry, ClassEntry> knownMatches) {
39 m_knownMatches.putAll(knownMatches);
40 }
41
42 public void match(Iterable<ClassEntry> sourceClasses, Iterable<ClassEntry> destClasses) {
43 for (ClassEntry sourceClass : sourceClasses) {
44 if (!m_knownMatches.containsKey(sourceClass)) {
45 m_sourceClasses.add(sourceClass);
46 }
47 }
48 for (ClassEntry destClass : destClasses) {
49 if (!m_knownMatches.containsValue(destClass)) {
50 m_destClasses.add(destClass);
51 }
52 }
53 }
54
55 public Collection<ClassMatch> matches() {
56 List<ClassMatch> matches = Lists.newArrayList();
57 for (Entry<ClassEntry, ClassEntry> entry : m_knownMatches.entrySet()) {
58 matches.add(new ClassMatch(
59 entry.getKey(),
60 entry.getValue()
61 ));
62 }
63 for (ClassIdentity identity : m_sourceClasses.identities()) {
64 matches.add(new ClassMatch(
65 m_sourceClasses.getClasses(identity),
66 m_destClasses.getClasses(identity)
67 ));
68 }
69 for (ClassIdentity identity : m_destClasses.identities()) {
70 if (!m_sourceClasses.containsIdentity(identity)) {
71 matches.add(new ClassMatch(
72 new ArrayList<ClassEntry>(),
73 m_destClasses.getClasses(identity)
74 ));
75 }
76 }
77 return matches;
78 }
79
80 public Collection<ClassEntry> sourceClasses() {
81 Set<ClassEntry> classes = Sets.newHashSet();
82 for (ClassMatch match : matches()) {
83 classes.addAll(match.sourceClasses);
84 }
85 return classes;
86 }
87
88 public Collection<ClassEntry> destClasses() {
89 Set<ClassEntry> classes = Sets.newHashSet();
90 for (ClassMatch match : matches()) {
91 classes.addAll(match.destClasses);
92 }
93 return classes;
94 }
95
96 public BiMap<ClassEntry, ClassEntry> uniqueMatches() {
97 BiMap<ClassEntry, ClassEntry> uniqueMatches = HashBiMap.create();
98 for (ClassMatch match : matches()) {
99 if (match.isMatched() && !match.isAmbiguous()) {
100 uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest());
101 }
102 }
103 return uniqueMatches;
104 }
105
106 public Collection<ClassMatch> ambiguousMatches() {
107 List<ClassMatch> ambiguousMatches = Lists.newArrayList();
108 for (ClassMatch match : matches()) {
109 if (match.isMatched() && match.isAmbiguous()) {
110 ambiguousMatches.add(match);
111 }
112 }
113 return ambiguousMatches;
114 }
115
116 public Collection<ClassEntry> unmatchedSourceClasses() {
117 List<ClassEntry> classes = Lists.newArrayList();
118 for (ClassMatch match : matches()) {
119 if (!match.isMatched() && !match.sourceClasses.isEmpty()) {
120 classes.addAll(match.sourceClasses);
121 }
122 }
123 return classes;
124 }
125
126 public Collection<ClassEntry> unmatchedDestClasses() {
127 List<ClassEntry> classes = Lists.newArrayList();
128 for (ClassMatch match : matches()) {
129 if (!match.isMatched() && !match.destClasses.isEmpty()) {
130 classes.addAll(match.destClasses);
131 }
132 }
133 return classes;
134 }
135
136 @Override
137 public String toString() {
138
139 // count the ambiguous classes
140 int numAmbiguousSource = 0;
141 int numAmbiguousDest = 0;
142 for (ClassMatch match : ambiguousMatches()) {
143 numAmbiguousSource += match.sourceClasses.size();
144 numAmbiguousDest += match.destClasses.size();
145 }
146
147 StringBuilder buf = new StringBuilder();
148 buf.append(String.format("%20s%8s%8s\n", "", "Source", "Dest"));
149 buf.append(String.format("%20s%8d%8d\n", "Classes", sourceClasses().size(), destClasses().size()));
150 buf.append(String.format("%20s%8d%8d\n", "Uniquely matched", uniqueMatches().size(), uniqueMatches().size()));
151 buf.append(String.format("%20s%8d%8d\n", "Ambiguously matched", numAmbiguousSource, numAmbiguousDest));
152 buf.append(String.format("%20s%8d%8d\n", "Unmatched", unmatchedSourceClasses().size(), unmatchedDestClasses().size()));
153 return buf.toString();
154 }
155}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassNamer.java b/src/main/java/cuchaz/enigma/convert/ClassNamer.java
new file mode 100644
index 00000000..f1d98200
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/ClassNamer.java
@@ -0,0 +1,56 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.BiMap;
14import com.google.common.collect.Maps;
15
16import java.util.Map;
17
18import cuchaz.enigma.mapping.ClassEntry;
19
20public class ClassNamer {
21
22 public interface SidedClassNamer {
23 String getName(String name);
24 }
25
26 private Map<String, String> m_sourceNames;
27 private Map<String, String> m_destNames;
28
29 public ClassNamer(BiMap<ClassEntry, ClassEntry> mappings) {
30 // convert the identity mappings to name maps
31 m_sourceNames = Maps.newHashMap();
32 m_destNames = Maps.newHashMap();
33 int i = 0;
34 for (Map.Entry<ClassEntry, ClassEntry> entry : mappings.entrySet()) {
35 String name = String.format("M%04d", i++);
36 m_sourceNames.put(entry.getKey().getName(), name);
37 m_destNames.put(entry.getValue().getName(), name);
38 }
39 }
40
41 public String getSourceName(String name) {
42 return m_sourceNames.get(name);
43 }
44
45 public String getDestName(String name) {
46 return m_destNames.get(name);
47 }
48
49 public SidedClassNamer getSourceNamer() {
50 return this::getSourceName;
51 }
52
53 public SidedClassNamer getDestNamer() {
54 return this::getDestName;
55 }
56}
diff --git a/src/main/java/cuchaz/enigma/convert/FieldMatches.java b/src/main/java/cuchaz/enigma/convert/FieldMatches.java
new file mode 100644
index 00000000..0899cd2e
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/FieldMatches.java
@@ -0,0 +1,151 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.*;
14
15import java.util.Collection;
16import java.util.Set;
17
18import cuchaz.enigma.mapping.ClassEntry;
19import cuchaz.enigma.mapping.FieldEntry;
20
21
22public class FieldMatches {
23
24 private BiMap<FieldEntry, FieldEntry> m_matches;
25 private Multimap<ClassEntry, FieldEntry> m_matchedSourceFields;
26 private Multimap<ClassEntry, FieldEntry> m_unmatchedSourceFields;
27 private Multimap<ClassEntry, FieldEntry> m_unmatchedDestFields;
28 private Multimap<ClassEntry, FieldEntry> m_unmatchableSourceFields;
29
30 public FieldMatches() {
31 m_matches = HashBiMap.create();
32 m_matchedSourceFields = HashMultimap.create();
33 m_unmatchedSourceFields = HashMultimap.create();
34 m_unmatchedDestFields = HashMultimap.create();
35 m_unmatchableSourceFields = HashMultimap.create();
36 }
37
38 public void addMatch(FieldEntry srcField, FieldEntry destField) {
39 boolean wasAdded = m_matches.put(srcField, destField) == null;
40 assert (wasAdded);
41 wasAdded = m_matchedSourceFields.put(srcField.getClassEntry(), srcField);
42 assert (wasAdded);
43 }
44
45 public void addUnmatchedSourceField(FieldEntry fieldEntry) {
46 boolean wasAdded = m_unmatchedSourceFields.put(fieldEntry.getClassEntry(), fieldEntry);
47 assert (wasAdded);
48 }
49
50 public void addUnmatchedSourceFields(Iterable<FieldEntry> fieldEntries) {
51 for (FieldEntry fieldEntry : fieldEntries) {
52 addUnmatchedSourceField(fieldEntry);
53 }
54 }
55
56 public void addUnmatchedDestField(FieldEntry fieldEntry) {
57 boolean wasAdded = m_unmatchedDestFields.put(fieldEntry.getClassEntry(), fieldEntry);
58 assert (wasAdded);
59 }
60
61 public void addUnmatchedDestFields(Iterable<FieldEntry> fieldEntries) {
62 for (FieldEntry fieldEntry : fieldEntries) {
63 addUnmatchedDestField(fieldEntry);
64 }
65 }
66
67 public void addUnmatchableSourceField(FieldEntry sourceField) {
68 boolean wasAdded = m_unmatchableSourceFields.put(sourceField.getClassEntry(), sourceField);
69 assert (wasAdded);
70 }
71
72 public Set<ClassEntry> getSourceClassesWithUnmatchedFields() {
73 return m_unmatchedSourceFields.keySet();
74 }
75
76 public Collection<ClassEntry> getSourceClassesWithoutUnmatchedFields() {
77 Set<ClassEntry> out = Sets.newHashSet();
78 out.addAll(m_matchedSourceFields.keySet());
79 out.removeAll(m_unmatchedSourceFields.keySet());
80 return out;
81 }
82
83 public Collection<FieldEntry> getUnmatchedSourceFields() {
84 return m_unmatchedSourceFields.values();
85 }
86
87 public Collection<FieldEntry> getUnmatchedSourceFields(ClassEntry sourceClass) {
88 return m_unmatchedSourceFields.get(sourceClass);
89 }
90
91 public Collection<FieldEntry> getUnmatchedDestFields() {
92 return m_unmatchedDestFields.values();
93 }
94
95 public Collection<FieldEntry> getUnmatchedDestFields(ClassEntry destClass) {
96 return m_unmatchedDestFields.get(destClass);
97 }
98
99 public Collection<FieldEntry> getUnmatchableSourceFields() {
100 return m_unmatchableSourceFields.values();
101 }
102
103 public boolean hasSource(FieldEntry fieldEntry) {
104 return m_matches.containsKey(fieldEntry) || m_unmatchedSourceFields.containsValue(fieldEntry);
105 }
106
107 public boolean hasDest(FieldEntry fieldEntry) {
108 return m_matches.containsValue(fieldEntry) || m_unmatchedDestFields.containsValue(fieldEntry);
109 }
110
111 public BiMap<FieldEntry, FieldEntry> matches() {
112 return m_matches;
113 }
114
115 public boolean isMatchedSourceField(FieldEntry sourceField) {
116 return m_matches.containsKey(sourceField);
117 }
118
119 public boolean isMatchedDestField(FieldEntry destField) {
120 return m_matches.containsValue(destField);
121 }
122
123 public void makeMatch(FieldEntry sourceField, FieldEntry destField) {
124 boolean wasRemoved = m_unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
125 assert (wasRemoved);
126 wasRemoved = m_unmatchedDestFields.remove(destField.getClassEntry(), destField);
127 assert (wasRemoved);
128 addMatch(sourceField, destField);
129 }
130
131 public boolean isMatched(FieldEntry sourceField, FieldEntry destField) {
132 FieldEntry match = m_matches.get(sourceField);
133 return match != null && match.equals(destField);
134 }
135
136 public void unmakeMatch(FieldEntry sourceField, FieldEntry destField) {
137 boolean wasRemoved = m_matches.remove(sourceField) != null;
138 assert (wasRemoved);
139 wasRemoved = m_matchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
140 assert (wasRemoved);
141 addUnmatchedSourceField(sourceField);
142 addUnmatchedDestField(destField);
143 }
144
145 public void makeSourceUnmatchable(FieldEntry sourceField) {
146 assert (!isMatchedSourceField(sourceField));
147 boolean wasRemoved = m_unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
148 assert (wasRemoved);
149 addUnmatchableSourceField(sourceField);
150 }
151}
diff --git a/src/main/java/cuchaz/enigma/convert/MappingsConverter.java b/src/main/java/cuchaz/enigma/convert/MappingsConverter.java
new file mode 100644
index 00000000..394b8c81
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/MappingsConverter.java
@@ -0,0 +1,582 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.*;
14
15import java.util.*;
16import java.util.jar.JarFile;
17
18import cuchaz.enigma.Constants;
19import cuchaz.enigma.Deobfuscator;
20import cuchaz.enigma.analysis.JarIndex;
21import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
22import cuchaz.enigma.mapping.*;
23
24public class MappingsConverter {
25
26 public static ClassMatches computeClassMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) {
27
28 // index jars
29 System.out.println("Indexing source jar...");
30 JarIndex sourceIndex = new JarIndex();
31 sourceIndex.indexJar(sourceJar, false);
32 System.out.println("Indexing dest jar...");
33 JarIndex destIndex = new JarIndex();
34 destIndex.indexJar(destJar, false);
35
36 // compute the matching
37 ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null);
38 return new ClassMatches(matching.matches());
39 }
40
41 public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap<ClassEntry, ClassEntry> knownMatches) {
42
43 System.out.println("Iteratively matching classes");
44
45 ClassMatching lastMatching = null;
46 int round = 0;
47 SidedClassNamer sourceNamer = null;
48 SidedClassNamer destNamer = null;
49 for (boolean useReferences : Arrays.asList(false, true)) {
50
51 int numUniqueMatchesLastTime = 0;
52 if (lastMatching != null) {
53 numUniqueMatchesLastTime = lastMatching.uniqueMatches().size();
54 }
55
56 while (true) {
57
58 System.out.println("Round " + (++round) + "...");
59
60 // init the matching with identity settings
61 ClassMatching matching = new ClassMatching(
62 new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences),
63 new ClassIdentifier(destJar, destIndex, destNamer, useReferences)
64 );
65
66 if (knownMatches != null) {
67 matching.addKnownMatches(knownMatches);
68 }
69
70 if (lastMatching == null) {
71 // search all classes
72 matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries());
73 } else {
74 // we already know about these matches from last time
75 matching.addKnownMatches(lastMatching.uniqueMatches());
76
77 // search unmatched and ambiguously-matched classes
78 matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses());
79 for (ClassMatch match : lastMatching.ambiguousMatches()) {
80 matching.match(match.sourceClasses, match.destClasses);
81 }
82 }
83 System.out.println(matching);
84 BiMap<ClassEntry, ClassEntry> uniqueMatches = matching.uniqueMatches();
85
86 // did we match anything new this time?
87 if (uniqueMatches.size() > numUniqueMatchesLastTime) {
88 numUniqueMatchesLastTime = uniqueMatches.size();
89 lastMatching = matching;
90 } else {
91 break;
92 }
93
94 // update the namers
95 ClassNamer namer = new ClassNamer(uniqueMatches);
96 sourceNamer = namer.getSourceNamer();
97 destNamer = namer.getDestNamer();
98 }
99 }
100
101 return lastMatching;
102 }
103
104 public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
105
106 // sort the unique matches by size of inner class chain
107 Multimap<Integer, java.util.Map.Entry<ClassEntry, ClassEntry>> matchesByDestChainSize = HashMultimap.create();
108 for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matches.getUniqueMatches().entrySet()) {
109 int chainSize = destDeobfuscator.getJarIndex().getObfClassChain(match.getValue()).size();
110 matchesByDestChainSize.put(chainSize, match);
111 }
112
113 // build the mappings (in order of small-to-large inner chains)
114 Mappings newMappings = new Mappings();
115 List<Integer> chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet());
116 Collections.sort(chainSizes);
117 for (int chainSize : chainSizes) {
118 for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matchesByDestChainSize.get(chainSize)) {
119
120 // get class info
121 ClassEntry obfSourceClassEntry = match.getKey();
122 ClassEntry obfDestClassEntry = match.getValue();
123 List<ClassEntry> destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(obfDestClassEntry);
124
125 ClassMapping sourceMapping = sourceDeobfuscator.getMappings().getClassByObf(obfSourceClassEntry);
126 if (sourceMapping == null) {
127 // if this class was never deobfuscated, don't try to match it
128 continue;
129 }
130
131 // find out where to make the dest class mapping
132 if (destClassChain.size() == 1) {
133 // not an inner class, add directly to mappings
134 newMappings.addClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, false));
135 } else {
136 // inner class, find the outer class mapping
137 ClassMapping destMapping = null;
138 for (int i = 0; i < destClassChain.size() - 1; i++) {
139 ClassEntry destChainClassEntry = destClassChain.get(i);
140 if (destMapping == null) {
141 destMapping = newMappings.getClassByObf(destChainClassEntry);
142 if (destMapping == null) {
143 destMapping = new ClassMapping(destChainClassEntry.getName());
144 newMappings.addClassMapping(destMapping);
145 }
146 } else {
147 destMapping = destMapping.getInnerClassByObfSimple(destChainClassEntry.getInnermostClassName());
148 if (destMapping == null) {
149 destMapping = new ClassMapping(destChainClassEntry.getName());
150 destMapping.addInnerClassMapping(destMapping);
151 }
152 }
153 }
154 destMapping.addInnerClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, true));
155 }
156 }
157 }
158 return newMappings;
159 }
160
161 private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping oldClassMapping, final ClassMatches matches, boolean useSimpleName) {
162
163 ClassNameReplacer replacer = new ClassNameReplacer() {
164 @Override
165 public String replace(String className) {
166 ClassEntry newClassEntry = matches.getUniqueMatches().get(new ClassEntry(className));
167 if (newClassEntry != null) {
168 return newClassEntry.getName();
169 }
170 return null;
171 }
172 };
173
174 ClassMapping newClassMapping;
175 String deobfName = oldClassMapping.getDeobfName();
176 if (deobfName != null) {
177 if (useSimpleName) {
178 deobfName = new ClassEntry(deobfName).getSimpleName();
179 }
180 newClassMapping = new ClassMapping(newObfClass.getName(), deobfName);
181 } else {
182 newClassMapping = new ClassMapping(newObfClass.getName());
183 }
184
185 // migrate fields
186 for (FieldMapping oldFieldMapping : oldClassMapping.fields()) {
187 if (canMigrate(oldFieldMapping.getObfType(), matches)) {
188 newClassMapping.addFieldMapping(new FieldMapping(oldFieldMapping, replacer));
189 } else {
190 System.out.println(String.format("Can't map field, dropping: %s.%s %s",
191 oldClassMapping.getDeobfName(),
192 oldFieldMapping.getDeobfName(),
193 oldFieldMapping.getObfType()
194 ));
195 }
196 }
197
198 // migrate methods
199 for (MethodMapping oldMethodMapping : oldClassMapping.methods()) {
200 if (canMigrate(oldMethodMapping.getObfSignature(), matches)) {
201 newClassMapping.addMethodMapping(new MethodMapping(oldMethodMapping, replacer));
202 } else {
203 System.out.println(String.format("Can't map method, dropping: %s.%s %s",
204 oldClassMapping.getDeobfName(),
205 oldMethodMapping.getDeobfName(),
206 oldMethodMapping.getObfSignature()
207 ));
208 }
209 }
210
211 return newClassMapping;
212 }
213
214 private static boolean canMigrate(Signature oldObfSignature, ClassMatches classMatches) {
215 for (Type oldObfType : oldObfSignature.types()) {
216 if (!canMigrate(oldObfType, classMatches)) {
217 return false;
218 }
219 }
220 return true;
221 }
222
223 private static boolean canMigrate(Type oldObfType, ClassMatches classMatches) {
224
225 // non classes can be migrated
226 if (!oldObfType.hasClass()) {
227 return true;
228 }
229
230 // non obfuscated classes can be migrated
231 ClassEntry classEntry = oldObfType.getClassEntry();
232 if (!classEntry.getPackageName().equals(Constants.NonePackage)) {
233 return true;
234 }
235
236 // obfuscated classes with mappings can be migrated
237 return classMatches.getUniqueMatches().containsKey(classEntry);
238 }
239
240 public static void convertMappings(Mappings mappings, BiMap<ClassEntry, ClassEntry> changes) {
241
242 // sort the changes so classes are renamed in the correct order
243 // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b
244 LinkedHashMap<ClassEntry, ClassEntry> sortedChanges = Maps.newLinkedHashMap();
245 int numChangesLeft = changes.size();
246 while (!changes.isEmpty()) {
247 Iterator<Map.Entry<ClassEntry, ClassEntry>> iter = changes.entrySet().iterator();
248 while (iter.hasNext()) {
249 Map.Entry<ClassEntry, ClassEntry> change = iter.next();
250 if (changes.containsKey(change.getValue())) {
251 sortedChanges.put(change.getKey(), change.getValue());
252 iter.remove();
253 }
254 }
255
256 // did we remove any changes?
257 if (numChangesLeft - changes.size() > 0) {
258 // keep going
259 numChangesLeft = changes.size();
260 } else {
261 // can't sort anymore. There must be a loop
262 break;
263 }
264 }
265 if (!changes.isEmpty()) {
266 throw new Error("Unable to sort class changes! There must be a cycle.");
267 }
268
269 // convert the mappings in the correct class order
270 for (Map.Entry<ClassEntry, ClassEntry> entry : sortedChanges.entrySet()) {
271 mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName());
272 }
273 }
274
275 public interface Doer<T extends Entry> {
276 Collection<T> getDroppedEntries(MappingsChecker checker);
277
278 Collection<T> getObfEntries(JarIndex jarIndex);
279
280 Collection<? extends MemberMapping<T>> getMappings(ClassMapping destClassMapping);
281
282 Set<T> filterEntries(Collection<T> obfEntries, T obfSourceEntry, ClassMatches classMatches);
283
284 void setUpdateObfMember(ClassMapping classMapping, MemberMapping<T> memberMapping, T newEntry);
285
286 boolean hasObfMember(ClassMapping classMapping, T obfEntry);
287
288 void removeMemberByObf(ClassMapping classMapping, T obfEntry);
289 }
290
291 public static Doer<FieldEntry> getFieldDoer() {
292 return new Doer<FieldEntry>() {
293
294 @Override
295 public Collection<FieldEntry> getDroppedEntries(MappingsChecker checker) {
296 return checker.getDroppedFieldMappings().keySet();
297 }
298
299 @Override
300 public Collection<FieldEntry> getObfEntries(JarIndex jarIndex) {
301 return jarIndex.getObfFieldEntries();
302 }
303
304 @Override
305 public Collection<? extends MemberMapping<FieldEntry>> getMappings(ClassMapping destClassMapping) {
306 return (Collection<? extends MemberMapping<FieldEntry>>) destClassMapping.fields();
307 }
308
309 @Override
310 public Set<FieldEntry> filterEntries(Collection<FieldEntry> obfDestFields, FieldEntry obfSourceField, ClassMatches classMatches) {
311 Set<FieldEntry> out = Sets.newHashSet();
312 for (FieldEntry obfDestField : obfDestFields) {
313 Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse());
314 if (translatedDestType.equals(obfSourceField.getType())) {
315 out.add(obfDestField);
316 }
317 }
318 return out;
319 }
320
321 @Override
322 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<FieldEntry> memberMapping, FieldEntry newField) {
323 FieldMapping fieldMapping = (FieldMapping) memberMapping;
324 classMapping.setFieldObfNameAndType(fieldMapping.getObfName(), fieldMapping.getObfType(), newField.getName(), newField.getType());
325 }
326
327 @Override
328 public boolean hasObfMember(ClassMapping classMapping, FieldEntry obfField) {
329 return classMapping.getFieldByObf(obfField.getName(), obfField.getType()) != null;
330 }
331
332 @Override
333 public void removeMemberByObf(ClassMapping classMapping, FieldEntry obfField) {
334 classMapping.removeFieldMapping(classMapping.getFieldByObf(obfField.getName(), obfField.getType()));
335 }
336 };
337 }
338
339 public static Doer<BehaviorEntry> getMethodDoer() {
340 return new Doer<BehaviorEntry>() {
341
342 @Override
343 public Collection<BehaviorEntry> getDroppedEntries(MappingsChecker checker) {
344 return checker.getDroppedMethodMappings().keySet();
345 }
346
347 @Override
348 public Collection<BehaviorEntry> getObfEntries(JarIndex jarIndex) {
349 return jarIndex.getObfBehaviorEntries();
350 }
351
352 @Override
353 public Collection<? extends MemberMapping<BehaviorEntry>> getMappings(ClassMapping destClassMapping) {
354 return (Collection<? extends MemberMapping<BehaviorEntry>>) destClassMapping.methods();
355 }
356
357 @Override
358 public Set<BehaviorEntry> filterEntries(Collection<BehaviorEntry> obfDestFields, BehaviorEntry obfSourceField, ClassMatches classMatches) {
359 Set<BehaviorEntry> out = Sets.newHashSet();
360 for (BehaviorEntry obfDestField : obfDestFields) {
361 Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse());
362 if (translatedDestSignature == null && obfSourceField.getSignature() == null) {
363 out.add(obfDestField);
364 } else if (translatedDestSignature == null || obfSourceField.getSignature() == null) {
365 // skip it
366 } else if (translatedDestSignature.equals(obfSourceField.getSignature())) {
367 out.add(obfDestField);
368 }
369 }
370 return out;
371 }
372
373 @Override
374 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<BehaviorEntry> memberMapping, BehaviorEntry newBehavior) {
375 MethodMapping methodMapping = (MethodMapping) memberMapping;
376 classMapping.setMethodObfNameAndSignature(methodMapping.getObfName(), methodMapping.getObfSignature(), newBehavior.getName(), newBehavior.getSignature());
377 }
378
379 @Override
380 public boolean hasObfMember(ClassMapping classMapping, BehaviorEntry obfBehavior) {
381 return classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()) != null;
382 }
383
384 @Override
385 public void removeMemberByObf(ClassMapping classMapping, BehaviorEntry obfBehavior) {
386 classMapping.removeMethodMapping(classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()));
387 }
388 };
389 }
390
391 public static <T extends Entry> MemberMatches<T> computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer<T> doer) {
392
393 MemberMatches<T> memberMatches = new MemberMatches<T>();
394
395 // unmatched source fields are easy
396 MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex());
397 checker.dropBrokenMappings(destMappings);
398 for (T destObfEntry : doer.getDroppedEntries(checker)) {
399 T srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse());
400 memberMatches.addUnmatchedSourceEntry(srcObfEntry);
401 }
402
403 // get matched fields (anything that's left after the checks/drops is matched(
404 for (ClassMapping classMapping : destMappings.classes()) {
405 collectMatchedFields(memberMatches, classMapping, classMatches, doer);
406 }
407
408 // get unmatched dest fields
409 for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) {
410 if (!memberMatches.isMatchedDestEntry(destEntry)) {
411 memberMatches.addUnmatchedDestEntry(destEntry);
412 }
413 }
414
415 System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries...");
416
417 // go through the unmatched source fields and try to pick out the easy matches
418 for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) {
419 for (T obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) {
420
421 // get the possible dest matches
422 ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass);
423
424 // filter by type/signature
425 Set<T> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches);
426
427 if (obfDestEntries.size() == 1) {
428 // make the easy match
429 memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next());
430 } else if (obfDestEntries.isEmpty()) {
431 // no match is possible =(
432 memberMatches.makeSourceUnmatchable(obfSourceEntry);
433 }
434 }
435 }
436
437 System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries",
438 memberMatches.getUnmatchedSourceEntries().size(),
439 memberMatches.getUnmatchableSourceEntries().size()
440 ));
441
442 return memberMatches;
443 }
444
445 private static <T extends Entry> void collectMatchedFields(MemberMatches<T> memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer<T> doer) {
446
447 // get the fields for this class
448 for (MemberMapping<T> destEntryMapping : doer.getMappings(destClassMapping)) {
449 T destObfField = destEntryMapping.getObfEntry(destClassMapping.getObfEntry());
450 T srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse());
451 memberMatches.addMatch(srcObfField, destObfField);
452 }
453
454 // recurse
455 for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) {
456 collectMatchedFields(memberMatches, destInnerClassMapping, classMatches, doer);
457 }
458 }
459
460 @SuppressWarnings("unchecked")
461 private static <T extends Entry> T translate(T in, BiMap<ClassEntry, ClassEntry> map) {
462 if (in instanceof FieldEntry) {
463 return (T) new FieldEntry(
464 map.get(in.getClassEntry()),
465 in.getName(),
466 translate(((FieldEntry) in).getType(), map)
467 );
468 } else if (in instanceof MethodEntry) {
469 return (T) new MethodEntry(
470 map.get(in.getClassEntry()),
471 in.getName(),
472 translate(((MethodEntry) in).getSignature(), map)
473 );
474 } else if (in instanceof ConstructorEntry) {
475 return (T) new ConstructorEntry(
476 map.get(in.getClassEntry()),
477 translate(((ConstructorEntry) in).getSignature(), map)
478 );
479 }
480 throw new Error("Unhandled entry type: " + in.getClass());
481 }
482
483 private static Type translate(Type type, final BiMap<ClassEntry, ClassEntry> map) {
484 return new Type(type, new ClassNameReplacer() {
485 @Override
486 public String replace(String inClassName) {
487 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName));
488 if (outClassEntry == null) {
489 return null;
490 }
491 return outClassEntry.getName();
492 }
493 });
494 }
495
496 private static Signature translate(Signature signature, final BiMap<ClassEntry, ClassEntry> map) {
497 if (signature == null) {
498 return null;
499 }
500 return new Signature(signature, new ClassNameReplacer() {
501 @Override
502 public String replace(String inClassName) {
503 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName));
504 if (outClassEntry == null) {
505 return null;
506 }
507 return outClassEntry.getName();
508 }
509 });
510 }
511
512 public static <T extends Entry> void applyMemberMatches(Mappings mappings, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) {
513 for (ClassMapping classMapping : mappings.classes()) {
514 applyMemberMatches(classMapping, classMatches, memberMatches, doer);
515 }
516 }
517
518 private static <T extends Entry> void applyMemberMatches(ClassMapping classMapping, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) {
519
520 // get the classes
521 ClassEntry obfDestClass = classMapping.getObfEntry();
522
523 // make a map of all the renames we need to make
524 Map<T, T> renames = Maps.newHashMap();
525 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) {
526 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass);
527 T obfSourceEntry = getSourceEntryFromDestMapping(memberMapping, obfDestClass, classMatches);
528
529 // but drop the unmatchable things
530 if (memberMatches.isUnmatchableSourceEntry(obfSourceEntry)) {
531 doer.removeMemberByObf(classMapping, obfOldDestEntry);
532 continue;
533 }
534
535 T obfNewDestEntry = memberMatches.matches().get(obfSourceEntry);
536 if (obfNewDestEntry != null && !obfOldDestEntry.getName().equals(obfNewDestEntry.getName())) {
537 renames.put(obfOldDestEntry, obfNewDestEntry);
538 }
539 }
540
541 if (!renames.isEmpty()) {
542
543 // apply to this class (should never need more than n passes)
544 int numRenamesAppliedThisRound;
545 do {
546 numRenamesAppliedThisRound = 0;
547
548 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) {
549 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass);
550 T obfNewDestEntry = renames.get(obfOldDestEntry);
551 if (obfNewDestEntry != null) {
552 // make sure this rename won't cause a collision
553 // otherwise, save it for the next round and try again next time
554 if (!doer.hasObfMember(classMapping, obfNewDestEntry)) {
555 doer.setUpdateObfMember(classMapping, memberMapping, obfNewDestEntry);
556 renames.remove(obfOldDestEntry);
557 numRenamesAppliedThisRound++;
558 }
559 }
560 }
561 } while (numRenamesAppliedThisRound > 0);
562
563 if (!renames.isEmpty()) {
564 System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d renames left.",
565 classMapping.getObfFullName(), renames.size()
566 ));
567 for (Map.Entry<T, T> entry : renames.entrySet()) {
568 System.err.println(String.format("\t%s -> %s", entry.getKey().getName(), entry.getValue().getName()));
569 }
570 }
571 }
572
573 // recurse
574 for (ClassMapping innerClassMapping : classMapping.innerClasses()) {
575 applyMemberMatches(innerClassMapping, classMatches, memberMatches, doer);
576 }
577 }
578
579 private static <T extends Entry> T getSourceEntryFromDestMapping(MemberMapping<T> destMemberMapping, ClassEntry obfDestClass, ClassMatches classMatches) {
580 return translate(destMemberMapping.getObfEntry(obfDestClass), classMatches.getUniqueMatches().inverse());
581 }
582}
diff --git a/src/main/java/cuchaz/enigma/convert/MatchesReader.java b/src/main/java/cuchaz/enigma/convert/MatchesReader.java
new file mode 100644
index 00000000..773566df
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/MatchesReader.java
@@ -0,0 +1,109 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.Lists;
14
15import java.io.BufferedReader;
16import java.io.File;
17import java.io.FileReader;
18import java.io.IOException;
19import java.util.Collection;
20import java.util.List;
21
22import cuchaz.enigma.mapping.*;
23
24
25public class MatchesReader {
26
27 public static ClassMatches readClasses(File file)
28 throws IOException {
29 try (BufferedReader in = new BufferedReader(new FileReader(file))) {
30 ClassMatches matches = new ClassMatches();
31 String line = null;
32 while ((line = in.readLine()) != null) {
33 matches.add(readClassMatch(line));
34 }
35 return matches;
36 }
37 }
38
39 private static ClassMatch readClassMatch(String line)
40 throws IOException {
41 String[] sides = line.split(":", 2);
42 return new ClassMatch(readClasses(sides[0]), readClasses(sides[1]));
43 }
44
45 private static Collection<ClassEntry> readClasses(String in) {
46 List<ClassEntry> entries = Lists.newArrayList();
47 for (String className : in.split(",")) {
48 className = className.trim();
49 if (className.length() > 0) {
50 entries.add(new ClassEntry(className));
51 }
52 }
53 return entries;
54 }
55
56 public static <T extends Entry> MemberMatches<T> readMembers(File file)
57 throws IOException {
58 try (BufferedReader in = new BufferedReader(new FileReader(file))) {
59 MemberMatches<T> matches = new MemberMatches<T>();
60 String line = null;
61 while ((line = in.readLine()) != null) {
62 readMemberMatch(matches, line);
63 }
64 return matches;
65 }
66 }
67
68 private static <T extends Entry> void readMemberMatch(MemberMatches<T> matches, String line) {
69 if (line.startsWith("!")) {
70 T source = readEntry(line.substring(1));
71 matches.addUnmatchableSourceEntry(source);
72 } else {
73 String[] parts = line.split(":", 2);
74 T source = readEntry(parts[0]);
75 T dest = readEntry(parts[1]);
76 if (source != null && dest != null) {
77 matches.addMatch(source, dest);
78 } else if (source != null) {
79 matches.addUnmatchedSourceEntry(source);
80 } else if (dest != null) {
81 matches.addUnmatchedDestEntry(dest);
82 }
83 }
84 }
85
86 @SuppressWarnings("unchecked")
87 private static <T extends Entry> T readEntry(String in) {
88 if (in.length() <= 0) {
89 return null;
90 }
91 String[] parts = in.split(" ");
92 if (parts.length == 3 && parts[2].indexOf('(') < 0) {
93 return (T) new FieldEntry(
94 new ClassEntry(parts[0]),
95 parts[1],
96 new Type(parts[2])
97 );
98 } else {
99 assert (parts.length == 2 || parts.length == 3);
100 if (parts.length == 2) {
101 return (T) EntryFactory.getBehaviorEntry(parts[0], parts[1]);
102 } else if (parts.length == 3) {
103 return (T) EntryFactory.getBehaviorEntry(parts[0], parts[1], parts[2]);
104 } else {
105 throw new Error("Malformed behavior entry: " + in);
106 }
107 }
108 }
109}
diff --git a/src/main/java/cuchaz/enigma/convert/MatchesWriter.java b/src/main/java/cuchaz/enigma/convert/MatchesWriter.java
new file mode 100644
index 00000000..baf79293
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/MatchesWriter.java
@@ -0,0 +1,121 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.io.File;
14import java.io.FileWriter;
15import java.io.IOException;
16import java.util.Map;
17
18import cuchaz.enigma.mapping.BehaviorEntry;
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.Entry;
21import cuchaz.enigma.mapping.FieldEntry;
22
23
24public class MatchesWriter {
25
26 public static void writeClasses(ClassMatches matches, File file)
27 throws IOException {
28 try (FileWriter out = new FileWriter(file)) {
29 for (ClassMatch match : matches) {
30 writeClassMatch(out, match);
31 }
32 }
33 }
34
35 private static void writeClassMatch(FileWriter out, ClassMatch match)
36 throws IOException {
37 writeClasses(out, match.sourceClasses);
38 out.write(":");
39 writeClasses(out, match.destClasses);
40 out.write("\n");
41 }
42
43 private static void writeClasses(FileWriter out, Iterable<ClassEntry> classes)
44 throws IOException {
45 boolean isFirst = true;
46 for (ClassEntry entry : classes) {
47 if (isFirst) {
48 isFirst = false;
49 } else {
50 out.write(",");
51 }
52 out.write(entry.toString());
53 }
54 }
55
56 public static <T extends Entry> void writeMembers(MemberMatches<T> matches, File file)
57 throws IOException {
58 try (FileWriter out = new FileWriter(file)) {
59 for (Map.Entry<T, T> match : matches.matches().entrySet()) {
60 writeMemberMatch(out, match.getKey(), match.getValue());
61 }
62 for (T entry : matches.getUnmatchedSourceEntries()) {
63 writeMemberMatch(out, entry, null);
64 }
65 for (T entry : matches.getUnmatchedDestEntries()) {
66 writeMemberMatch(out, null, entry);
67 }
68 for (T entry : matches.getUnmatchableSourceEntries()) {
69 writeUnmatchableEntry(out, entry);
70 }
71 }
72 }
73
74 private static <T extends Entry> void writeMemberMatch(FileWriter out, T source, T dest)
75 throws IOException {
76 if (source != null) {
77 writeEntry(out, source);
78 }
79 out.write(":");
80 if (dest != null) {
81 writeEntry(out, dest);
82 }
83 out.write("\n");
84 }
85
86 private static <T extends Entry> void writeUnmatchableEntry(FileWriter out, T entry)
87 throws IOException {
88 out.write("!");
89 writeEntry(out, entry);
90 out.write("\n");
91 }
92
93 private static <T extends Entry> void writeEntry(FileWriter out, T entry)
94 throws IOException {
95 if (entry instanceof FieldEntry) {
96 writeField(out, (FieldEntry) entry);
97 } else if (entry instanceof BehaviorEntry) {
98 writeBehavior(out, (BehaviorEntry) entry);
99 }
100 }
101
102 private static void writeField(FileWriter out, FieldEntry fieldEntry)
103 throws IOException {
104 out.write(fieldEntry.getClassName());
105 out.write(" ");
106 out.write(fieldEntry.getName());
107 out.write(" ");
108 out.write(fieldEntry.getType().toString());
109 }
110
111 private static void writeBehavior(FileWriter out, BehaviorEntry behaviorEntry)
112 throws IOException {
113 out.write(behaviorEntry.getClassName());
114 out.write(" ");
115 out.write(behaviorEntry.getName());
116 out.write(" ");
117 if (behaviorEntry.getSignature() != null) {
118 out.write(behaviorEntry.getSignature().toString());
119 }
120 }
121}
diff --git a/src/main/java/cuchaz/enigma/convert/MemberMatches.java b/src/main/java/cuchaz/enigma/convert/MemberMatches.java
new file mode 100644
index 00000000..32850cca
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/convert/MemberMatches.java
@@ -0,0 +1,155 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import com.google.common.collect.*;
14
15import java.util.Collection;
16import java.util.Set;
17
18import cuchaz.enigma.mapping.ClassEntry;
19import cuchaz.enigma.mapping.Entry;
20
21
22public class MemberMatches<T extends Entry> {
23
24 private BiMap<T, T> m_matches;
25 private Multimap<ClassEntry, T> m_matchedSourceEntries;
26 private Multimap<ClassEntry, T> m_unmatchedSourceEntries;
27 private Multimap<ClassEntry, T> m_unmatchedDestEntries;
28 private Multimap<ClassEntry, T> m_unmatchableSourceEntries;
29
30 public MemberMatches() {
31 m_matches = HashBiMap.create();
32 m_matchedSourceEntries = HashMultimap.create();
33 m_unmatchedSourceEntries = HashMultimap.create();
34 m_unmatchedDestEntries = HashMultimap.create();
35 m_unmatchableSourceEntries = HashMultimap.create();
36 }
37
38 public void addMatch(T srcEntry, T destEntry) {
39 boolean wasAdded = m_matches.put(srcEntry, destEntry) == null;
40 assert (wasAdded);
41 wasAdded = m_matchedSourceEntries.put(srcEntry.getClassEntry(), srcEntry);
42 assert (wasAdded);
43 }
44
45 public void addUnmatchedSourceEntry(T sourceEntry) {
46 boolean wasAdded = m_unmatchedSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry);
47 assert (wasAdded);
48 }
49
50 public void addUnmatchedSourceEntries(Iterable<T> sourceEntries) {
51 for (T sourceEntry : sourceEntries) {
52 addUnmatchedSourceEntry(sourceEntry);
53 }
54 }
55
56 public void addUnmatchedDestEntry(T destEntry) {
57 boolean wasAdded = m_unmatchedDestEntries.put(destEntry.getClassEntry(), destEntry);
58 assert (wasAdded);
59 }
60
61 public void addUnmatchedDestEntries(Iterable<T> destEntriesntries) {
62 for (T entry : destEntriesntries) {
63 addUnmatchedDestEntry(entry);
64 }
65 }
66
67 public void addUnmatchableSourceEntry(T sourceEntry) {
68 boolean wasAdded = m_unmatchableSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry);
69 assert (wasAdded);
70 }
71
72 public Set<ClassEntry> getSourceClassesWithUnmatchedEntries() {
73 return m_unmatchedSourceEntries.keySet();
74 }
75
76 public Collection<ClassEntry> getSourceClassesWithoutUnmatchedEntries() {
77 Set<ClassEntry> out = Sets.newHashSet();
78 out.addAll(m_matchedSourceEntries.keySet());
79 out.removeAll(m_unmatchedSourceEntries.keySet());
80 return out;
81 }
82
83 public Collection<T> getUnmatchedSourceEntries() {
84 return m_unmatchedSourceEntries.values();
85 }
86
87 public Collection<T> getUnmatchedSourceEntries(ClassEntry sourceClass) {
88 return m_unmatchedSourceEntries.get(sourceClass);
89 }
90
91 public Collection<T> getUnmatchedDestEntries() {
92 return m_unmatchedDestEntries.values();
93 }
94
95 public Collection<T> getUnmatchedDestEntries(ClassEntry destClass) {
96 return m_unmatchedDestEntries.get(destClass);
97 }
98
99 public Collection<T> getUnmatchableSourceEntries() {
100 return m_unmatchableSourceEntries.values();
101 }
102
103 public boolean hasSource(T sourceEntry) {
104 return m_matches.containsKey(sourceEntry) || m_unmatchedSourceEntries.containsValue(sourceEntry);
105 }
106
107 public boolean hasDest(T destEntry) {
108 return m_matches.containsValue(destEntry) || m_unmatchedDestEntries.containsValue(destEntry);
109 }
110
111 public BiMap<T, T> matches() {
112 return m_matches;
113 }
114
115 public boolean isMatchedSourceEntry(T sourceEntry) {
116 return m_matches.containsKey(sourceEntry);
117 }
118
119 public boolean isMatchedDestEntry(T destEntry) {
120 return m_matches.containsValue(destEntry);
121 }
122
123 public boolean isUnmatchableSourceEntry(T sourceEntry) {
124 return m_unmatchableSourceEntries.containsEntry(sourceEntry.getClassEntry(), sourceEntry);
125 }
126
127 public void makeMatch(T sourceEntry, T destEntry) {
128 boolean wasRemoved = m_unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
129 assert (wasRemoved);
130 wasRemoved = m_unmatchedDestEntries.remove(destEntry.getClassEntry(), destEntry);
131 assert (wasRemoved);
132 addMatch(sourceEntry, destEntry);
133 }
134
135 public boolean isMatched(T sourceEntry, T destEntry) {
136 T match = m_matches.get(sourceEntry);
137 return match != null && match.equals(destEntry);
138 }
139
140 public void unmakeMatch(T sourceEntry, T destEntry) {
141 boolean wasRemoved = m_matches.remove(sourceEntry) != null;
142 assert (wasRemoved);
143 wasRemoved = m_matchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
144 assert (wasRemoved);
145 addUnmatchedSourceEntry(sourceEntry);
146 addUnmatchedDestEntry(destEntry);
147 }
148
149 public void makeSourceUnmatchable(T sourceEntry) {
150 assert (!isMatchedSourceEntry(sourceEntry));
151 boolean wasRemoved = m_unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
152 assert (wasRemoved);
153 addUnmatchableSourceEntry(sourceEntry);
154 }
155}
diff --git a/src/main/java/cuchaz/enigma/gui/AboutDialog.java b/src/main/java/cuchaz/enigma/gui/AboutDialog.java
new file mode 100644
index 00000000..a8743998
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/AboutDialog.java
@@ -0,0 +1,70 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14import java.awt.Container;
15import java.awt.Cursor;
16import java.awt.FlowLayout;
17import java.io.IOException;
18
19import javax.swing.*;
20
21import cuchaz.enigma.Constants;
22import cuchaz.enigma.Util;
23
24public class AboutDialog {
25
26 public static void show(JFrame parent) {
27 // init frame
28 final JFrame frame = new JFrame(Constants.Name + " - About");
29 final Container pane = frame.getContentPane();
30 pane.setLayout(new FlowLayout());
31
32 // load the content
33 try {
34 String html = Util.readResourceToString("/about.html");
35 html = String.format(html, Constants.Name, Constants.Version);
36 JLabel label = new JLabel(html);
37 label.setHorizontalAlignment(JLabel.CENTER);
38 pane.add(label);
39 } catch (IOException ex) {
40 throw new Error(ex);
41 }
42
43 // show the link
44 String html = "<html><a href=\"%s\">%s</a></html>";
45 html = String.format(html, Constants.Url, Constants.Url);
46 JButton link = new JButton(html);
47 link.addActionListener(event -> Util.openUrl(Constants.Url));
48 link.setBorderPainted(false);
49 link.setOpaque(false);
50 link.setBackground(Color.WHITE);
51 link.setCursor(new Cursor(Cursor.HAND_CURSOR));
52 link.setFocusable(false);
53 JPanel linkPanel = new JPanel();
54 linkPanel.add(link);
55 pane.add(linkPanel);
56
57 // show ok button
58 JButton okButton = new JButton("Ok");
59 pane.add(okButton);
60 okButton.addActionListener(arg0 -> frame.dispose());
61
62 // show the frame
63 pane.doLayout();
64 frame.setSize(400, 220);
65 frame.setResizable(false);
66 frame.setLocationRelativeTo(parent);
67 frame.setVisible(true);
68 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
69 }
70}
diff --git a/src/main/java/cuchaz/enigma/gui/BoxHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/BoxHighlightPainter.java
new file mode 100644
index 00000000..efe6b506
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/BoxHighlightPainter.java
@@ -0,0 +1,64 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14import java.awt.Graphics;
15import java.awt.Rectangle;
16import java.awt.Shape;
17
18import javax.swing.text.BadLocationException;
19import javax.swing.text.Highlighter;
20import javax.swing.text.JTextComponent;
21
22public abstract class BoxHighlightPainter implements Highlighter.HighlightPainter {
23
24 private Color m_fillColor;
25 private Color m_borderColor;
26
27 protected BoxHighlightPainter(Color fillColor, Color borderColor) {
28 m_fillColor = fillColor;
29 m_borderColor = borderColor;
30 }
31
32 @Override
33 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) {
34 Rectangle bounds = getBounds(text, start, end);
35
36 // fill the area
37 if (m_fillColor != null) {
38 g.setColor(m_fillColor);
39 g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
40 }
41
42 // draw a box around the area
43 g.setColor(m_borderColor);
44 g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
45 }
46
47 protected static Rectangle getBounds(JTextComponent text, int start, int end) {
48 try {
49 // determine the bounds of the text
50 Rectangle bounds = text.modelToView(start).union(text.modelToView(end));
51
52 // adjust the box so it looks nice
53 bounds.x -= 2;
54 bounds.width += 2;
55 bounds.y += 1;
56 bounds.height -= 2;
57
58 return bounds;
59 } catch (BadLocationException ex) {
60 // don't care... just return something
61 return new Rectangle(0, 0, 0, 0);
62 }
63 }
64}
diff --git a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java b/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
new file mode 100644
index 00000000..b4acbce9
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
@@ -0,0 +1,38 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import javax.swing.text.DefaultCaret;
14import javax.swing.text.Highlighter;
15
16public class BrowserCaret extends DefaultCaret {
17
18 private static final long serialVersionUID = 1158977422507969940L;
19
20 private static final Highlighter.HighlightPainter m_selectionPainter = (g, p0, p1, bounds, c) -> {
21 // don't paint anything
22 };
23
24 @Override
25 public boolean isSelectionVisible() {
26 return false;
27 }
28
29 @Override
30 public boolean isVisible() {
31 return true;
32 }
33
34 @Override
35 public Highlighter.HighlightPainter getSelectionPainter() {
36 return m_selectionPainter;
37 }
38}
diff --git a/src/cuchaz/enigma/gui/ClassListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/ClassListCellRenderer.java
index cde3e4ca..89b9bdd4 100644
--- a/src/cuchaz/enigma/gui/ClassListCellRenderer.java
+++ b/src/main/java/cuchaz/enigma/gui/ClassListCellRenderer.java
@@ -4,33 +4,33 @@
4 * are made available under the terms of the GNU Lesser General Public 4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at 5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html 6 * http://www.gnu.org/licenses/lgpl.html
7 * 7 * <p>
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.gui; 11package cuchaz.enigma.gui;
12 12
13import java.awt.Component; 13import java.awt.Component;
14 14
15import javassist.bytecode.Descriptor;
16
17import javax.swing.DefaultListCellRenderer; 15import javax.swing.DefaultListCellRenderer;
18import javax.swing.JLabel; 16import javax.swing.JLabel;
19import javax.swing.JList; 17import javax.swing.JList;
20import javax.swing.ListCellRenderer; 18import javax.swing.ListCellRenderer;
21 19
20import javassist.bytecode.Descriptor;
21
22public class ClassListCellRenderer implements ListCellRenderer<String> { 22public class ClassListCellRenderer implements ListCellRenderer<String> {
23 23
24 private DefaultListCellRenderer m_defaultRenderer; 24 private DefaultListCellRenderer m_defaultRenderer;
25 25
26 public ClassListCellRenderer() { 26 public ClassListCellRenderer() {
27 m_defaultRenderer = new DefaultListCellRenderer(); 27 m_defaultRenderer = new DefaultListCellRenderer();
28 } 28 }
29 29
30 @Override 30 @Override
31 public Component getListCellRendererComponent(JList<? extends String> list, String className, int index, boolean isSelected, boolean hasFocus) { 31 public Component getListCellRendererComponent(JList<? extends String> list, String className, int index, boolean isSelected, boolean hasFocus) {
32 JLabel label = (JLabel)m_defaultRenderer.getListCellRendererComponent(list, className, index, isSelected, hasFocus); 32 JLabel label = (JLabel) m_defaultRenderer.getListCellRendererComponent(list, className, index, isSelected, hasFocus);
33 label.setText(Descriptor.toJavaName(className)); 33 label.setText(Descriptor.toJavaName(className));
34 return label; 34 return label;
35 } 35 }
36} 36}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java b/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
new file mode 100644
index 00000000..440565c3
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ClassMatchingGui.java
@@ -0,0 +1,538 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import com.google.common.collect.BiMap;
14import com.google.common.collect.Lists;
15import com.google.common.collect.Maps;
16
17import java.awt.BorderLayout;
18import java.awt.Container;
19import java.awt.Dimension;
20import java.awt.FlowLayout;
21import java.awt.event.ActionListener;
22import java.util.Collection;
23import java.util.Collections;
24import java.util.List;
25import java.util.Map;
26
27import javax.swing.*;
28
29import cuchaz.enigma.Constants;
30import cuchaz.enigma.Deobfuscator;
31import cuchaz.enigma.convert.*;
32import cuchaz.enigma.mapping.ClassEntry;
33import cuchaz.enigma.mapping.Mappings;
34import cuchaz.enigma.mapping.MappingsChecker;
35import de.sciss.syntaxpane.DefaultSyntaxKit;
36
37
38public class ClassMatchingGui {
39
40 private enum SourceType {
41 Matched {
42 @Override
43 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
44 return matches.getUniqueMatches().keySet();
45 }
46 },
47 Unmatched {
48 @Override
49 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
50 return matches.getUnmatchedSourceClasses();
51 }
52 },
53 Ambiguous {
54 @Override
55 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
56 return matches.getAmbiguouslyMatchedSourceClasses();
57 }
58 };
59
60 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
61 JRadioButton button = new JRadioButton(name(), this == getDefault());
62 button.setActionCommand(name());
63 button.addActionListener(listener);
64 group.add(button);
65 return button;
66 }
67
68 public abstract Collection<ClassEntry> getSourceClasses(ClassMatches matches);
69
70 public static SourceType getDefault() {
71 return values()[0];
72 }
73 }
74
75 public interface SaveListener {
76 void save(ClassMatches matches);
77 }
78
79 // controls
80 private JFrame m_frame;
81 private ClassSelector m_sourceClasses;
82 private ClassSelector m_destClasses;
83 private CodeReader m_sourceReader;
84 private CodeReader m_destReader;
85 private JLabel m_sourceClassLabel;
86 private JLabel m_destClassLabel;
87 private JButton m_matchButton;
88 private Map<SourceType, JRadioButton> m_sourceTypeButtons;
89 private JCheckBox m_advanceCheck;
90 private JCheckBox m_top10Matches;
91
92 private ClassMatches m_classMatches;
93 private Deobfuscator m_sourceDeobfuscator;
94 private Deobfuscator m_destDeobfuscator;
95 private ClassEntry m_sourceClass;
96 private ClassEntry m_destClass;
97 private SourceType m_sourceType;
98 private SaveListener m_saveListener;
99
100 public ClassMatchingGui(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
101
102 m_classMatches = matches;
103 m_sourceDeobfuscator = sourceDeobfuscator;
104 m_destDeobfuscator = destDeobfuscator;
105
106 // init frame
107 m_frame = new JFrame(Constants.Name + " - Class Matcher");
108 final Container pane = m_frame.getContentPane();
109 pane.setLayout(new BorderLayout());
110
111 // init source side
112 JPanel sourcePanel = new JPanel();
113 sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS));
114 sourcePanel.setPreferredSize(new Dimension(200, 0));
115 pane.add(sourcePanel, BorderLayout.WEST);
116 sourcePanel.add(new JLabel("Source Classes"));
117
118 // init source type radios
119 JPanel sourceTypePanel = new JPanel();
120 sourcePanel.add(sourceTypePanel);
121 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
122 ActionListener sourceTypeListener = event -> setSourceType(SourceType.valueOf(event.getActionCommand()));
123 ButtonGroup sourceTypeButtons = new ButtonGroup();
124 m_sourceTypeButtons = Maps.newHashMap();
125 for (SourceType sourceType : SourceType.values()) {
126 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
127 m_sourceTypeButtons.put(sourceType, button);
128 sourceTypePanel.add(button);
129 }
130
131 m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
132 m_sourceClasses.setListener(classEntry -> setSourceClass(classEntry));
133 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
134 sourcePanel.add(sourceScroller);
135
136 // init dest side
137 JPanel destPanel = new JPanel();
138 destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS));
139 destPanel.setPreferredSize(new Dimension(200, 0));
140 pane.add(destPanel, BorderLayout.WEST);
141 destPanel.add(new JLabel("Destination Classes"));
142
143 m_top10Matches = new JCheckBox("Show only top 10 matches");
144 destPanel.add(m_top10Matches);
145 m_top10Matches.addActionListener(event -> toggleTop10Matches());
146
147 m_destClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
148 m_destClasses.setListener(this::setDestClass);
149 JScrollPane destScroller = new JScrollPane(m_destClasses);
150 destPanel.add(destScroller);
151
152 JButton autoMatchButton = new JButton("AutoMatch");
153 autoMatchButton.addActionListener(event -> autoMatch());
154 destPanel.add(autoMatchButton);
155
156 // init source panels
157 DefaultSyntaxKit.initKit();
158 m_sourceReader = new CodeReader();
159 m_destReader = new CodeReader();
160
161 // init all the splits
162 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(m_sourceReader));
163 splitLeft.setResizeWeight(0); // let the right side take all the slack
164 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_destReader), destPanel);
165 splitRight.setResizeWeight(1); // let the left side take all the slack
166 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight);
167 splitCenter.setResizeWeight(0.5); // resize 50:50
168 pane.add(splitCenter, BorderLayout.CENTER);
169 splitCenter.resetToPreferredSizes();
170
171 // init bottom panel
172 JPanel bottomPanel = new JPanel();
173 bottomPanel.setLayout(new FlowLayout());
174
175 m_sourceClassLabel = new JLabel();
176 m_sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT);
177 m_destClassLabel = new JLabel();
178 m_destClassLabel.setHorizontalAlignment(SwingConstants.LEFT);
179
180 m_matchButton = new JButton();
181
182 m_advanceCheck = new JCheckBox("Advance to next likely match");
183 m_advanceCheck.addActionListener(event -> {
184 if (m_advanceCheck.isSelected()) {
185 advance();
186 }
187 });
188
189 bottomPanel.add(m_sourceClassLabel);
190 bottomPanel.add(m_matchButton);
191 bottomPanel.add(m_destClassLabel);
192 bottomPanel.add(m_advanceCheck);
193 pane.add(bottomPanel, BorderLayout.SOUTH);
194
195 // show the frame
196 pane.doLayout();
197 m_frame.setSize(1024, 576);
198 m_frame.setMinimumSize(new Dimension(640, 480));
199 m_frame.setVisible(true);
200 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
201
202 // init state
203 updateDestMappings();
204 setSourceType(SourceType.getDefault());
205 updateMatchButton();
206 m_saveListener = null;
207 }
208
209 public void setSaveListener(SaveListener val) {
210 m_saveListener = val;
211 }
212
213 private void updateDestMappings() {
214
215 Mappings newMappings = MappingsConverter.newMappings(
216 m_classMatches,
217 m_sourceDeobfuscator.getMappings(),
218 m_sourceDeobfuscator,
219 m_destDeobfuscator
220 );
221
222 // look for dropped mappings
223 MappingsChecker checker = new MappingsChecker(m_destDeobfuscator.getJarIndex());
224 checker.dropBrokenMappings(newMappings);
225
226 // count them
227 int numDroppedFields = checker.getDroppedFieldMappings().size();
228 int numDroppedMethods = checker.getDroppedMethodMappings().size();
229 System.out.println(String.format(
230 "%d mappings from matched classes don't match the dest jar:\n\t%5d fields\n\t%5d methods",
231 numDroppedFields + numDroppedMethods,
232 numDroppedFields,
233 numDroppedMethods
234 ));
235
236 m_destDeobfuscator.setMappings(newMappings);
237 }
238
239 protected void setSourceType(SourceType val) {
240
241 // show the source classes
242 m_sourceType = val;
243 m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_classMatches), m_sourceDeobfuscator));
244
245 // update counts
246 for (SourceType sourceType : SourceType.values()) {
247 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
248 sourceType.name(),
249 sourceType.getSourceClasses(m_classMatches).size()
250 ));
251 }
252 }
253
254 private Collection<ClassEntry> deobfuscateClasses(Collection<ClassEntry> in, Deobfuscator deobfuscator) {
255 List<ClassEntry> out = Lists.newArrayList();
256 for (ClassEntry entry : in) {
257
258 ClassEntry deobf = deobfuscator.deobfuscateEntry(entry);
259
260 // make sure we preserve any scores
261 if (entry instanceof ScoredClassEntry) {
262 deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry) entry).getScore());
263 }
264
265 out.add(deobf);
266 }
267 return out;
268 }
269
270 protected void setSourceClass(ClassEntry classEntry) {
271
272 Runnable onGetDestClasses = null;
273 if (m_advanceCheck.isSelected()) {
274 onGetDestClasses = this::pickBestDestClass;
275 }
276
277 setSourceClass(classEntry, onGetDestClasses);
278 }
279
280 protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) {
281
282 // update the current source class
283 m_sourceClass = classEntry;
284 m_sourceClassLabel.setText(m_sourceClass != null ? m_sourceClass.getName() : "");
285
286 if (m_sourceClass != null) {
287
288 // show the dest class(es)
289 ClassMatch match = m_classMatches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass));
290 assert (match != null);
291 if (match.destClasses.isEmpty()) {
292
293 m_destClasses.setClasses(null);
294
295 // run in a separate thread to keep ui responsive
296 new Thread() {
297 @Override
298 public void run() {
299 m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator));
300 m_destClasses.expandAll();
301
302 if (onGetDestClasses != null) {
303 onGetDestClasses.run();
304 }
305 }
306 }.start();
307
308 } else {
309
310 m_destClasses.setClasses(deobfuscateClasses(match.destClasses, m_destDeobfuscator));
311 m_destClasses.expandAll();
312
313 if (onGetDestClasses != null) {
314 onGetDestClasses.run();
315 }
316 }
317 }
318
319 setDestClass(null);
320 m_sourceReader.decompileClass(m_sourceClass, m_sourceDeobfuscator, () -> m_sourceReader.navigateToClassDeclaration(m_sourceClass));
321
322 updateMatchButton();
323 }
324
325 private Collection<ClassEntry> getLikelyMatches(ClassEntry sourceClass) {
326
327 ClassEntry obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
328
329 // set up identifiers
330 ClassNamer namer = new ClassNamer(m_classMatches.getUniqueMatches());
331 ClassIdentifier sourceIdentifier = new ClassIdentifier(
332 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
333 namer.getSourceNamer(), true
334 );
335 ClassIdentifier destIdentifier = new ClassIdentifier(
336 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
337 namer.getDestNamer(), true
338 );
339
340 try {
341
342 // rank all the unmatched dest classes against the source class
343 ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass);
344 List<ClassEntry> scoredDestClasses = Lists.newArrayList();
345 for (ClassEntry unmatchedDestClass : m_classMatches.getUnmatchedDestClasses()) {
346 ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass);
347 float score = 100.0f * (sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity))
348 / (sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore());
349 scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score));
350 }
351
352 if (m_top10Matches.isSelected() && scoredDestClasses.size() > 10) {
353 Collections.sort(scoredDestClasses, (a, b) -> {
354 ScoredClassEntry sa = (ScoredClassEntry) a;
355 ScoredClassEntry sb = (ScoredClassEntry) b;
356 return -Float.compare(sa.getScore(), sb.getScore());
357 });
358 scoredDestClasses = scoredDestClasses.subList(0, 10);
359 }
360
361 return scoredDestClasses;
362
363 } catch (ClassNotFoundException ex) {
364 throw new Error("Unable to find class " + ex.getMessage());
365 }
366 }
367
368 protected void setDestClass(ClassEntry classEntry) {
369
370 // update the current source class
371 m_destClass = classEntry;
372 m_destClassLabel.setText(m_destClass != null ? m_destClass.getName() : "");
373
374 m_destReader.decompileClass(m_destClass, m_destDeobfuscator, () -> m_destReader.navigateToClassDeclaration(m_destClass));
375
376 updateMatchButton();
377 }
378
379 private void updateMatchButton() {
380
381 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
382 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
383
384 BiMap<ClassEntry, ClassEntry> uniqueMatches = m_classMatches.getUniqueMatches();
385 boolean twoSelected = m_sourceClass != null && m_destClass != null;
386 boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest);
387 boolean canMatch = !uniqueMatches.containsKey(obfSource) && !uniqueMatches.containsValue(obfDest);
388
389 GuiTricks.deactivateButton(m_matchButton);
390 if (twoSelected) {
391 if (isMatched) {
392 GuiTricks.activateButton(m_matchButton, "Unmatch", event -> onUnmatchClick());
393 } else if (canMatch) {
394 GuiTricks.activateButton(m_matchButton, "Match", event -> onMatchClick());
395 }
396 }
397 }
398
399 private void onMatchClick() {
400 // precondition: source and dest classes are set correctly
401
402 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
403 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
404
405 // remove the classes from their match
406 m_classMatches.removeSource(obfSource);
407 m_classMatches.removeDest(obfDest);
408
409 // add them as matched classes
410 m_classMatches.add(new ClassMatch(obfSource, obfDest));
411
412 ClassEntry nextClass = null;
413 if (m_advanceCheck.isSelected()) {
414 nextClass = m_sourceClasses.getNextClass(m_sourceClass);
415 }
416
417 save();
418 updateMatches();
419
420 if (nextClass != null) {
421 advance(nextClass);
422 }
423 }
424
425 private void onUnmatchClick() {
426 // precondition: source and dest classes are set to a unique match
427
428 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
429
430 // remove the source to break the match, then add the source back as unmatched
431 m_classMatches.removeSource(obfSource);
432 m_classMatches.add(new ClassMatch(obfSource, null));
433
434 save();
435 updateMatches();
436 }
437
438 private void updateMatches() {
439 updateDestMappings();
440 setDestClass(null);
441 m_destClasses.setClasses(null);
442 updateMatchButton();
443
444 // remember where we were in the source tree
445 String packageName = m_sourceClasses.getSelectedPackage();
446
447 setSourceType(m_sourceType);
448
449 m_sourceClasses.expandPackage(packageName);
450 }
451
452 private void save() {
453 if (m_saveListener != null) {
454 m_saveListener.save(m_classMatches);
455 }
456 }
457
458 private void autoMatch() {
459
460 System.out.println("Automatching...");
461
462 // compute a new matching
463 ClassMatching matching = MappingsConverter.computeMatching(
464 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
465 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
466 m_classMatches.getUniqueMatches()
467 );
468 ClassMatches newMatches = new ClassMatches(matching.matches());
469 System.out.println(String.format("Automatch found %d new matches",
470 newMatches.getUniqueMatches().size() - m_classMatches.getUniqueMatches().size()
471 ));
472
473 // update the current matches
474 m_classMatches = newMatches;
475 save();
476 updateMatches();
477 }
478
479 private void advance() {
480 advance(null);
481 }
482
483 private void advance(ClassEntry sourceClass) {
484
485 // make sure we have a source class
486 if (sourceClass == null) {
487 sourceClass = m_sourceClasses.getSelectedClass();
488 if (sourceClass != null) {
489 sourceClass = m_sourceClasses.getNextClass(sourceClass);
490 } else {
491 sourceClass = m_sourceClasses.getFirstClass();
492 }
493 }
494
495 // set the source class
496 setSourceClass(sourceClass, this::pickBestDestClass);
497 m_sourceClasses.setSelectionClass(sourceClass);
498 }
499
500 private void pickBestDestClass() {
501
502 // then, pick the best dest class
503 ClassEntry firstClass = null;
504 ScoredClassEntry bestDestClass = null;
505 for (ClassSelectorPackageNode packageNode : m_destClasses.packageNodes()) {
506 for (ClassSelectorClassNode classNode : m_destClasses.classNodes(packageNode)) {
507 if (firstClass == null) {
508 firstClass = classNode.getClassEntry();
509 }
510 if (classNode.getClassEntry() instanceof ScoredClassEntry) {
511 ScoredClassEntry scoredClass = (ScoredClassEntry) classNode.getClassEntry();
512 if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) {
513 bestDestClass = scoredClass;
514 }
515 }
516 }
517 }
518
519 // pick the entry to show
520 ClassEntry destClass = null;
521 if (bestDestClass != null) {
522 destClass = bestDestClass;
523 } else if (firstClass != null) {
524 destClass = firstClass;
525 }
526
527 setDestClass(destClass);
528 m_destClasses.setSelectionClass(destClass);
529 }
530
531 private void toggleTop10Matches() {
532 if (m_sourceClass != null) {
533 m_destClasses.clearSelection();
534 m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator));
535 m_destClasses.expandAll();
536 }
537 }
538}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
new file mode 100644
index 00000000..0c93c439
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
@@ -0,0 +1,279 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import com.google.common.collect.ArrayListMultimap;
14import com.google.common.collect.Lists;
15import com.google.common.collect.Maps;
16import com.google.common.collect.Multimap;
17
18import java.awt.event.MouseAdapter;
19import java.awt.event.MouseEvent;
20import java.util.*;
21
22import javax.swing.JTree;
23import javax.swing.tree.DefaultMutableTreeNode;
24import javax.swing.tree.DefaultTreeModel;
25import javax.swing.tree.TreePath;
26
27import cuchaz.enigma.mapping.ClassEntry;
28
29public class ClassSelector extends JTree {
30
31 private static final long serialVersionUID = -7632046902384775977L;
32
33 public interface ClassSelectionListener {
34 void onSelectClass(ClassEntry classEntry);
35 }
36
37 public static Comparator<ClassEntry> ObfuscatedClassEntryComparator;
38 public static Comparator<ClassEntry> DeobfuscatedClassEntryComparator;
39
40 static {
41 ObfuscatedClassEntryComparator = (a, b) -> {
42 String aname = a.getName();
43 String bname = a.getName();
44 if (aname.length() != bname.length()) {
45 return aname.length() - bname.length();
46 }
47 return aname.compareTo(bname);
48 };
49
50 DeobfuscatedClassEntryComparator = (a, b) -> {
51 if (a instanceof ScoredClassEntry && b instanceof ScoredClassEntry) {
52 return Float.compare(
53 ((ScoredClassEntry) b).getScore(),
54 ((ScoredClassEntry) a).getScore()
55 );
56 }
57 return a.getName().compareTo(b.getName());
58 };
59 }
60
61 private ClassSelectionListener m_listener;
62 private Comparator<ClassEntry> m_comparator;
63
64 public ClassSelector(Comparator<ClassEntry> comparator) {
65 m_comparator = comparator;
66
67 // configure the tree control
68 setRootVisible(false);
69 setShowsRootHandles(false);
70 setModel(null);
71
72 // hook events
73 addMouseListener(new MouseAdapter() {
74 @Override
75 public void mouseClicked(MouseEvent event) {
76 if (m_listener != null && event.getClickCount() == 2) {
77 // get the selected node
78 TreePath path = getSelectionPath();
79 if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) {
80 ClassSelectorClassNode node = (ClassSelectorClassNode) path.getLastPathComponent();
81 m_listener.onSelectClass(node.getClassEntry());
82 }
83 }
84 }
85 });
86
87 // init defaults
88 m_listener = null;
89 }
90
91 public void setListener(ClassSelectionListener val) {
92 m_listener = val;
93 }
94
95 public void setClasses(Collection<ClassEntry> classEntries) {
96 if (classEntries == null) {
97 setModel(null);
98 return;
99 }
100
101 // build the package names
102 Map<String, ClassSelectorPackageNode> packages = Maps.newHashMap();
103 for (ClassEntry classEntry : classEntries) {
104 packages.put(classEntry.getPackageName(), null);
105 }
106
107 // sort the packages
108 List<String> sortedPackageNames = Lists.newArrayList(packages.keySet());
109 Collections.sort(sortedPackageNames, (a, b) -> {
110 // I can never keep this rule straight when writing these damn things...
111 // a < b => -1, a == b => 0, a > b => +1
112
113 String[] aparts = a.split("/");
114 String[] bparts = b.split("/");
115 for (int i = 0; true; i++) {
116 if (i >= aparts.length) {
117 return -1;
118 } else if (i >= bparts.length) {
119 return 1;
120 }
121
122 int result = aparts[i].compareTo(bparts[i]);
123 if (result != 0) {
124 return result;
125 }
126 }
127 });
128
129 // create the root node and the package nodes
130 DefaultMutableTreeNode root = new DefaultMutableTreeNode();
131 for (String packageName : sortedPackageNames) {
132 ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName);
133 packages.put(packageName, node);
134 root.add(node);
135 }
136
137 // put the classes into packages
138 Multimap<String, ClassEntry> packagedClassEntries = ArrayListMultimap.create();
139 for (ClassEntry classEntry : classEntries) {
140 packagedClassEntries.put(classEntry.getPackageName(), classEntry);
141 }
142
143 // build the class nodes
144 for (String packageName : packagedClassEntries.keySet()) {
145 // sort the class entries
146 List<ClassEntry> classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName));
147 Collections.sort(classEntriesInPackage, m_comparator);
148
149 // create the nodes in order
150 for (ClassEntry classEntry : classEntriesInPackage) {
151 ClassSelectorPackageNode node = packages.get(packageName);
152 node.add(new ClassSelectorClassNode(classEntry));
153 }
154 }
155
156 // finally, update the tree control
157 setModel(new DefaultTreeModel(root));
158 }
159
160 public ClassEntry getSelectedClass() {
161 if (!isSelectionEmpty()) {
162 Object selectedNode = getSelectionPath().getLastPathComponent();
163 if (selectedNode instanceof ClassSelectorClassNode) {
164 ClassSelectorClassNode classNode = (ClassSelectorClassNode) selectedNode;
165 return classNode.getClassEntry();
166 }
167 }
168 return null;
169 }
170
171 public String getSelectedPackage() {
172 if (!isSelectionEmpty()) {
173 Object selectedNode = getSelectionPath().getLastPathComponent();
174 if (selectedNode instanceof ClassSelectorPackageNode) {
175 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode) selectedNode;
176 return packageNode.getPackageName();
177 } else if (selectedNode instanceof ClassSelectorClassNode) {
178 ClassSelectorClassNode classNode = (ClassSelectorClassNode) selectedNode;
179 return classNode.getClassEntry().getPackageName();
180 }
181 }
182 return null;
183 }
184
185 public Iterable<ClassSelectorPackageNode> packageNodes() {
186 List<ClassSelectorPackageNode> nodes = Lists.newArrayList();
187 DefaultMutableTreeNode root = (DefaultMutableTreeNode) getModel().getRoot();
188 Enumeration<?> children = root.children();
189 while (children.hasMoreElements()) {
190 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode) children.nextElement();
191 nodes.add(packageNode);
192 }
193 return nodes;
194 }
195
196 public Iterable<ClassSelectorClassNode> classNodes(ClassSelectorPackageNode packageNode) {
197 List<ClassSelectorClassNode> nodes = Lists.newArrayList();
198 Enumeration<?> children = packageNode.children();
199 while (children.hasMoreElements()) {
200 ClassSelectorClassNode classNode = (ClassSelectorClassNode) children.nextElement();
201 nodes.add(classNode);
202 }
203 return nodes;
204 }
205
206 public void expandPackage(String packageName) {
207 if (packageName == null) {
208 return;
209 }
210 for (ClassSelectorPackageNode packageNode : packageNodes()) {
211 if (packageNode.getPackageName().equals(packageName)) {
212 expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode}));
213 return;
214 }
215 }
216 }
217
218 public void expandAll() {
219 for (ClassSelectorPackageNode packageNode : packageNodes()) {
220 expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode}));
221 }
222 }
223
224 public ClassEntry getFirstClass() {
225 for (ClassSelectorPackageNode packageNode : packageNodes()) {
226 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
227 return classNode.getClassEntry();
228 }
229 }
230 return null;
231 }
232
233 public ClassSelectorPackageNode getPackageNode(ClassEntry entry) {
234 for (ClassSelectorPackageNode packageNode : packageNodes()) {
235 if (packageNode.getPackageName().equals(entry.getPackageName())) {
236 return packageNode;
237 }
238 }
239 return null;
240 }
241
242 public ClassEntry getNextClass(ClassEntry entry) {
243 boolean foundIt = false;
244 for (ClassSelectorPackageNode packageNode : packageNodes()) {
245 if (!foundIt) {
246 // skip to the package with our target in it
247 if (packageNode.getPackageName().equals(entry.getPackageName())) {
248 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
249 if (!foundIt) {
250 if (classNode.getClassEntry().equals(entry)) {
251 foundIt = true;
252 }
253 } else {
254 // return the next class
255 return classNode.getClassEntry();
256 }
257 }
258 }
259 } else {
260 // return the next class
261 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
262 return classNode.getClassEntry();
263 }
264 }
265 }
266 return null;
267 }
268
269 public void setSelectionClass(ClassEntry classEntry) {
270 expandPackage(classEntry.getPackageName());
271 for (ClassSelectorPackageNode packageNode : packageNodes()) {
272 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
273 if (classNode.getClassEntry().equals(classEntry)) {
274 setSelectionPath(new TreePath(new Object[]{getModel().getRoot(), packageNode, classNode}));
275 }
276 }
277 }
278 }
279}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelectorClassNode.java b/src/main/java/cuchaz/enigma/gui/ClassSelectorClassNode.java
new file mode 100644
index 00000000..6da9782f
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ClassSelectorClassNode.java
@@ -0,0 +1,50 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import javax.swing.tree.DefaultMutableTreeNode;
14
15import cuchaz.enigma.mapping.ClassEntry;
16
17public class ClassSelectorClassNode extends DefaultMutableTreeNode {
18
19 private static final long serialVersionUID = -8956754339813257380L;
20
21 private ClassEntry m_classEntry;
22
23 public ClassSelectorClassNode(ClassEntry classEntry) {
24 m_classEntry = classEntry;
25 }
26
27 public ClassEntry getClassEntry() {
28 return m_classEntry;
29 }
30
31 @Override
32 public String toString() {
33 if (m_classEntry instanceof ScoredClassEntry) {
34 return String.format("%d%% %s", (int) ((ScoredClassEntry) m_classEntry).getScore(), m_classEntry.getSimpleName());
35 }
36 return m_classEntry.getSimpleName();
37 }
38
39 @Override
40 public boolean equals(Object other) {
41 if (other instanceof ClassSelectorClassNode) {
42 return equals((ClassSelectorClassNode) other);
43 }
44 return false;
45 }
46
47 public boolean equals(ClassSelectorClassNode other) {
48 return m_classEntry.equals(other.m_classEntry);
49 }
50}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelectorPackageNode.java b/src/main/java/cuchaz/enigma/gui/ClassSelectorPackageNode.java
new file mode 100644
index 00000000..36220423
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ClassSelectorPackageNode.java
@@ -0,0 +1,45 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import javax.swing.tree.DefaultMutableTreeNode;
14
15public class ClassSelectorPackageNode extends DefaultMutableTreeNode {
16
17 private static final long serialVersionUID = -3730868701219548043L;
18
19 private String m_packageName;
20
21 public ClassSelectorPackageNode(String packageName) {
22 m_packageName = packageName;
23 }
24
25 public String getPackageName() {
26 return m_packageName;
27 }
28
29 @Override
30 public String toString() {
31 return m_packageName;
32 }
33
34 @Override
35 public boolean equals(Object other) {
36 if (other instanceof ClassSelectorPackageNode) {
37 return equals((ClassSelectorPackageNode) other);
38 }
39 return false;
40 }
41
42 public boolean equals(ClassSelectorPackageNode other) {
43 return m_packageName.equals(other.m_packageName);
44 }
45}
diff --git a/src/main/java/cuchaz/enigma/gui/CodeReader.java b/src/main/java/cuchaz/enigma/gui/CodeReader.java
new file mode 100644
index 00000000..93f9a75b
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/CodeReader.java
@@ -0,0 +1,222 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import com.strobel.decompiler.languages.java.ast.CompilationUnit;
14
15import java.awt.Rectangle;
16import java.awt.event.ActionEvent;
17import java.awt.event.ActionListener;
18
19import javax.swing.JEditorPane;
20import javax.swing.SwingUtilities;
21import javax.swing.Timer;
22import javax.swing.event.CaretEvent;
23import javax.swing.event.CaretListener;
24import javax.swing.text.BadLocationException;
25import javax.swing.text.Highlighter.HighlightPainter;
26
27import cuchaz.enigma.Deobfuscator;
28import cuchaz.enigma.analysis.EntryReference;
29import cuchaz.enigma.analysis.SourceIndex;
30import cuchaz.enigma.analysis.Token;
31import cuchaz.enigma.mapping.ClassEntry;
32import cuchaz.enigma.mapping.Entry;
33import de.sciss.syntaxpane.DefaultSyntaxKit;
34
35
36public class CodeReader extends JEditorPane {
37
38 private static final long serialVersionUID = 3673180950485748810L;
39
40 private static final Object m_lock = new Object();
41
42 public interface SelectionListener {
43 void onSelect(EntryReference<Entry, Entry> reference);
44 }
45
46 private SelectionHighlightPainter m_selectionHighlightPainter;
47 private SourceIndex m_sourceIndex;
48 private SelectionListener m_selectionListener;
49
50 public CodeReader() {
51
52 setEditable(false);
53 setContentType("text/java");
54
55 // turn off token highlighting (it's wrong most of the time anyway...)
56 DefaultSyntaxKit kit = (DefaultSyntaxKit) getEditorKit();
57 kit.toggleComponent(this, "de.sciss.syntaxpane.components.TokenMarker");
58
59 // hook events
60 addCaretListener(new CaretListener() {
61 @Override
62 public void caretUpdate(CaretEvent event) {
63 if (m_selectionListener != null && m_sourceIndex != null) {
64 Token token = m_sourceIndex.getReferenceToken(event.getDot());
65 if (token != null) {
66 m_selectionListener.onSelect(m_sourceIndex.getDeobfReference(token));
67 } else {
68 m_selectionListener.onSelect(null);
69 }
70 }
71 }
72 });
73
74 m_selectionHighlightPainter = new SelectionHighlightPainter();
75 m_sourceIndex = null;
76 m_selectionListener = null;
77 }
78
79 public void setSelectionListener(SelectionListener val) {
80 m_selectionListener = val;
81 }
82
83 public void setCode(String code) {
84 // sadly, the java lexer is not thread safe, so we have to serialize all these calls
85 synchronized (m_lock) {
86 setText(code);
87 }
88 }
89
90 public SourceIndex getSourceIndex() {
91 return m_sourceIndex;
92 }
93
94 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) {
95 decompileClass(classEntry, deobfuscator, null);
96 }
97
98 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator, Runnable callback) {
99 decompileClass(classEntry, deobfuscator, null, callback);
100 }
101
102 public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Boolean ignoreBadTokens, final Runnable callback) {
103
104 if (classEntry == null) {
105 setCode(null);
106 return;
107 }
108
109 setCode("(decompiling...)");
110
111 // run decompilation in a separate thread to keep ui responsive
112 new Thread() {
113 @Override
114 public void run() {
115
116 // decompile it
117 CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClassName());
118 String source = deobfuscator.getSource(sourceTree);
119 setCode(source);
120 m_sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens);
121
122 if (callback != null) {
123 callback.run();
124 }
125 }
126 }.start();
127 }
128
129 public void navigateToClassDeclaration(ClassEntry classEntry) {
130
131 // navigate to the class declaration
132 Token token = m_sourceIndex.getDeclarationToken(classEntry);
133 if (token == null) {
134 // couldn't find the class declaration token, might be an anonymous class
135 // look for any declaration in that class instead
136 for (Entry entry : m_sourceIndex.declarations()) {
137 if (entry.getClassEntry().equals(classEntry)) {
138 token = m_sourceIndex.getDeclarationToken(entry);
139 break;
140 }
141 }
142 }
143
144 if (token != null) {
145 navigateToToken(token);
146 } else {
147 // couldn't find anything =(
148 System.out.println("Unable to find declaration in source for " + classEntry);
149 }
150 }
151
152 public void navigateToToken(final Token token) {
153 navigateToToken(this, token, m_selectionHighlightPainter);
154 }
155
156 // HACKHACK: someday we can update the main GUI to use this code reader
157 public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) {
158
159 // set the caret position to the token
160 editor.setCaretPosition(token.start);
161 editor.grabFocus();
162
163 try {
164 // make sure the token is visible in the scroll window
165 Rectangle start = editor.modelToView(token.start);
166 Rectangle end = editor.modelToView(token.end);
167 final Rectangle show = start.union(end);
168 show.grow(start.width * 10, start.height * 6);
169 SwingUtilities.invokeLater(new Runnable() {
170 @Override
171 public void run() {
172 editor.scrollRectToVisible(show);
173 }
174 });
175 } catch (BadLocationException ex) {
176 throw new Error(ex);
177 }
178
179 // highlight the token momentarily
180 final Timer timer = new Timer(200, new ActionListener() {
181 private int m_counter = 0;
182 private Object m_highlight = null;
183
184 @Override
185 public void actionPerformed(ActionEvent event) {
186 if (m_counter % 2 == 0) {
187 try {
188 m_highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter);
189 } catch (BadLocationException ex) {
190 // don't care
191 }
192 } else if (m_highlight != null) {
193 editor.getHighlighter().removeHighlight(m_highlight);
194 }
195
196 if (m_counter++ > 6) {
197 Timer timer = (Timer) event.getSource();
198 timer.stop();
199 }
200 }
201 });
202 timer.start();
203 }
204
205 public void setHighlightedTokens(Iterable<Token> tokens, HighlightPainter painter) {
206 for (Token token : tokens) {
207 setHighlightedToken(token, painter);
208 }
209 }
210
211 public void setHighlightedToken(Token token, HighlightPainter painter) {
212 try {
213 getHighlighter().addHighlight(token.start, token.end, painter);
214 } catch (BadLocationException ex) {
215 throw new IllegalArgumentException(ex);
216 }
217 }
218
219 public void clearHighlights() {
220 getHighlighter().removeAllHighlights();
221 }
222}
diff --git a/src/main/java/cuchaz/enigma/gui/CrashDialog.java b/src/main/java/cuchaz/enigma/gui/CrashDialog.java
new file mode 100644
index 00000000..c0c0869b
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/CrashDialog.java
@@ -0,0 +1,94 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.FlowLayout;
16import java.awt.event.ActionEvent;
17import java.awt.event.ActionListener;
18import java.io.PrintWriter;
19import java.io.StringWriter;
20
21import javax.swing.*;
22
23import cuchaz.enigma.Constants;
24
25public class CrashDialog {
26
27 private static CrashDialog m_instance = null;
28
29 private JFrame m_frame;
30 private JTextArea m_text;
31
32 private CrashDialog(JFrame parent) {
33 // init frame
34 m_frame = new JFrame(Constants.Name + " - Crash Report");
35 final Container pane = m_frame.getContentPane();
36 pane.setLayout(new BorderLayout());
37
38 JLabel label = new JLabel(Constants.Name + " has crashed! =(");
39 label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
40 pane.add(label, BorderLayout.NORTH);
41
42 // report panel
43 m_text = new JTextArea();
44 m_text.setTabSize(2);
45 pane.add(new JScrollPane(m_text), BorderLayout.CENTER);
46
47 // buttons panel
48 JPanel buttonsPanel = new JPanel();
49 FlowLayout buttonsLayout = new FlowLayout();
50 buttonsLayout.setAlignment(FlowLayout.RIGHT);
51 buttonsPanel.setLayout(buttonsLayout);
52 buttonsPanel.add(GuiTricks.unboldLabel(new JLabel("If you choose exit, you will lose any unsaved work.")));
53 JButton ignoreButton = new JButton("Ignore");
54 ignoreButton.addActionListener(new ActionListener() {
55 @Override
56 public void actionPerformed(ActionEvent event) {
57 // close (hide) the dialog
58 m_frame.setVisible(false);
59 }
60 });
61 buttonsPanel.add(ignoreButton);
62 JButton exitButton = new JButton("Exit");
63 exitButton.addActionListener(new ActionListener() {
64 @Override
65 public void actionPerformed(ActionEvent event) {
66 // exit enigma
67 System.exit(1);
68 }
69 });
70 buttonsPanel.add(exitButton);
71 pane.add(buttonsPanel, BorderLayout.SOUTH);
72
73 // show the frame
74 m_frame.setSize(600, 400);
75 m_frame.setLocationRelativeTo(parent);
76 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
77 }
78
79 public static void init(JFrame parent) {
80 m_instance = new CrashDialog(parent);
81 }
82
83 public static void show(Throwable ex) {
84 // get the error report
85 StringWriter buf = new StringWriter();
86 ex.printStackTrace(new PrintWriter(buf));
87 String report = buf.toString();
88
89 // show it!
90 m_instance.m_text.setText(report);
91 m_instance.m_frame.doLayout();
92 m_instance.m_frame.setVisible(true);
93 }
94}
diff --git a/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java
index 57210a84..d92bb0d2 100644
--- a/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java
+++ b/src/main/java/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java
@@ -4,18 +4,18 @@
4 * are made available under the terms of the GNU Lesser General Public 4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at 5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html 6 * http://www.gnu.org/licenses/lgpl.html
7 * 7 * <p>
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.gui; 11package cuchaz.enigma.gui;
12 12
13import java.awt.Color; 13import java.awt.Color;
14 14
15public class DeobfuscatedHighlightPainter extends BoxHighlightPainter { 15public class DeobfuscatedHighlightPainter extends BoxHighlightPainter {
16 16
17 public DeobfuscatedHighlightPainter() { 17 public DeobfuscatedHighlightPainter() {
18 // green ish 18 // green ish
19 super(new Color(220, 255, 220), new Color(80, 160, 80)); 19 super(new Color(220, 255, 220), new Color(80, 160, 80));
20 } 20 }
21} 21}
diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java
new file mode 100644
index 00000000..eb26ddd1
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/Gui.java
@@ -0,0 +1,1100 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import com.google.common.collect.Lists;
14
15import java.awt.*;
16import java.awt.event.*;
17import java.io.File;
18import java.io.IOException;
19import java.lang.Thread.UncaughtExceptionHandler;
20import java.util.Collection;
21import java.util.Collections;
22import java.util.List;
23import java.util.Vector;
24import java.util.jar.JarFile;
25
26import javax.swing.*;
27import javax.swing.event.CaretEvent;
28import javax.swing.event.CaretListener;
29import javax.swing.text.BadLocationException;
30import javax.swing.text.Highlighter;
31import javax.swing.tree.DefaultTreeModel;
32import javax.swing.tree.TreeNode;
33import javax.swing.tree.TreePath;
34
35import cuchaz.enigma.Constants;
36import cuchaz.enigma.ExceptionIgnorer;
37import cuchaz.enigma.analysis.*;
38import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
39import cuchaz.enigma.mapping.*;
40import de.sciss.syntaxpane.DefaultSyntaxKit;
41
42public class Gui {
43
44 private GuiController m_controller;
45
46 // controls
47 private JFrame m_frame;
48 private ClassSelector m_obfClasses;
49 private ClassSelector m_deobfClasses;
50 private JEditorPane m_editor;
51 private JPanel m_classesPanel;
52 private JSplitPane m_splitClasses;
53 private JPanel m_infoPanel;
54 private ObfuscatedHighlightPainter m_obfuscatedHighlightPainter;
55 private DeobfuscatedHighlightPainter m_deobfuscatedHighlightPainter;
56 private OtherHighlightPainter m_otherHighlightPainter;
57 private SelectionHighlightPainter m_selectionHighlightPainter;
58 private JTree m_inheritanceTree;
59 private JTree m_implementationsTree;
60 private JTree m_callsTree;
61 private JList<Token> m_tokens;
62 private JTabbedPane m_tabs;
63
64 // dynamic menu items
65 private JMenuItem m_closeJarMenu;
66 private JMenuItem m_openMappingsMenu;
67 private JMenuItem m_openOldMappingsMenu;
68 private JMenuItem m_saveMappingsMenu;
69 private JMenuItem m_saveMappingsAsMenu;
70 private JMenuItem m_closeMappingsMenu;
71 private JMenuItem m_renameMenu;
72 private JMenuItem m_showInheritanceMenu;
73 private JMenuItem m_openEntryMenu;
74 private JMenuItem m_openPreviousMenu;
75 private JMenuItem m_showCallsMenu;
76 private JMenuItem m_showImplementationsMenu;
77 private JMenuItem m_toggleMappingMenu;
78 private JMenuItem m_exportSourceMenu;
79 private JMenuItem m_exportJarMenu;
80
81 // state
82 private EntryReference<Entry, Entry> m_reference;
83 private JFileChooser m_jarFileChooser;
84 private JFileChooser m_mappingsFileChooser;
85 private JFileChooser m_oldMappingsFileChooser;
86
87 private JFileChooser m_exportSourceFileChooser;
88 private JFileChooser m_exportJarFileChooser;
89
90 public Gui() {
91
92 // init frame
93 m_frame = new JFrame(Constants.Name);
94 final Container pane = m_frame.getContentPane();
95 pane.setLayout(new BorderLayout());
96
97 if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) {
98 // install a global exception handler to the event thread
99 CrashDialog.init(m_frame);
100 Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
101 @Override
102 public void uncaughtException(Thread thread, Throwable t) {
103 t.printStackTrace(System.err);
104 if (!ExceptionIgnorer.shouldIgnore(t)) {
105 CrashDialog.show(t);
106 }
107 }
108 });
109 }
110
111 m_controller = new GuiController(this);
112
113 // init file choosers
114 m_jarFileChooser = new JFileChooser();
115 m_mappingsFileChooser = new JFileChooser();
116 m_mappingsFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
117 m_mappingsFileChooser.setAcceptAllFileFilterUsed(false);
118
119 m_oldMappingsFileChooser= new JFileChooser();
120 m_exportSourceFileChooser = new JFileChooser();
121 m_exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
122 m_exportJarFileChooser = new JFileChooser();
123
124 // init obfuscated classes list
125 m_obfClasses = new ClassSelector(ClassSelector.ObfuscatedClassEntryComparator);
126 m_obfClasses.setListener(new ClassSelectionListener() {
127 @Override
128 public void onSelectClass(ClassEntry classEntry) {
129 navigateTo(classEntry);
130 }
131 });
132 JScrollPane obfScroller = new JScrollPane(m_obfClasses);
133 JPanel obfPanel = new JPanel();
134 obfPanel.setLayout(new BorderLayout());
135 obfPanel.add(new JLabel("Obfuscated Classes"), BorderLayout.NORTH);
136 obfPanel.add(obfScroller, BorderLayout.CENTER);
137
138 // init deobfuscated classes list
139 m_deobfClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
140 m_deobfClasses.setListener(new ClassSelectionListener() {
141 @Override
142 public void onSelectClass(ClassEntry classEntry) {
143 navigateTo(classEntry);
144 }
145 });
146 JScrollPane deobfScroller = new JScrollPane(m_deobfClasses);
147 JPanel deobfPanel = new JPanel();
148 deobfPanel.setLayout(new BorderLayout());
149 deobfPanel.add(new JLabel("De-obfuscated Classes"), BorderLayout.NORTH);
150 deobfPanel.add(deobfScroller, BorderLayout.CENTER);
151
152 // set up classes panel (don't add the splitter yet)
153 m_splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, obfPanel, deobfPanel);
154 m_splitClasses.setResizeWeight(0.3);
155 m_classesPanel = new JPanel();
156 m_classesPanel.setLayout(new BorderLayout());
157 m_classesPanel.setPreferredSize(new Dimension(250, 0));
158
159 // init info panel
160 m_infoPanel = new JPanel();
161 m_infoPanel.setLayout(new GridLayout(4, 1, 0, 0));
162 m_infoPanel.setPreferredSize(new Dimension(0, 100));
163 m_infoPanel.setBorder(BorderFactory.createTitledBorder("Identifier Info"));
164 clearReference();
165
166 // init editor
167 DefaultSyntaxKit.initKit();
168 m_obfuscatedHighlightPainter = new ObfuscatedHighlightPainter();
169 m_deobfuscatedHighlightPainter = new DeobfuscatedHighlightPainter();
170 m_otherHighlightPainter = new OtherHighlightPainter();
171 m_selectionHighlightPainter = new SelectionHighlightPainter();
172 m_editor = new JEditorPane();
173 m_editor.setEditable(false);
174 m_editor.setCaret(new BrowserCaret());
175 JScrollPane sourceScroller = new JScrollPane(m_editor);
176 m_editor.setContentType("text/java");
177 m_editor.addCaretListener(new CaretListener() {
178 @Override
179 public void caretUpdate(CaretEvent event) {
180 onCaretMove(event.getDot());
181 }
182 });
183 m_editor.addKeyListener(new KeyAdapter() {
184 @Override
185 public void keyPressed(KeyEvent event) {
186 switch (event.getKeyCode()) {
187 case KeyEvent.VK_R:
188 m_renameMenu.doClick();
189 break;
190
191 case KeyEvent.VK_I:
192 m_showInheritanceMenu.doClick();
193 break;
194
195 case KeyEvent.VK_M:
196 m_showImplementationsMenu.doClick();
197 break;
198
199 case KeyEvent.VK_N:
200 m_openEntryMenu.doClick();
201 break;
202
203 case KeyEvent.VK_P:
204 m_openPreviousMenu.doClick();
205 break;
206
207 case KeyEvent.VK_C:
208 m_showCallsMenu.doClick();
209 break;
210
211 case KeyEvent.VK_T:
212 m_toggleMappingMenu.doClick();
213 break;
214 }
215 }
216 });
217
218 // turn off token highlighting (it's wrong most of the time anyway...)
219 DefaultSyntaxKit kit = (DefaultSyntaxKit) m_editor.getEditorKit();
220 kit.toggleComponent(m_editor, "de.sciss.syntaxpane.components.TokenMarker");
221
222 // init editor popup menu
223 JPopupMenu popupMenu = new JPopupMenu();
224 m_editor.setComponentPopupMenu(popupMenu);
225 {
226 JMenuItem menu = new JMenuItem("Rename");
227 menu.addActionListener(new ActionListener() {
228 @Override
229 public void actionPerformed(ActionEvent event) {
230 startRename();
231 }
232 });
233 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0));
234 menu.setEnabled(false);
235 popupMenu.add(menu);
236 m_renameMenu = menu;
237 }
238 {
239 JMenuItem menu = new JMenuItem("Show Inheritance");
240 menu.addActionListener(new ActionListener() {
241 @Override
242 public void actionPerformed(ActionEvent event) {
243 showInheritance();
244 }
245 });
246 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, 0));
247 menu.setEnabled(false);
248 popupMenu.add(menu);
249 m_showInheritanceMenu = menu;
250 }
251 {
252 JMenuItem menu = new JMenuItem("Show Implementations");
253 menu.addActionListener(new ActionListener() {
254 @Override
255 public void actionPerformed(ActionEvent event) {
256 showImplementations();
257 }
258 });
259 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0));
260 menu.setEnabled(false);
261 popupMenu.add(menu);
262 m_showImplementationsMenu = menu;
263 }
264 {
265 JMenuItem menu = new JMenuItem("Show Calls");
266 menu.addActionListener(new ActionListener() {
267 @Override
268 public void actionPerformed(ActionEvent event) {
269 showCalls();
270 }
271 });
272 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0));
273 menu.setEnabled(false);
274 popupMenu.add(menu);
275 m_showCallsMenu = menu;
276 }
277 {
278 JMenuItem menu = new JMenuItem("Go to Declaration");
279 menu.addActionListener(new ActionListener() {
280 @Override
281 public void actionPerformed(ActionEvent event) {
282 navigateTo(m_reference.entry);
283 }
284 });
285 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0));
286 menu.setEnabled(false);
287 popupMenu.add(menu);
288 m_openEntryMenu = menu;
289 }
290 {
291 JMenuItem menu = new JMenuItem("Go to previous");
292 menu.addActionListener(new ActionListener() {
293 @Override
294 public void actionPerformed(ActionEvent event) {
295 m_controller.openPreviousReference();
296 }
297 });
298 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0));
299 menu.setEnabled(false);
300 popupMenu.add(menu);
301 m_openPreviousMenu = menu;
302 }
303 {
304 JMenuItem menu = new JMenuItem("Mark as deobfuscated");
305 menu.addActionListener(new ActionListener() {
306 @Override
307 public void actionPerformed(ActionEvent event) {
308 toggleMapping();
309 }
310 });
311 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0));
312 menu.setEnabled(false);
313 popupMenu.add(menu);
314 m_toggleMappingMenu = menu;
315 }
316
317 // init inheritance panel
318 m_inheritanceTree = new JTree();
319 m_inheritanceTree.setModel(null);
320 m_inheritanceTree.addMouseListener(new MouseAdapter() {
321 @Override
322 public void mouseClicked(MouseEvent event) {
323 if (event.getClickCount() == 2) {
324 // get the selected node
325 TreePath path = m_inheritanceTree.getSelectionPath();
326 if (path == null) {
327 return;
328 }
329
330 Object node = path.getLastPathComponent();
331 if (node instanceof ClassInheritanceTreeNode) {
332 ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode) node;
333 navigateTo(new ClassEntry(classNode.getObfClassName()));
334 } else if (node instanceof MethodInheritanceTreeNode) {
335 MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode) node;
336 if (methodNode.isImplemented()) {
337 navigateTo(methodNode.getMethodEntry());
338 }
339 }
340 }
341 }
342 });
343 JPanel inheritancePanel = new JPanel();
344 inheritancePanel.setLayout(new BorderLayout());
345 inheritancePanel.add(new JScrollPane(m_inheritanceTree));
346
347 // init implementations panel
348 m_implementationsTree = new JTree();
349 m_implementationsTree.setModel(null);
350 m_implementationsTree.addMouseListener(new MouseAdapter() {
351 @Override
352 public void mouseClicked(MouseEvent event) {
353 if (event.getClickCount() == 2) {
354 // get the selected node
355 TreePath path = m_implementationsTree.getSelectionPath();
356 if (path == null) {
357 return;
358 }
359
360 Object node = path.getLastPathComponent();
361 if (node instanceof ClassImplementationsTreeNode) {
362 ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode) node;
363 navigateTo(classNode.getClassEntry());
364 } else if (node instanceof MethodImplementationsTreeNode) {
365 MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode) node;
366 navigateTo(methodNode.getMethodEntry());
367 }
368 }
369 }
370 });
371 JPanel implementationsPanel = new JPanel();
372 implementationsPanel.setLayout(new BorderLayout());
373 implementationsPanel.add(new JScrollPane(m_implementationsTree));
374
375 // init call panel
376 m_callsTree = new JTree();
377 m_callsTree.setModel(null);
378 m_callsTree.addMouseListener(new MouseAdapter() {
379 @SuppressWarnings("unchecked")
380 @Override
381 public void mouseClicked(MouseEvent event) {
382 if (event.getClickCount() == 2) {
383 // get the selected node
384 TreePath path = m_callsTree.getSelectionPath();
385 if (path == null) {
386 return;
387 }
388
389 Object node = path.getLastPathComponent();
390 if (node instanceof ReferenceTreeNode) {
391 ReferenceTreeNode<Entry, Entry> referenceNode = ((ReferenceTreeNode<Entry, Entry>) node);
392 if (referenceNode.getReference() != null) {
393 navigateTo(referenceNode.getReference());
394 } else {
395 navigateTo(referenceNode.getEntry());
396 }
397 }
398 }
399 }
400 });
401 m_tokens = new JList<Token>();
402 m_tokens.setCellRenderer(new TokenListCellRenderer(m_controller));
403 m_tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
404 m_tokens.setLayoutOrientation(JList.VERTICAL);
405 m_tokens.addMouseListener(new MouseAdapter() {
406 @Override
407 public void mouseClicked(MouseEvent event) {
408 if (event.getClickCount() == 2) {
409 Token selected = m_tokens.getSelectedValue();
410 if (selected != null) {
411 showToken(selected);
412 }
413 }
414 }
415 });
416 m_tokens.setPreferredSize(new Dimension(0, 200));
417 m_tokens.setMinimumSize(new Dimension(0, 200));
418 JSplitPane callPanel = new JSplitPane(
419 JSplitPane.VERTICAL_SPLIT,
420 true,
421 new JScrollPane(m_callsTree),
422 new JScrollPane(m_tokens)
423 );
424 callPanel.setResizeWeight(1); // let the top side take all the slack
425 callPanel.resetToPreferredSizes();
426
427 // layout controls
428 JPanel centerPanel = new JPanel();
429 centerPanel.setLayout(new BorderLayout());
430 centerPanel.add(m_infoPanel, BorderLayout.NORTH);
431 centerPanel.add(sourceScroller, BorderLayout.CENTER);
432 m_tabs = new JTabbedPane();
433 m_tabs.setPreferredSize(new Dimension(250, 0));
434 m_tabs.addTab("Inheritance", inheritancePanel);
435 m_tabs.addTab("Implementations", implementationsPanel);
436 m_tabs.addTab("Call Graph", callPanel);
437 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, m_tabs);
438 splitRight.setResizeWeight(1); // let the left side take all the slack
439 splitRight.resetToPreferredSizes();
440 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, m_classesPanel, splitRight);
441 splitCenter.setResizeWeight(0); // let the right side take all the slack
442 pane.add(splitCenter, BorderLayout.CENTER);
443
444 // init menus
445 JMenuBar menuBar = new JMenuBar();
446 m_frame.setJMenuBar(menuBar);
447 {
448 JMenu menu = new JMenu("File");
449 menuBar.add(menu);
450 {
451 JMenuItem item = new JMenuItem("Open Jar...");
452 menu.add(item);
453 item.addActionListener(new ActionListener() {
454 @Override
455 public void actionPerformed(ActionEvent event) {
456 if (m_jarFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
457 // load the jar in a separate thread
458 new Thread() {
459 @Override
460 public void run() {
461 try {
462 m_controller.openJar(new JarFile(m_jarFileChooser.getSelectedFile()));
463 } catch (IOException ex) {
464 throw new Error(ex);
465 }
466 }
467 }.start();
468 }
469 }
470 });
471 }
472 {
473 JMenuItem item = new JMenuItem("Close Jar");
474 menu.add(item);
475 item.addActionListener(new ActionListener() {
476 @Override
477 public void actionPerformed(ActionEvent event) {
478 m_controller.closeJar();
479 }
480 });
481 m_closeJarMenu = item;
482 }
483 menu.addSeparator();
484 {
485 JMenuItem item = new JMenuItem("Open Mappings...");
486 menu.add(item);
487 item.addActionListener(new ActionListener() {
488 @Override
489 public void actionPerformed(ActionEvent event) {
490 if (m_mappingsFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
491 try {
492 m_controller.openMappings(m_mappingsFileChooser.getSelectedFile());
493 } catch (IOException ex) {
494 throw new Error(ex);
495 } catch (MappingParseException ex) {
496 JOptionPane.showMessageDialog(m_frame, ex.getMessage());
497 }
498 }
499 }
500 });
501 m_openMappingsMenu = item;
502 }
503 {
504 JMenuItem item = new JMenuItem("Open Old Mappings...");
505 menu.add(item);
506 item.addActionListener(event -> {
507 if (m_oldMappingsFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
508 try {
509 m_controller.openOldMappings(m_oldMappingsFileChooser.getSelectedFile());
510 } catch (IOException ex) {
511 throw new Error(ex);
512 } catch (MappingParseException ex) {
513 JOptionPane.showMessageDialog(m_frame, ex.getMessage());
514 }
515 }
516 });
517 m_openOldMappingsMenu = item;
518 }
519 menu.addSeparator();
520 {
521 JMenuItem item = new JMenuItem("Save Mappings");
522 menu.add(item);
523 item.addActionListener(new ActionListener() {
524 @Override
525 public void actionPerformed(ActionEvent event) {
526 try {
527 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
528 } catch (IOException ex) {
529 throw new Error(ex);
530 }
531 }
532 });
533 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK));
534 m_saveMappingsMenu = item;
535 }
536 {
537 JMenuItem item = new JMenuItem("Save Mappings As...");
538 menu.add(item);
539 item.addActionListener(new ActionListener() {
540 @Override
541 public void actionPerformed(ActionEvent event) {
542 if (m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
543 try {
544 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
545 m_saveMappingsMenu.setEnabled(true);
546 } catch (IOException ex) {
547 throw new Error(ex);
548 }
549 }
550 }
551 });
552 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
553 m_saveMappingsAsMenu = item;
554 }
555 {
556 JMenuItem item = new JMenuItem("Close Mappings");
557 menu.add(item);
558 item.addActionListener(new ActionListener() {
559 @Override
560 public void actionPerformed(ActionEvent event) {
561 m_controller.closeMappings();
562 }
563 });
564 m_closeMappingsMenu = item;
565 }
566 menu.addSeparator();
567 {
568 JMenuItem item = new JMenuItem("Export Source...");
569 menu.add(item);
570 item.addActionListener(new ActionListener() {
571 @Override
572 public void actionPerformed(ActionEvent event) {
573 if (m_exportSourceFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
574 m_controller.exportSource(m_exportSourceFileChooser.getSelectedFile());
575 }
576 }
577 });
578 m_exportSourceMenu = item;
579 }
580 {
581 JMenuItem item = new JMenuItem("Export Jar...");
582 menu.add(item);
583 item.addActionListener(new ActionListener() {
584 @Override
585 public void actionPerformed(ActionEvent event) {
586 if (m_exportJarFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
587 m_controller.exportJar(m_exportJarFileChooser.getSelectedFile());
588 }
589 }
590 });
591 m_exportJarMenu = item;
592 }
593 menu.addSeparator();
594 {
595 JMenuItem item = new JMenuItem("Exit");
596 menu.add(item);
597 item.addActionListener(new ActionListener() {
598 @Override
599 public void actionPerformed(ActionEvent event) {
600 close();
601 }
602 });
603 }
604 }
605 {
606 JMenu menu = new JMenu("Help");
607 menuBar.add(menu);
608 {
609 JMenuItem item = new JMenuItem("About");
610 menu.add(item);
611 item.addActionListener(new ActionListener() {
612 @Override
613 public void actionPerformed(ActionEvent event) {
614 AboutDialog.show(m_frame);
615 }
616 });
617 }
618 }
619
620 // init state
621 onCloseJar();
622
623 m_frame.addWindowListener(new WindowAdapter() {
624 @Override
625 public void windowClosing(WindowEvent event) {
626 close();
627 }
628 });
629
630 // show the frame
631 pane.doLayout();
632 m_frame.setSize(1024, 576);
633 m_frame.setMinimumSize(new Dimension(640, 480));
634 m_frame.setVisible(true);
635 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
636 }
637
638 public JFrame getFrame() {
639 return m_frame;
640 }
641
642 public GuiController getController() {
643 return m_controller;
644 }
645
646 public void onStartOpenJar() {
647 m_classesPanel.removeAll();
648 JPanel panel = new JPanel();
649 panel.setLayout(new FlowLayout());
650 panel.add(new JLabel("Loading..."));
651 m_classesPanel.add(panel);
652 redraw();
653 }
654
655 public void onFinishOpenJar(String jarName) {
656 // update gui
657 m_frame.setTitle(Constants.Name + " - " + jarName);
658 m_classesPanel.removeAll();
659 m_classesPanel.add(m_splitClasses);
660 setSource(null);
661
662 // update menu
663 m_closeJarMenu.setEnabled(true);
664 m_openOldMappingsMenu.setEnabled(true);
665 m_openMappingsMenu.setEnabled(true);
666 m_saveMappingsMenu.setEnabled(false);
667 m_saveMappingsAsMenu.setEnabled(true);
668 m_closeMappingsMenu.setEnabled(true);
669 m_exportSourceMenu.setEnabled(true);
670 m_exportJarMenu.setEnabled(true);
671
672 redraw();
673 }
674
675 public void onCloseJar() {
676 // update gui
677 m_frame.setTitle(Constants.Name);
678 setObfClasses(null);
679 setDeobfClasses(null);
680 setSource(null);
681 m_classesPanel.removeAll();
682
683 // update menu
684 m_closeJarMenu.setEnabled(false);
685 m_openOldMappingsMenu.setEnabled(false);
686 m_openMappingsMenu.setEnabled(false);
687 m_saveMappingsMenu.setEnabled(false);
688 m_saveMappingsAsMenu.setEnabled(false);
689 m_closeMappingsMenu.setEnabled(false);
690 m_exportSourceMenu.setEnabled(false);
691 m_exportJarMenu.setEnabled(false);
692
693 redraw();
694 }
695
696 public void setObfClasses(Collection<ClassEntry> obfClasses) {
697 m_obfClasses.setClasses(obfClasses);
698 }
699
700 public void setDeobfClasses(Collection<ClassEntry> deobfClasses) {
701 m_deobfClasses.setClasses(deobfClasses);
702 }
703
704 public void setMappingsFile(File file) {
705 m_mappingsFileChooser.setSelectedFile(file);
706 m_saveMappingsMenu.setEnabled(file != null);
707 }
708
709 public void setSource(String source) {
710 m_editor.getHighlighter().removeAllHighlights();
711 m_editor.setText(source);
712 }
713
714 public void showToken(final Token token) {
715 if (token == null) {
716 throw new IllegalArgumentException("Token cannot be null!");
717 }
718 CodeReader.navigateToToken(m_editor, token, m_selectionHighlightPainter);
719 redraw();
720 }
721
722 public void showTokens(Collection<Token> tokens) {
723 Vector<Token> sortedTokens = new Vector<Token>(tokens);
724 Collections.sort(sortedTokens);
725 if (sortedTokens.size() > 1) {
726 // sort the tokens and update the tokens panel
727 m_tokens.setListData(sortedTokens);
728 m_tokens.setSelectedIndex(0);
729 } else {
730 m_tokens.setListData(new Vector<Token>());
731 }
732
733 // show the first token
734 showToken(sortedTokens.get(0));
735 }
736
737 public void setHighlightedTokens(Iterable<Token> obfuscatedTokens, Iterable<Token> deobfuscatedTokens, Iterable<Token> otherTokens) {
738
739 // remove any old highlighters
740 m_editor.getHighlighter().removeAllHighlights();
741
742 // color things based on the index
743 if (obfuscatedTokens != null) {
744 setHighlightedTokens(obfuscatedTokens, m_obfuscatedHighlightPainter);
745 }
746 if (deobfuscatedTokens != null) {
747 setHighlightedTokens(deobfuscatedTokens, m_deobfuscatedHighlightPainter);
748 }
749 if (otherTokens != null) {
750 setHighlightedTokens(otherTokens, m_otherHighlightPainter);
751 }
752
753 redraw();
754 }
755
756 private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) {
757 for (Token token : tokens) {
758 try {
759 m_editor.getHighlighter().addHighlight(token.start, token.end, painter);
760 } catch (BadLocationException ex) {
761 throw new IllegalArgumentException(ex);
762 }
763 }
764 }
765
766 private void clearReference() {
767 m_infoPanel.removeAll();
768 JLabel label = new JLabel("No identifier selected");
769 GuiTricks.unboldLabel(label);
770 label.setHorizontalAlignment(JLabel.CENTER);
771 m_infoPanel.add(label);
772
773 redraw();
774 }
775
776 private void showReference(EntryReference<Entry, Entry> reference) {
777 if (reference == null) {
778 clearReference();
779 return;
780 }
781
782 m_reference = reference;
783
784 m_infoPanel.removeAll();
785 if (reference.entry instanceof ClassEntry) {
786 showClassEntry((ClassEntry) m_reference.entry);
787 } else if (m_reference.entry instanceof FieldEntry) {
788 showFieldEntry((FieldEntry) m_reference.entry);
789 } else if (m_reference.entry instanceof MethodEntry) {
790 showMethodEntry((MethodEntry) m_reference.entry);
791 } else if (m_reference.entry instanceof ConstructorEntry) {
792 showConstructorEntry((ConstructorEntry) m_reference.entry);
793 } else if (m_reference.entry instanceof ArgumentEntry) {
794 showArgumentEntry((ArgumentEntry) m_reference.entry);
795 } else {
796 throw new Error("Unknown entry type: " + m_reference.entry.getClass().getName());
797 }
798
799 redraw();
800 }
801
802 private void showClassEntry(ClassEntry entry) {
803 addNameValue(m_infoPanel, "Class", entry.getName());
804 }
805
806 private void showFieldEntry(FieldEntry entry) {
807 addNameValue(m_infoPanel, "Field", entry.getName());
808 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
809 addNameValue(m_infoPanel, "Type", entry.getType().toString());
810 }
811
812 private void showMethodEntry(MethodEntry entry) {
813 addNameValue(m_infoPanel, "Method", entry.getName());
814 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
815 addNameValue(m_infoPanel, "Signature", entry.getSignature().toString());
816 }
817
818 private void showConstructorEntry(ConstructorEntry entry) {
819 addNameValue(m_infoPanel, "Constructor", entry.getClassEntry().getName());
820 if (!entry.isStatic()) {
821 addNameValue(m_infoPanel, "Signature", entry.getSignature().toString());
822 }
823 }
824
825 private void showArgumentEntry(ArgumentEntry entry) {
826 addNameValue(m_infoPanel, "Argument", entry.getName());
827 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
828 addNameValue(m_infoPanel, "Method", entry.getBehaviorEntry().getName());
829 addNameValue(m_infoPanel, "Index", Integer.toString(entry.getIndex()));
830 }
831
832 private void addNameValue(JPanel container, String name, String value) {
833 JPanel panel = new JPanel();
834 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
835 container.add(panel);
836
837 JLabel label = new JLabel(name + ":", JLabel.RIGHT);
838 label.setPreferredSize(new Dimension(100, label.getPreferredSize().height));
839 panel.add(label);
840
841 panel.add(GuiTricks.unboldLabel(new JLabel(value, JLabel.LEFT)));
842 }
843
844 private void onCaretMove(int pos) {
845
846 Token token = m_controller.getToken(pos);
847 boolean isToken = token != null;
848
849 m_reference = m_controller.getDeobfReference(token);
850 boolean isClassEntry = isToken && m_reference.entry instanceof ClassEntry;
851 boolean isFieldEntry = isToken && m_reference.entry instanceof FieldEntry;
852 boolean isMethodEntry = isToken && m_reference.entry instanceof MethodEntry;
853 boolean isConstructorEntry = isToken && m_reference.entry instanceof ConstructorEntry;
854 boolean isInJar = isToken && m_controller.entryIsInJar(m_reference.entry);
855 boolean isRenameable = isToken && m_controller.referenceIsRenameable(m_reference);
856
857 if (isToken) {
858 showReference(m_reference);
859 } else {
860 clearReference();
861 }
862
863 m_renameMenu.setEnabled(isRenameable && isToken);
864 m_showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry);
865 m_showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry);
866 m_showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry);
867 m_openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry));
868 m_openPreviousMenu.setEnabled(m_controller.hasPreviousLocation());
869 m_toggleMappingMenu.setEnabled(isRenameable && isToken);
870
871 if (isToken && m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
872 m_toggleMappingMenu.setText("Reset to obfuscated");
873 } else {
874 m_toggleMappingMenu.setText("Mark as deobfuscated");
875 }
876 }
877
878 private void navigateTo(Entry entry) {
879 if (!m_controller.entryIsInJar(entry)) {
880 // entry is not in the jar. Ignore it
881 return;
882 }
883 if (m_reference != null) {
884 m_controller.savePreviousReference(m_reference);
885 }
886 m_controller.openDeclaration(entry);
887 }
888
889 private void navigateTo(EntryReference<Entry, Entry> reference) {
890 if (!m_controller.entryIsInJar(reference.getLocationClassEntry())) {
891 // reference is not in the jar. Ignore it
892 return;
893 }
894 if (m_reference != null) {
895 m_controller.savePreviousReference(m_reference);
896 }
897 m_controller.openReference(reference);
898 }
899
900 private void startRename() {
901
902 // init the text box
903 final JTextField text = new JTextField();
904 text.setText(m_reference.getNamableName());
905 text.setPreferredSize(new Dimension(360, text.getPreferredSize().height));
906 text.addKeyListener(new KeyAdapter() {
907 @Override
908 public void keyPressed(KeyEvent event) {
909 switch (event.getKeyCode()) {
910 case KeyEvent.VK_ENTER:
911 finishRename(text, true);
912 break;
913
914 case KeyEvent.VK_ESCAPE:
915 finishRename(text, false);
916 break;
917 }
918 }
919 });
920
921 // find the label with the name and replace it with the text box
922 JPanel panel = (JPanel) m_infoPanel.getComponent(0);
923 panel.remove(panel.getComponentCount() - 1);
924 panel.add(text);
925 text.grabFocus();
926 text.selectAll();
927
928 redraw();
929 }
930
931 private void finishRename(JTextField text, boolean saveName) {
932 String newName = text.getText();
933 if (saveName && newName != null && newName.length() > 0) {
934 try {
935 m_controller.rename(m_reference, newName);
936 } catch (IllegalNameException ex) {
937 text.setBorder(BorderFactory.createLineBorder(Color.red, 1));
938 text.setToolTipText(ex.getReason());
939 GuiTricks.showToolTipNow(text);
940 }
941 return;
942 }
943
944 // abort the rename
945 JPanel panel = (JPanel) m_infoPanel.getComponent(0);
946 panel.remove(panel.getComponentCount() - 1);
947 panel.add(GuiTricks.unboldLabel(new JLabel(m_reference.getNamableName(), JLabel.LEFT)));
948
949 m_editor.grabFocus();
950
951 redraw();
952 }
953
954 private void showInheritance() {
955
956 if (m_reference == null) {
957 return;
958 }
959
960 m_inheritanceTree.setModel(null);
961
962 if (m_reference.entry instanceof ClassEntry) {
963 // get the class inheritance
964 ClassInheritanceTreeNode classNode = m_controller.getClassInheritance((ClassEntry) m_reference.entry);
965
966 // show the tree at the root
967 TreePath path = getPathToRoot(classNode);
968 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
969 m_inheritanceTree.expandPath(path);
970 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
971 } else if (m_reference.entry instanceof MethodEntry) {
972 // get the method inheritance
973 MethodInheritanceTreeNode classNode = m_controller.getMethodInheritance((MethodEntry) m_reference.entry);
974
975 // show the tree at the root
976 TreePath path = getPathToRoot(classNode);
977 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
978 m_inheritanceTree.expandPath(path);
979 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
980 }
981
982 m_tabs.setSelectedIndex(0);
983 redraw();
984 }
985
986 private void showImplementations() {
987
988 if (m_reference == null) {
989 return;
990 }
991
992 m_implementationsTree.setModel(null);
993
994 if (m_reference.entry instanceof ClassEntry) {
995 // get the class implementations
996 ClassImplementationsTreeNode node = m_controller.getClassImplementations((ClassEntry) m_reference.entry);
997 if (node != null) {
998 // show the tree at the root
999 TreePath path = getPathToRoot(node);
1000 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
1001 m_implementationsTree.expandPath(path);
1002 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1003 }
1004 } else if (m_reference.entry instanceof MethodEntry) {
1005 // get the method implementations
1006 MethodImplementationsTreeNode node = m_controller.getMethodImplementations((MethodEntry) m_reference.entry);
1007 if (node != null) {
1008 // show the tree at the root
1009 TreePath path = getPathToRoot(node);
1010 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
1011 m_implementationsTree.expandPath(path);
1012 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1013 }
1014 }
1015
1016 m_tabs.setSelectedIndex(1);
1017 redraw();
1018 }
1019
1020 private void showCalls() {
1021
1022 if (m_reference == null) {
1023 return;
1024 }
1025
1026 if (m_reference.entry instanceof ClassEntry) {
1027 // look for calls to the default constructor
1028 // TODO: get a list of all the constructors and find calls to all of them
1029 BehaviorReferenceTreeNode node = m_controller.getMethodReferences(new ConstructorEntry((ClassEntry) m_reference.entry, new Signature("()V")));
1030 m_callsTree.setModel(new DefaultTreeModel(node));
1031 } else if (m_reference.entry instanceof FieldEntry) {
1032 FieldReferenceTreeNode node = m_controller.getFieldReferences((FieldEntry) m_reference.entry);
1033 m_callsTree.setModel(new DefaultTreeModel(node));
1034 } else if (m_reference.entry instanceof MethodEntry) {
1035 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((MethodEntry) m_reference.entry);
1036 m_callsTree.setModel(new DefaultTreeModel(node));
1037 } else if (m_reference.entry instanceof ConstructorEntry) {
1038 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((ConstructorEntry) m_reference.entry);
1039 m_callsTree.setModel(new DefaultTreeModel(node));
1040 }
1041
1042 m_tabs.setSelectedIndex(2);
1043 redraw();
1044 }
1045
1046 private void toggleMapping() {
1047 if (m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
1048 m_controller.removeMapping(m_reference);
1049 } else {
1050 m_controller.markAsDeobfuscated(m_reference);
1051 }
1052 }
1053
1054 private TreePath getPathToRoot(TreeNode node) {
1055 List<TreeNode> nodes = Lists.newArrayList();
1056 TreeNode n = node;
1057 do {
1058 nodes.add(n);
1059 n = n.getParent();
1060 } while (n != null);
1061 Collections.reverse(nodes);
1062 return new TreePath(nodes.toArray());
1063 }
1064
1065 private void close() {
1066 if (!m_controller.isDirty()) {
1067 // everything is saved, we can exit safely
1068 m_frame.dispose();
1069 } else {
1070 // ask to save before closing
1071 String[] options = {"Save and exit", "Discard changes", "Cancel"};
1072 int response = JOptionPane.showOptionDialog(m_frame, "Your mappings have not been saved yet. Do you want to save?", "Save your changes?", JOptionPane.YES_NO_CANCEL_OPTION,
1073 JOptionPane.QUESTION_MESSAGE, null, options, options[2]);
1074 switch (response) {
1075 case JOptionPane.YES_OPTION: // save and exit
1076 if (m_mappingsFileChooser.getSelectedFile() != null || m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
1077 try {
1078 m_controller.saveMappings(m_mappingsFileChooser.getCurrentDirectory());
1079 m_frame.dispose();
1080 } catch (IOException ex) {
1081 throw new Error(ex);
1082 }
1083 }
1084 break;
1085
1086 case JOptionPane.NO_OPTION:
1087 // don't save, exit
1088 m_frame.dispose();
1089 break;
1090
1091 // cancel means do nothing
1092 }
1093 }
1094 }
1095
1096 private void redraw() {
1097 m_frame.validate();
1098 m_frame.repaint();
1099 }
1100}
diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java
new file mode 100644
index 00000000..aa6acdc5
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/GuiController.java
@@ -0,0 +1,349 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import com.google.common.collect.Lists;
14import com.google.common.collect.Queues;
15
16import com.strobel.decompiler.languages.java.ast.CompilationUnit;
17
18import java.io.File;
19import java.io.FileReader;
20import java.io.FileWriter;
21import java.io.IOException;
22import java.util.Collection;
23import java.util.Deque;
24import java.util.List;
25import java.util.jar.JarFile;
26
27import cuchaz.enigma.Deobfuscator;
28import cuchaz.enigma.Deobfuscator.ProgressListener;
29import cuchaz.enigma.analysis.*;
30import cuchaz.enigma.gui.ProgressDialog.ProgressRunnable;
31import cuchaz.enigma.mapping.*;
32
33public class GuiController {
34
35 private Deobfuscator m_deobfuscator;
36 private Gui m_gui;
37 private SourceIndex m_index;
38 private ClassEntry m_currentObfClass;
39 private boolean m_isDirty;
40 private Deque<EntryReference<Entry, Entry>> m_referenceStack;
41
42 public GuiController(Gui gui) {
43 m_gui = gui;
44 m_deobfuscator = null;
45 m_index = null;
46 m_currentObfClass = null;
47 m_isDirty = false;
48 m_referenceStack = Queues.newArrayDeque();
49 }
50
51 public boolean isDirty() {
52 return m_isDirty;
53 }
54
55 public void openJar(final JarFile jar) throws IOException {
56 m_gui.onStartOpenJar();
57 m_deobfuscator = new Deobfuscator(jar);
58 m_gui.onFinishOpenJar(m_deobfuscator.getJarName());
59 refreshClasses();
60 }
61
62 public void closeJar() {
63 m_deobfuscator = null;
64 m_gui.onCloseJar();
65 }
66
67 public void openOldMappings(File file) throws IOException, MappingParseException {
68 FileReader in = new FileReader(file);
69 m_deobfuscator.setMappings(new MappingsReaderOld().read(in));
70 in.close();
71 m_isDirty = false;
72 m_gui.setMappingsFile(file);
73 refreshClasses();
74 refreshCurrentClass();
75 }
76
77 public void openMappings(File file) throws IOException, MappingParseException {
78 m_deobfuscator.setMappings(new MappingsReader().read(file));
79 m_isDirty = false;
80 m_gui.setMappingsFile(file);
81 refreshClasses();
82 refreshCurrentClass();
83 }
84
85 public void saveMappings(File file) throws IOException {
86 new MappingsWriter().write(file, m_deobfuscator.getMappings());
87 m_isDirty = false;
88 }
89
90 public void closeMappings() {
91 m_deobfuscator.setMappings(null);
92 m_gui.setMappingsFile(null);
93 refreshClasses();
94 refreshCurrentClass();
95 }
96
97 public void exportSource(final File dirOut) {
98 ProgressDialog.runInThread(m_gui.getFrame(), new ProgressRunnable() {
99 @Override
100 public void run(ProgressListener progress) throws Exception {
101 m_deobfuscator.writeSources(dirOut, progress);
102 }
103 });
104 }
105
106 public void exportJar(final File fileOut) {
107 ProgressDialog.runInThread(m_gui.getFrame(), new ProgressRunnable() {
108 @Override
109 public void run(ProgressListener progress) {
110 m_deobfuscator.writeJar(fileOut, progress);
111 }
112 });
113 }
114
115 public Token getToken(int pos) {
116 if (m_index == null) {
117 return null;
118 }
119 return m_index.getReferenceToken(pos);
120 }
121
122 public EntryReference<Entry, Entry> getDeobfReference(Token token) {
123 if (m_index == null) {
124 return null;
125 }
126 return m_index.getDeobfReference(token);
127 }
128
129 public ReadableToken getReadableToken(Token token) {
130 if (m_index == null) {
131 return null;
132 }
133 return new ReadableToken(
134 m_index.getLineNumber(token.start),
135 m_index.getColumnNumber(token.start),
136 m_index.getColumnNumber(token.end)
137 );
138 }
139
140 public boolean entryHasDeobfuscatedName(Entry deobfEntry) {
141 return m_deobfuscator.hasDeobfuscatedName(m_deobfuscator.obfuscateEntry(deobfEntry));
142 }
143
144 public boolean entryIsInJar(Entry deobfEntry) {
145 return m_deobfuscator.isObfuscatedIdentifier(m_deobfuscator.obfuscateEntry(deobfEntry));
146 }
147
148 public boolean referenceIsRenameable(EntryReference<Entry, Entry> deobfReference) {
149 return m_deobfuscator.isRenameable(m_deobfuscator.obfuscateReference(deobfReference));
150 }
151
152 public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) {
153 ClassEntry obfClassEntry = m_deobfuscator.obfuscateEntry(deobfClassEntry);
154 ClassInheritanceTreeNode rootNode = m_deobfuscator.getJarIndex().getClassInheritance(
155 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
156 obfClassEntry
157 );
158 return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry);
159 }
160
161 public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) {
162 ClassEntry obfClassEntry = m_deobfuscator.obfuscateEntry(deobfClassEntry);
163 return m_deobfuscator.getJarIndex().getClassImplementations(
164 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
165 obfClassEntry
166 );
167 }
168
169 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) {
170 MethodEntry obfMethodEntry = m_deobfuscator.obfuscateEntry(deobfMethodEntry);
171 MethodInheritanceTreeNode rootNode = m_deobfuscator.getJarIndex().getMethodInheritance(
172 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
173 obfMethodEntry
174 );
175 return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry);
176 }
177
178 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) {
179 MethodEntry obfMethodEntry = m_deobfuscator.obfuscateEntry(deobfMethodEntry);
180 List<MethodImplementationsTreeNode> rootNodes = m_deobfuscator.getJarIndex().getMethodImplementations(
181 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
182 obfMethodEntry
183 );
184 if (rootNodes.isEmpty()) {
185 return null;
186 }
187 if (rootNodes.size() > 1) {
188 System.err.println("WARNING: Method " + deobfMethodEntry + " implements multiple interfaces. Only showing first one.");
189 }
190 return MethodImplementationsTreeNode.findNode(rootNodes.get(0), obfMethodEntry);
191 }
192
193 public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) {
194 FieldEntry obfFieldEntry = m_deobfuscator.obfuscateEntry(deobfFieldEntry);
195 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(
196 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
197 obfFieldEntry
198 );
199 rootNode.load(m_deobfuscator.getJarIndex(), true);
200 return rootNode;
201 }
202
203 public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) {
204 BehaviorEntry obfBehaviorEntry = m_deobfuscator.obfuscateEntry(deobfBehaviorEntry);
205 BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode(
206 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
207 obfBehaviorEntry
208 );
209 rootNode.load(m_deobfuscator.getJarIndex(), true);
210 return rootNode;
211 }
212
213 public void rename(EntryReference<Entry, Entry> deobfReference, String newName) {
214 EntryReference<Entry, Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
215 m_deobfuscator.rename(obfReference.getNameableEntry(), newName);
216 m_isDirty = true;
217 refreshClasses();
218 refreshCurrentClass(obfReference);
219 }
220
221 public void removeMapping(EntryReference<Entry, Entry> deobfReference) {
222 EntryReference<Entry, Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
223 m_deobfuscator.removeMapping(obfReference.getNameableEntry());
224 m_isDirty = true;
225 refreshClasses();
226 refreshCurrentClass(obfReference);
227 }
228
229 public void markAsDeobfuscated(EntryReference<Entry, Entry> deobfReference) {
230 EntryReference<Entry, Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
231 m_deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry());
232 m_isDirty = true;
233 refreshClasses();
234 refreshCurrentClass(obfReference);
235 }
236
237 public void openDeclaration(Entry deobfEntry) {
238 if (deobfEntry == null) {
239 throw new IllegalArgumentException("Entry cannot be null!");
240 }
241 openReference(new EntryReference<Entry, Entry>(deobfEntry, deobfEntry.getName()));
242 }
243
244 public void openReference(EntryReference<Entry, Entry> deobfReference) {
245 if (deobfReference == null) {
246 throw new IllegalArgumentException("Reference cannot be null!");
247 }
248
249 // get the reference target class
250 EntryReference<Entry, Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
251 ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOutermostClassEntry();
252 if (!m_deobfuscator.isObfuscatedIdentifier(obfClassEntry)) {
253 throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!");
254 }
255 if (m_currentObfClass == null || !m_currentObfClass.equals(obfClassEntry)) {
256 // deobfuscate the class, then navigate to the reference
257 m_currentObfClass = obfClassEntry;
258 deobfuscate(m_currentObfClass, obfReference);
259 } else {
260 showReference(obfReference);
261 }
262 }
263
264 private void showReference(EntryReference<Entry, Entry> obfReference) {
265 EntryReference<Entry, Entry> deobfReference = m_deobfuscator.deobfuscateReference(obfReference);
266 Collection<Token> tokens = m_index.getReferenceTokens(deobfReference);
267 if (tokens.isEmpty()) {
268 // DEBUG
269 System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, m_currentObfClass));
270 } else {
271 m_gui.showTokens(tokens);
272 }
273 }
274
275 public void savePreviousReference(EntryReference<Entry, Entry> deobfReference) {
276 m_referenceStack.push(m_deobfuscator.obfuscateReference(deobfReference));
277 }
278
279 public void openPreviousReference() {
280 if (hasPreviousLocation()) {
281 openReference(m_deobfuscator.deobfuscateReference(m_referenceStack.pop()));
282 }
283 }
284
285 public boolean hasPreviousLocation() {
286 return !m_referenceStack.isEmpty();
287 }
288
289 private void refreshClasses() {
290 List<ClassEntry> obfClasses = Lists.newArrayList();
291 List<ClassEntry> deobfClasses = Lists.newArrayList();
292 m_deobfuscator.getSeparatedClasses(obfClasses, deobfClasses);
293 m_gui.setObfClasses(obfClasses);
294 m_gui.setDeobfClasses(deobfClasses);
295 }
296
297 private void refreshCurrentClass() {
298 refreshCurrentClass(null);
299 }
300
301 private void refreshCurrentClass(EntryReference<Entry, Entry> obfReference) {
302 if (m_currentObfClass != null) {
303 deobfuscate(m_currentObfClass, obfReference);
304 }
305 }
306
307 private void deobfuscate(final ClassEntry classEntry, final EntryReference<Entry, Entry> obfReference) {
308
309 m_gui.setSource("(deobfuscating...)");
310
311 // run the deobfuscator in a separate thread so we don't block the GUI event queue
312 new Thread() {
313 @Override
314 public void run() {
315 // decompile,deobfuscate the bytecode
316 CompilationUnit sourceTree = m_deobfuscator.getSourceTree(classEntry.getClassName());
317 if (sourceTree == null) {
318 // decompilation of this class is not supported
319 m_gui.setSource("Unable to find class: " + classEntry);
320 return;
321 }
322 String source = m_deobfuscator.getSource(sourceTree);
323 m_index = m_deobfuscator.getSourceIndex(sourceTree, source);
324 m_gui.setSource(m_index.getSource());
325 if (obfReference != null) {
326 showReference(obfReference);
327 }
328
329 // set the highlighted tokens
330 List<Token> obfuscatedTokens = Lists.newArrayList();
331 List<Token> deobfuscatedTokens = Lists.newArrayList();
332 List<Token> otherTokens = Lists.newArrayList();
333 for (Token token : m_index.referenceTokens()) {
334 EntryReference<Entry, Entry> reference = m_index.getDeobfReference(token);
335 if (referenceIsRenameable(reference)) {
336 if (entryHasDeobfuscatedName(reference.getNameableEntry())) {
337 deobfuscatedTokens.add(token);
338 } else {
339 obfuscatedTokens.add(token);
340 }
341 } else {
342 otherTokens.add(token);
343 }
344 }
345 m_gui.setHighlightedTokens(obfuscatedTokens, deobfuscatedTokens, otherTokens);
346 }
347 }.start();
348 }
349}
diff --git a/src/main/java/cuchaz/enigma/gui/GuiTricks.java b/src/main/java/cuchaz/enigma/gui/GuiTricks.java
new file mode 100644
index 00000000..da2ec74f
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/GuiTricks.java
@@ -0,0 +1,56 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Font;
14import java.awt.event.ActionListener;
15import java.awt.event.MouseEvent;
16import java.util.Arrays;
17
18import javax.swing.JButton;
19import javax.swing.JComponent;
20import javax.swing.JLabel;
21import javax.swing.ToolTipManager;
22
23public class GuiTricks {
24
25 public static JLabel unboldLabel(JLabel label) {
26 Font font = label.getFont();
27 label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD));
28 return label;
29 }
30
31 public static void showToolTipNow(JComponent component) {
32 // HACKHACK: trick the tooltip manager into showing the tooltip right now
33 ToolTipManager manager = ToolTipManager.sharedInstance();
34 int oldDelay = manager.getInitialDelay();
35 manager.setInitialDelay(0);
36 manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false));
37 manager.setInitialDelay(oldDelay);
38 }
39
40 public static void deactivateButton(JButton button) {
41 button.setEnabled(false);
42 button.setText("");
43 for (ActionListener listener : Arrays.asList(button.getActionListeners())) {
44 button.removeActionListener(listener);
45 }
46 }
47
48 public static void activateButton(JButton button, String text, ActionListener newListener) {
49 button.setText(text);
50 button.setEnabled(true);
51 for (ActionListener listener : Arrays.asList(button.getActionListeners())) {
52 button.removeActionListener(listener);
53 }
54 button.addActionListener(newListener);
55 }
56}
diff --git a/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java b/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
new file mode 100644
index 00000000..4b79b77c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/MemberMatchingGui.java
@@ -0,0 +1,488 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import com.google.common.collect.Lists;
14import com.google.common.collect.Maps;
15
16import java.awt.BorderLayout;
17import java.awt.Container;
18import java.awt.Dimension;
19import java.awt.FlowLayout;
20import java.awt.event.ActionEvent;
21import java.awt.event.ActionListener;
22import java.awt.event.KeyAdapter;
23import java.awt.event.KeyEvent;
24import java.util.Collection;
25import java.util.List;
26import java.util.Map;
27
28import javax.swing.*;
29import javax.swing.text.Highlighter.HighlightPainter;
30
31import cuchaz.enigma.Constants;
32import cuchaz.enigma.Deobfuscator;
33import cuchaz.enigma.analysis.EntryReference;
34import cuchaz.enigma.analysis.SourceIndex;
35import cuchaz.enigma.analysis.Token;
36import cuchaz.enigma.convert.ClassMatches;
37import cuchaz.enigma.convert.MemberMatches;
38import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
39import cuchaz.enigma.mapping.ClassEntry;
40import cuchaz.enigma.mapping.Entry;
41import de.sciss.syntaxpane.DefaultSyntaxKit;
42
43
44public class MemberMatchingGui<T extends Entry> {
45
46 private enum SourceType {
47 Matched {
48 @Override
49 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
50 return matches.getSourceClassesWithoutUnmatchedEntries();
51 }
52 },
53 Unmatched {
54 @Override
55 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
56 return matches.getSourceClassesWithUnmatchedEntries();
57 }
58 };
59
60 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
61 JRadioButton button = new JRadioButton(name(), this == getDefault());
62 button.setActionCommand(name());
63 button.addActionListener(listener);
64 group.add(button);
65 return button;
66 }
67
68 public abstract <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches);
69
70 public static SourceType getDefault() {
71 return values()[0];
72 }
73 }
74
75 public interface SaveListener<T extends Entry> {
76 void save(MemberMatches<T> matches);
77 }
78
79 // controls
80 private JFrame m_frame;
81 private Map<SourceType, JRadioButton> m_sourceTypeButtons;
82 private ClassSelector m_sourceClasses;
83 private CodeReader m_sourceReader;
84 private CodeReader m_destReader;
85 private JButton m_matchButton;
86 private JButton m_unmatchableButton;
87 private JLabel m_sourceLabel;
88 private JLabel m_destLabel;
89 private HighlightPainter m_unmatchedHighlightPainter;
90 private HighlightPainter m_matchedHighlightPainter;
91
92 private ClassMatches m_classMatches;
93 private MemberMatches<T> m_memberMatches;
94 private Deobfuscator m_sourceDeobfuscator;
95 private Deobfuscator m_destDeobfuscator;
96 private SaveListener<T> m_saveListener;
97 private SourceType m_sourceType;
98 private ClassEntry m_obfSourceClass;
99 private ClassEntry m_obfDestClass;
100 private T m_obfSourceEntry;
101 private T m_obfDestEntry;
102
103 public MemberMatchingGui(ClassMatches classMatches, MemberMatches<T> fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
104
105 m_classMatches = classMatches;
106 m_memberMatches = fieldMatches;
107 m_sourceDeobfuscator = sourceDeobfuscator;
108 m_destDeobfuscator = destDeobfuscator;
109
110 // init frame
111 m_frame = new JFrame(Constants.Name + " - Member Matcher");
112 final Container pane = m_frame.getContentPane();
113 pane.setLayout(new BorderLayout());
114
115 // init classes side
116 JPanel classesPanel = new JPanel();
117 classesPanel.setLayout(new BoxLayout(classesPanel, BoxLayout.PAGE_AXIS));
118 classesPanel.setPreferredSize(new Dimension(200, 0));
119 pane.add(classesPanel, BorderLayout.WEST);
120 classesPanel.add(new JLabel("Classes"));
121
122 // init source type radios
123 JPanel sourceTypePanel = new JPanel();
124 classesPanel.add(sourceTypePanel);
125 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
126 ActionListener sourceTypeListener = new ActionListener() {
127 @Override
128 public void actionPerformed(ActionEvent event) {
129 setSourceType(SourceType.valueOf(event.getActionCommand()));
130 }
131 };
132 ButtonGroup sourceTypeButtons = new ButtonGroup();
133 m_sourceTypeButtons = Maps.newHashMap();
134 for (SourceType sourceType : SourceType.values()) {
135 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
136 m_sourceTypeButtons.put(sourceType, button);
137 sourceTypePanel.add(button);
138 }
139
140 m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
141 m_sourceClasses.setListener(new ClassSelectionListener() {
142 @Override
143 public void onSelectClass(ClassEntry classEntry) {
144 setSourceClass(classEntry);
145 }
146 });
147 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
148 classesPanel.add(sourceScroller);
149
150 // init readers
151 DefaultSyntaxKit.initKit();
152 m_sourceReader = new CodeReader();
153 m_sourceReader.setSelectionListener(new CodeReader.SelectionListener() {
154 @Override
155 public void onSelect(EntryReference<Entry, Entry> reference) {
156 if (reference != null) {
157 onSelectSource(reference.entry);
158 } else {
159 onSelectSource(null);
160 }
161 }
162 });
163 m_destReader = new CodeReader();
164 m_destReader.setSelectionListener(new CodeReader.SelectionListener() {
165 @Override
166 public void onSelect(EntryReference<Entry, Entry> reference) {
167 if (reference != null) {
168 onSelectDest(reference.entry);
169 } else {
170 onSelectDest(null);
171 }
172 }
173 });
174
175 // add key bindings
176 KeyAdapter keyListener = new KeyAdapter() {
177 @Override
178 public void keyPressed(KeyEvent event) {
179 switch (event.getKeyCode()) {
180 case KeyEvent.VK_M:
181 m_matchButton.doClick();
182 break;
183 }
184 }
185 };
186 m_sourceReader.addKeyListener(keyListener);
187 m_destReader.addKeyListener(keyListener);
188
189 // init all the splits
190 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_sourceReader), new JScrollPane(m_destReader));
191 splitRight.setResizeWeight(0.5); // resize 50:50
192 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, splitRight);
193 splitLeft.setResizeWeight(0); // let the right side take all the slack
194 pane.add(splitLeft, BorderLayout.CENTER);
195 splitLeft.resetToPreferredSizes();
196
197 // init bottom panel
198 JPanel bottomPanel = new JPanel();
199 bottomPanel.setLayout(new FlowLayout());
200 pane.add(bottomPanel, BorderLayout.SOUTH);
201
202 m_matchButton = new JButton();
203 m_unmatchableButton = new JButton();
204
205 m_sourceLabel = new JLabel();
206 bottomPanel.add(m_sourceLabel);
207 bottomPanel.add(m_matchButton);
208 bottomPanel.add(m_unmatchableButton);
209 m_destLabel = new JLabel();
210 bottomPanel.add(m_destLabel);
211
212 // show the frame
213 pane.doLayout();
214 m_frame.setSize(1024, 576);
215 m_frame.setMinimumSize(new Dimension(640, 480));
216 m_frame.setVisible(true);
217 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
218
219 m_unmatchedHighlightPainter = new ObfuscatedHighlightPainter();
220 m_matchedHighlightPainter = new DeobfuscatedHighlightPainter();
221
222 // init state
223 m_saveListener = null;
224 m_obfSourceClass = null;
225 m_obfDestClass = null;
226 m_obfSourceEntry = null;
227 m_obfDestEntry = null;
228 setSourceType(SourceType.getDefault());
229 updateButtons();
230 }
231
232 protected void setSourceType(SourceType val) {
233 m_sourceType = val;
234 updateSourceClasses();
235 }
236
237 public void setSaveListener(SaveListener<T> val) {
238 m_saveListener = val;
239 }
240
241 private void updateSourceClasses() {
242
243 String selectedPackage = m_sourceClasses.getSelectedPackage();
244
245 List<ClassEntry> deobfClassEntries = Lists.newArrayList();
246 for (ClassEntry entry : m_sourceType.getObfSourceClasses(m_memberMatches)) {
247 deobfClassEntries.add(m_sourceDeobfuscator.deobfuscateEntry(entry));
248 }
249 m_sourceClasses.setClasses(deobfClassEntries);
250
251 if (selectedPackage != null) {
252 m_sourceClasses.expandPackage(selectedPackage);
253 }
254
255 for (SourceType sourceType : SourceType.values()) {
256 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
257 sourceType.name(), sourceType.getObfSourceClasses(m_memberMatches).size()
258 ));
259 }
260 }
261
262 protected void setSourceClass(ClassEntry sourceClass) {
263
264 m_obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
265 m_obfDestClass = m_classMatches.getUniqueMatches().get(m_obfSourceClass);
266 if (m_obfDestClass == null) {
267 throw new Error("No matching dest class for source class: " + m_obfSourceClass);
268 }
269
270 m_sourceReader.decompileClass(m_obfSourceClass, m_sourceDeobfuscator, false, new Runnable() {
271 @Override
272 public void run() {
273 updateSourceHighlights();
274 }
275 });
276 m_destReader.decompileClass(m_obfDestClass, m_destDeobfuscator, false, new Runnable() {
277 @Override
278 public void run() {
279 updateDestHighlights();
280 }
281 });
282 }
283
284 protected void updateSourceHighlights() {
285 highlightEntries(m_sourceReader, m_sourceDeobfuscator, m_memberMatches.matches().keySet(), m_memberMatches.getUnmatchedSourceEntries());
286 }
287
288 protected void updateDestHighlights() {
289 highlightEntries(m_destReader, m_destDeobfuscator, m_memberMatches.matches().values(), m_memberMatches.getUnmatchedDestEntries());
290 }
291
292 private void highlightEntries(CodeReader reader, Deobfuscator deobfuscator, Collection<T> obfMatchedEntries, Collection<T> obfUnmatchedEntries) {
293 reader.clearHighlights();
294 SourceIndex index = reader.getSourceIndex();
295
296 // matched fields
297 for (T obfT : obfMatchedEntries) {
298 T deobfT = deobfuscator.deobfuscateEntry(obfT);
299 Token token = index.getDeclarationToken(deobfT);
300 if (token != null) {
301 reader.setHighlightedToken(token, m_matchedHighlightPainter);
302 }
303 }
304
305 // unmatched fields
306 for (T obfT : obfUnmatchedEntries) {
307 T deobfT = deobfuscator.deobfuscateEntry(obfT);
308 Token token = index.getDeclarationToken(deobfT);
309 if (token != null) {
310 reader.setHighlightedToken(token, m_unmatchedHighlightPainter);
311 }
312 }
313 }
314
315 private boolean isSelectionMatched() {
316 return m_obfSourceEntry != null && m_obfDestEntry != null
317 && m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry);
318 }
319
320 protected void onSelectSource(Entry source) {
321
322 // start with no selection
323 if (isSelectionMatched()) {
324 setDest(null);
325 }
326 setSource(null);
327
328 // then look for a valid source selection
329 if (source != null) {
330
331 // this looks really scary, but it's actually ok
332 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
333 // and MemberMatches.hasSource() will only pass entries that actually match T
334 @SuppressWarnings("unchecked")
335 T sourceEntry = (T) source;
336
337 T obfSourceEntry = m_sourceDeobfuscator.obfuscateEntry(sourceEntry);
338 if (m_memberMatches.hasSource(obfSourceEntry)) {
339 setSource(obfSourceEntry);
340
341 // look for a matched dest too
342 T obfDestEntry = m_memberMatches.matches().get(obfSourceEntry);
343 if (obfDestEntry != null) {
344 setDest(obfDestEntry);
345 }
346 }
347 }
348
349 updateButtons();
350 }
351
352 protected void onSelectDest(Entry dest) {
353
354 // start with no selection
355 if (isSelectionMatched()) {
356 setSource(null);
357 }
358 setDest(null);
359
360 // then look for a valid dest selection
361 if (dest != null) {
362
363 // this looks really scary, but it's actually ok
364 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
365 // and MemberMatches.hasSource() will only pass entries that actually match T
366 @SuppressWarnings("unchecked")
367 T destEntry = (T) dest;
368
369 T obfDestEntry = m_destDeobfuscator.obfuscateEntry(destEntry);
370 if (m_memberMatches.hasDest(obfDestEntry)) {
371 setDest(obfDestEntry);
372
373 // look for a matched source too
374 T obfSourceEntry = m_memberMatches.matches().inverse().get(obfDestEntry);
375 if (obfSourceEntry != null) {
376 setSource(obfSourceEntry);
377 }
378 }
379 }
380
381 updateButtons();
382 }
383
384 private void setSource(T obfEntry) {
385 if (obfEntry == null) {
386 m_obfSourceEntry = obfEntry;
387 m_sourceLabel.setText("");
388 } else {
389 m_obfSourceEntry = obfEntry;
390 m_sourceLabel.setText(getEntryLabel(obfEntry, m_sourceDeobfuscator));
391 }
392 }
393
394 private void setDest(T obfEntry) {
395 if (obfEntry == null) {
396 m_obfDestEntry = obfEntry;
397 m_destLabel.setText("");
398 } else {
399 m_obfDestEntry = obfEntry;
400 m_destLabel.setText(getEntryLabel(obfEntry, m_destDeobfuscator));
401 }
402 }
403
404 private String getEntryLabel(T obfEntry, Deobfuscator deobfuscator) {
405 // show obfuscated and deobfuscated names, but no types/signatures
406 T deobfEntry = deobfuscator.deobfuscateEntry(obfEntry);
407 return String.format("%s (%s)", deobfEntry.getName(), obfEntry.getName());
408 }
409
410 private void updateButtons() {
411
412 GuiTricks.deactivateButton(m_matchButton);
413 GuiTricks.deactivateButton(m_unmatchableButton);
414
415 if (m_obfSourceEntry != null && m_obfDestEntry != null) {
416 if (m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry)) {
417 GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() {
418 @Override
419 public void actionPerformed(ActionEvent event) {
420 unmatch();
421 }
422 });
423 } else if (!m_memberMatches.isMatchedSourceEntry(m_obfSourceEntry) && !m_memberMatches.isMatchedDestEntry(m_obfDestEntry)) {
424 GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() {
425 @Override
426 public void actionPerformed(ActionEvent event) {
427 match();
428 }
429 });
430 }
431 } else if (m_obfSourceEntry != null) {
432 GuiTricks.activateButton(m_unmatchableButton, "Set Unmatchable", new ActionListener() {
433 @Override
434 public void actionPerformed(ActionEvent event) {
435 unmatchable();
436 }
437 });
438 }
439 }
440
441 protected void match() {
442
443 // update the field matches
444 m_memberMatches.makeMatch(m_obfSourceEntry, m_obfDestEntry);
445 save();
446
447 // update the ui
448 onSelectSource(null);
449 onSelectDest(null);
450 updateSourceHighlights();
451 updateDestHighlights();
452 updateSourceClasses();
453 }
454
455 protected void unmatch() {
456
457 // update the field matches
458 m_memberMatches.unmakeMatch(m_obfSourceEntry, m_obfDestEntry);
459 save();
460
461 // update the ui
462 onSelectSource(null);
463 onSelectDest(null);
464 updateSourceHighlights();
465 updateDestHighlights();
466 updateSourceClasses();
467 }
468
469 protected void unmatchable() {
470
471 // update the field matches
472 m_memberMatches.makeSourceUnmatchable(m_obfSourceEntry);
473 save();
474
475 // update the ui
476 onSelectSource(null);
477 onSelectDest(null);
478 updateSourceHighlights();
479 updateDestHighlights();
480 updateSourceClasses();
481 }
482
483 private void save() {
484 if (m_saveListener != null) {
485 m_saveListener.save(m_memberMatches);
486 }
487 }
488}
diff --git a/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java
index 4c3714a9..caaf99c3 100644
--- a/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java
+++ b/src/main/java/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java
@@ -4,18 +4,18 @@
4 * are made available under the terms of the GNU Lesser General Public 4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at 5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html 6 * http://www.gnu.org/licenses/lgpl.html
7 * 7 * <p>
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.gui; 11package cuchaz.enigma.gui;
12 12
13import java.awt.Color; 13import java.awt.Color;
14 14
15public class ObfuscatedHighlightPainter extends BoxHighlightPainter { 15public class ObfuscatedHighlightPainter extends BoxHighlightPainter {
16 16
17 public ObfuscatedHighlightPainter() { 17 public ObfuscatedHighlightPainter() {
18 // red ish 18 // red ish
19 super(new Color(255, 220, 220), new Color(160, 80, 80)); 19 super(new Color(255, 220, 220), new Color(160, 80, 80));
20 } 20 }
21} 21}
diff --git a/src/cuchaz/enigma/gui/OtherHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/OtherHighlightPainter.java
index 8d3fbe86..d2a2f02f 100644
--- a/src/cuchaz/enigma/gui/OtherHighlightPainter.java
+++ b/src/main/java/cuchaz/enigma/gui/OtherHighlightPainter.java
@@ -4,18 +4,18 @@
4 * are made available under the terms of the GNU Lesser General Public 4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at 5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html 6 * http://www.gnu.org/licenses/lgpl.html
7 * 7 * <p>
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.gui; 11package cuchaz.enigma.gui;
12 12
13import java.awt.Color; 13import java.awt.Color;
14 14
15public class OtherHighlightPainter extends BoxHighlightPainter { 15public class OtherHighlightPainter extends BoxHighlightPainter {
16 16
17 public OtherHighlightPainter() { 17 public OtherHighlightPainter() {
18 // grey 18 // grey
19 super(null, new Color(180, 180, 180)); 19 super(null, new Color(180, 180, 180));
20 } 20 }
21} 21}
diff --git a/src/main/java/cuchaz/enigma/gui/ProgressDialog.java b/src/main/java/cuchaz/enigma/gui/ProgressDialog.java
new file mode 100644
index 00000000..087d843b
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ProgressDialog.java
@@ -0,0 +1,100 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.Dimension;
16import java.awt.FlowLayout;
17
18import javax.swing.*;
19
20import cuchaz.enigma.Constants;
21import cuchaz.enigma.Deobfuscator.ProgressListener;
22
23public class ProgressDialog implements ProgressListener, AutoCloseable {
24
25 private JFrame m_frame;
26 private JLabel m_title;
27 private JLabel m_text;
28 private JProgressBar m_progress;
29
30 public ProgressDialog(JFrame parent) {
31
32 // init frame
33 m_frame = new JFrame(Constants.Name + " - Operation in progress");
34 final Container pane = m_frame.getContentPane();
35 FlowLayout layout = new FlowLayout();
36 layout.setAlignment(FlowLayout.LEFT);
37 pane.setLayout(layout);
38
39 m_title = new JLabel();
40 pane.add(m_title);
41
42 // set up the progress bar
43 JPanel panel = new JPanel();
44 pane.add(panel);
45 panel.setLayout(new BorderLayout());
46 m_text = GuiTricks.unboldLabel(new JLabel());
47 m_progress = new JProgressBar();
48 m_text.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
49 panel.add(m_text, BorderLayout.NORTH);
50 panel.add(m_progress, BorderLayout.CENTER);
51 panel.setPreferredSize(new Dimension(360, 50));
52
53 // show the frame
54 pane.doLayout();
55 m_frame.setSize(400, 120);
56 m_frame.setResizable(false);
57 m_frame.setLocationRelativeTo(parent);
58 m_frame.setVisible(true);
59 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
60 }
61
62 public void close() {
63 m_frame.dispose();
64 }
65
66 @Override
67 public void init(int totalWork, String title) {
68 m_title.setText(title);
69 m_progress.setMinimum(0);
70 m_progress.setMaximum(totalWork);
71 m_progress.setValue(0);
72 }
73
74 @Override
75 public void onProgress(int numDone, String message) {
76 m_text.setText(message);
77 m_progress.setValue(numDone);
78
79 // update the frame
80 m_frame.validate();
81 m_frame.repaint();
82 }
83
84 public interface ProgressRunnable {
85 void run(ProgressListener listener) throws Exception;
86 }
87
88 public static void runInThread(final JFrame parent, final ProgressRunnable runnable) {
89 new Thread() {
90 @Override
91 public void run() {
92 try (ProgressDialog progress = new ProgressDialog(parent)) {
93 runnable.run(progress);
94 } catch (Exception ex) {
95 throw new Error(ex);
96 }
97 }
98 }.start();
99 }
100}
diff --git a/src/main/java/cuchaz/enigma/gui/ReadableToken.java b/src/main/java/cuchaz/enigma/gui/ReadableToken.java
new file mode 100644
index 00000000..feec8c06
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/ReadableToken.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13public class ReadableToken {
14
15 public int line;
16 public int startColumn;
17 public int endColumn;
18
19 public ReadableToken(int line, int startColumn, int endColumn) {
20 this.line = line;
21 this.startColumn = startColumn;
22 this.endColumn = endColumn;
23 }
24
25 @Override
26 public String toString() {
27 StringBuilder buf = new StringBuilder();
28 buf.append("line ");
29 buf.append(line);
30 buf.append(" columns ");
31 buf.append(startColumn);
32 buf.append("-");
33 buf.append(endColumn);
34 return buf.toString();
35 }
36}
diff --git a/src/cuchaz/enigma/gui/RenameListener.java b/src/main/java/cuchaz/enigma/gui/RenameListener.java
index 8b515bbd..f0f9dcc4 100644
--- a/src/cuchaz/enigma/gui/RenameListener.java
+++ b/src/main/java/cuchaz/enigma/gui/RenameListener.java
@@ -4,14 +4,14 @@
4 * are made available under the terms of the GNU Lesser General Public 4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at 5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html 6 * http://www.gnu.org/licenses/lgpl.html
7 * 7 * <p>
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.gui; 11package cuchaz.enigma.gui;
12 12
13import cuchaz.enigma.mapping.Entry; 13import cuchaz.enigma.mapping.Entry;
14 14
15public interface RenameListener { 15public interface RenameListener {
16 void rename(Entry obfEntry, String newName); 16 void rename(Entry obfEntry, String newName);
17} 17}
diff --git a/src/cuchaz/enigma/gui/ScoredClassEntry.java b/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java
index 60704528..d1e2de0e 100644
--- a/src/cuchaz/enigma/gui/ScoredClassEntry.java
+++ b/src/main/java/cuchaz/enigma/gui/ScoredClassEntry.java
@@ -4,9 +4,9 @@
4 * are made available under the terms of the GNU Lesser General Public 4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at 5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html 6 * http://www.gnu.org/licenses/lgpl.html
7 * 7 * <p>
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.gui; 11package cuchaz.enigma.gui;
12 12
@@ -15,16 +15,16 @@ import cuchaz.enigma.mapping.ClassEntry;
15 15
16public class ScoredClassEntry extends ClassEntry { 16public class ScoredClassEntry extends ClassEntry {
17 17
18 private static final long serialVersionUID = -8798725308554217105L; 18 private static final long serialVersionUID = -8798725308554217105L;
19 19
20 private float m_score; 20 private float m_score;
21 21
22 public ScoredClassEntry(ClassEntry other, float score) { 22 public ScoredClassEntry(ClassEntry other, float score) {
23 super(other); 23 super(other);
24 m_score = score; 24 m_score = score;
25 } 25 }
26 26
27 public float getScore() { 27 public float getScore() {
28 return m_score; 28 return m_score;
29 } 29 }
30} 30}
diff --git a/src/cuchaz/enigma/gui/SelectionHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/SelectionHighlightPainter.java
index 4165da4a..fcad07cd 100644
--- a/src/cuchaz/enigma/gui/SelectionHighlightPainter.java
+++ b/src/main/java/cuchaz/enigma/gui/SelectionHighlightPainter.java
@@ -4,31 +4,26 @@
4 * are made available under the terms of the GNU Lesser General Public 4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at 5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html 6 * http://www.gnu.org/licenses/lgpl.html
7 * 7 * <p>
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.gui; 11package cuchaz.enigma.gui;
12 12
13import java.awt.BasicStroke; 13import java.awt.*;
14import java.awt.Color;
15import java.awt.Graphics;
16import java.awt.Graphics2D;
17import java.awt.Rectangle;
18import java.awt.Shape;
19 14
20import javax.swing.text.Highlighter; 15import javax.swing.text.Highlighter;
21import javax.swing.text.JTextComponent; 16import javax.swing.text.JTextComponent;
22 17
23public class SelectionHighlightPainter implements Highlighter.HighlightPainter { 18public class SelectionHighlightPainter implements Highlighter.HighlightPainter {
24 19
25 @Override 20 @Override
26 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { 21 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) {
27 // draw a thick border 22 // draw a thick border
28 Graphics2D g2d = (Graphics2D)g; 23 Graphics2D g2d = (Graphics2D) g;
29 Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end); 24 Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end);
30 g2d.setColor(Color.black); 25 g2d.setColor(Color.black);
31 g2d.setStroke(new BasicStroke(2.0f)); 26 g2d.setStroke(new BasicStroke(2.0f));
32 g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); 27 g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
33 } 28 }
34} 29}
diff --git a/src/cuchaz/enigma/gui/TokenListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java
index e4f7c873..efc8df8b 100644
--- a/src/cuchaz/enigma/gui/TokenListCellRenderer.java
+++ b/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java
@@ -4,9 +4,9 @@
4 * are made available under the terms of the GNU Lesser General Public 4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at 5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html 6 * http://www.gnu.org/licenses/lgpl.html
7 * 7 * <p>
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.gui; 11package cuchaz.enigma.gui;
12 12
@@ -20,19 +20,19 @@ import javax.swing.ListCellRenderer;
20import cuchaz.enigma.analysis.Token; 20import cuchaz.enigma.analysis.Token;
21 21
22public class TokenListCellRenderer implements ListCellRenderer<Token> { 22public class TokenListCellRenderer implements ListCellRenderer<Token> {
23 23
24 private GuiController m_controller; 24 private GuiController m_controller;
25 private DefaultListCellRenderer m_defaultRenderer; 25 private DefaultListCellRenderer m_defaultRenderer;
26 26
27 public TokenListCellRenderer(GuiController controller) { 27 public TokenListCellRenderer(GuiController controller) {
28 m_controller = controller; 28 m_controller = controller;
29 m_defaultRenderer = new DefaultListCellRenderer(); 29 m_defaultRenderer = new DefaultListCellRenderer();
30 } 30 }
31 31
32 @Override 32 @Override
33 public Component getListCellRendererComponent(JList<? extends Token> list, Token token, int index, boolean isSelected, boolean hasFocus) { 33 public Component getListCellRendererComponent(JList<? extends Token> list, Token token, int index, boolean isSelected, boolean hasFocus) {
34 JLabel label = (JLabel)m_defaultRenderer.getListCellRendererComponent(list, token, index, isSelected, hasFocus); 34 JLabel label = (JLabel) m_defaultRenderer.getListCellRendererComponent(list, token, index, isSelected, hasFocus);
35 label.setText(m_controller.getReadableToken(token).toString()); 35 label.setText(m_controller.getReadableToken(token).toString());
36 return label; 36 return label;
37 } 37 }
38} 38}
diff --git a/src/main/java/cuchaz/enigma/json/JsonArgument.java b/src/main/java/cuchaz/enigma/json/JsonArgument.java
new file mode 100644
index 00000000..4600c87b
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/json/JsonArgument.java
@@ -0,0 +1,20 @@
1package cuchaz.enigma.json;
2
3public class JsonArgument {
4
5 private int index;
6 private String name;
7
8 public JsonArgument(int index, String name) {
9 this.index = index;
10 this.name = name;
11 }
12
13 public int getIndex() {
14 return index;
15 }
16
17 public String getName() {
18 return name;
19 }
20}
diff --git a/src/main/java/cuchaz/enigma/json/JsonClass.java b/src/main/java/cuchaz/enigma/json/JsonClass.java
new file mode 100644
index 00000000..dc646bcb
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/json/JsonClass.java
@@ -0,0 +1,58 @@
1package cuchaz.enigma.json;
2
3import java.util.ArrayList;
4import java.util.List;
5
6public class JsonClass {
7 private String obf;
8 private String name;
9 private List<JsonField> field = new ArrayList<>();
10 private List<JsonConstructor> constructors = new ArrayList<>();
11 private List<JsonMethod> method = new ArrayList<>();
12 private List<JsonClass> innerClass = new ArrayList<>();
13
14 public JsonClass(String obf, String name) {
15 this.obf = obf;
16 this.name = name;
17 }
18
19 public void addField(JsonField jsonField) {
20 this.field.add(jsonField);
21 }
22
23 public void addConstructor(JsonConstructor jsonConstructor) {
24 this.constructors.add(jsonConstructor);
25 }
26
27 public void addMethod(JsonMethod jsonMethod) {
28 this.method.add(jsonMethod);
29 }
30
31 public void addInnerClass(JsonClass jsonInnerClass) {
32 this.innerClass.add(jsonInnerClass);
33 }
34
35 public String getObf() {
36 return obf;
37 }
38
39 public String getName() {
40 return name;
41 }
42
43 public List<JsonField> getField() {
44 return field;
45 }
46
47 public List<JsonConstructor> getConstructors() {
48 return constructors;
49 }
50
51 public List<JsonMethod> getMethod() {
52 return method;
53 }
54
55 public List<JsonClass> getInnerClass() {
56 return innerClass;
57 }
58}
diff --git a/src/main/java/cuchaz/enigma/json/JsonConstructor.java b/src/main/java/cuchaz/enigma/json/JsonConstructor.java
new file mode 100644
index 00000000..82307ae2
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/json/JsonConstructor.java
@@ -0,0 +1,15 @@
1package cuchaz.enigma.json;
2
3import java.util.List;
4
5public class JsonConstructor {
6 private String signature;
7 private List<JsonArgument> args;
8 private boolean statics;
9
10 public JsonConstructor(String signature, List<JsonArgument> args, boolean statics) {
11 this.signature=signature;
12 this.args = args;
13 this.statics = statics;
14 }
15}
diff --git a/src/main/java/cuchaz/enigma/json/JsonField.java b/src/main/java/cuchaz/enigma/json/JsonField.java
new file mode 100644
index 00000000..195f2874
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/json/JsonField.java
@@ -0,0 +1,25 @@
1package cuchaz.enigma.json;
2
3public class JsonField {
4 private String obf;
5 private String name;
6 private String type;
7
8 public JsonField(String obf, String name, String type) {
9 this.obf = obf;
10 this.name = name;
11 this.type=type;
12 }
13
14 public String getObf() {
15 return obf;
16 }
17
18 public String getName() {
19 return name;
20 }
21
22 public String getType() {
23 return type;
24 }
25}
diff --git a/src/main/java/cuchaz/enigma/json/JsonMethod.java b/src/main/java/cuchaz/enigma/json/JsonMethod.java
new file mode 100644
index 00000000..7ec44806
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/json/JsonMethod.java
@@ -0,0 +1,33 @@
1package cuchaz.enigma.json;
2
3import java.util.List;
4
5public class JsonMethod {
6 private String obf;
7 private String name;
8 private String signature;
9 private List<JsonArgument> args;
10
11 public JsonMethod(String obf, String name, String signature, List<JsonArgument> args) {
12 this.obf = obf;
13 this.name = name;
14 this.signature = signature;
15 this.args = args;
16 }
17
18 public String getObf() {
19 return obf;
20 }
21
22 public String getName() {
23 return name;
24 }
25
26 public String getSignature() {
27 return signature;
28 }
29
30 public List<JsonArgument> getArgs() {
31 return args;
32 }
33}
diff --git a/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java b/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java
new file mode 100644
index 00000000..886e7be2
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java
@@ -0,0 +1,116 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class ArgumentEntry implements Entry, Serializable {
18
19 private static final long serialVersionUID = 4472172468162696006L;
20
21 private BehaviorEntry m_behaviorEntry;
22 private int m_index;
23 private String m_name;
24
25 public ArgumentEntry(BehaviorEntry behaviorEntry, int index, String name) {
26 if (behaviorEntry == null) {
27 throw new IllegalArgumentException("Behavior cannot be null!");
28 }
29 if (index < 0) {
30 throw new IllegalArgumentException("Index must be non-negative!");
31 }
32 if (name == null) {
33 throw new IllegalArgumentException("Argument name cannot be null!");
34 }
35
36 m_behaviorEntry = behaviorEntry;
37 m_index = index;
38 m_name = name;
39 }
40
41 public ArgumentEntry(ArgumentEntry other) {
42 m_behaviorEntry = (BehaviorEntry) m_behaviorEntry.cloneToNewClass(getClassEntry());
43 m_index = other.m_index;
44 m_name = other.m_name;
45 }
46
47 public ArgumentEntry(ArgumentEntry other, String newClassName) {
48 m_behaviorEntry = (BehaviorEntry) other.m_behaviorEntry.cloneToNewClass(new ClassEntry(newClassName));
49 m_index = other.m_index;
50 m_name = other.m_name;
51 }
52
53 public BehaviorEntry getBehaviorEntry() {
54 return m_behaviorEntry;
55 }
56
57 public int getIndex() {
58 return m_index;
59 }
60
61 @Override
62 public String getName() {
63 return m_name;
64 }
65
66 @Override
67 public ClassEntry getClassEntry() {
68 return m_behaviorEntry.getClassEntry();
69 }
70
71 @Override
72 public String getClassName() {
73 return m_behaviorEntry.getClassName();
74 }
75
76 @Override
77 public ArgumentEntry cloneToNewClass(ClassEntry classEntry) {
78 return new ArgumentEntry(this, classEntry.getName());
79 }
80
81 public String getMethodName() {
82 return m_behaviorEntry.getName();
83 }
84
85 public Signature getMethodSignature() {
86 return m_behaviorEntry.getSignature();
87 }
88
89 @Override
90 public int hashCode() {
91 return Util.combineHashesOrdered(
92 m_behaviorEntry,
93 Integer.valueOf(m_index).hashCode(),
94 m_name.hashCode()
95 );
96 }
97
98 @Override
99 public boolean equals(Object other) {
100 if (other instanceof ArgumentEntry) {
101 return equals((ArgumentEntry) other);
102 }
103 return false;
104 }
105
106 public boolean equals(ArgumentEntry other) {
107 return m_behaviorEntry.equals(other.m_behaviorEntry)
108 && m_index == other.m_index
109 && m_name.equals(other.m_name);
110 }
111
112 @Override
113 public String toString() {
114 return m_behaviorEntry.toString() + "(" + m_index + ":" + m_name + ")";
115 }
116}
diff --git a/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java b/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java
new file mode 100644
index 00000000..2b77d6ea
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java
@@ -0,0 +1,49 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15public class ArgumentMapping implements Serializable, Comparable<ArgumentMapping> {
16
17 private static final long serialVersionUID = 8610742471440861315L;
18
19 private int m_index;
20 private String m_name;
21
22 // NOTE: this argument order is important for the MethodReader/MethodWriter
23 public ArgumentMapping(int index, String name) {
24 m_index = index;
25 m_name = NameValidator.validateArgumentName(name);
26 }
27
28 public ArgumentMapping(ArgumentMapping other) {
29 m_index = other.m_index;
30 m_name = other.m_name;
31 }
32
33 public int getIndex() {
34 return m_index;
35 }
36
37 public String getName() {
38 return m_name;
39 }
40
41 public void setName(String val) {
42 m_name = NameValidator.validateArgumentName(val);
43 }
44
45 @Override
46 public int compareTo(ArgumentMapping other) {
47 return Integer.compare(m_index, other.m_index);
48 }
49}
diff --git a/src/cuchaz/enigma/mapping/BehaviorEntry.java b/src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java
index 031d2670..f5c6c051 100644
--- a/src/cuchaz/enigma/mapping/BehaviorEntry.java
+++ b/src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java
@@ -4,12 +4,12 @@
4 * are made available under the terms of the GNU Lesser General Public 4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at 5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html 6 * http://www.gnu.org/licenses/lgpl.html
7 * 7 * <p>
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.mapping; 11package cuchaz.enigma.mapping;
12 12
13public interface BehaviorEntry extends Entry { 13public interface BehaviorEntry extends Entry {
14 Signature getSignature(); 14 Signature getSignature();
15} 15}
diff --git a/src/main/java/cuchaz/enigma/mapping/ClassEntry.java b/src/main/java/cuchaz/enigma/mapping/ClassEntry.java
new file mode 100644
index 00000000..2e7711b7
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/ClassEntry.java
@@ -0,0 +1,172 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import com.google.common.collect.Lists;
14
15import java.io.Serializable;
16import java.util.List;
17
18public class ClassEntry implements Entry, Serializable {
19
20 private static final long serialVersionUID = 4235460580973955811L;
21
22 private String m_name;
23
24 public ClassEntry(String className) {
25 if (className == null) {
26 throw new IllegalArgumentException("Class name cannot be null!");
27 }
28 if (className.indexOf('.') >= 0) {
29 throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className);
30 }
31
32 m_name = className;
33
34 if (isInnerClass() && getInnermostClassName().indexOf('/') >= 0) {
35 throw new IllegalArgumentException("Inner class must not have a package: " + className);
36 }
37 }
38
39 public ClassEntry(ClassEntry other) {
40 m_name = other.m_name;
41 }
42
43 @Override
44 public String getName() {
45 return m_name;
46 }
47
48 @Override
49 public String getClassName() {
50 return m_name;
51 }
52
53 @Override
54 public ClassEntry getClassEntry() {
55 return this;
56 }
57
58 @Override
59 public ClassEntry cloneToNewClass(ClassEntry classEntry) {
60 return classEntry;
61 }
62
63 @Override
64 public int hashCode() {
65 return m_name.hashCode();
66 }
67
68 @Override
69 public boolean equals(Object other) {
70 if (other instanceof ClassEntry) {
71 return equals((ClassEntry) other);
72 }
73 return false;
74 }
75
76 public boolean equals(ClassEntry other) {
77 return m_name.equals(other.m_name);
78 }
79
80 @Override
81 public String toString() {
82 return m_name;
83 }
84
85 public boolean isInnerClass() {
86 return m_name.lastIndexOf('$') >= 0;
87 }
88
89 public List<String> getClassChainNames() {
90 return Lists.newArrayList(m_name.split("\\$"));
91 }
92
93 public List<ClassEntry> getClassChain() {
94 List<ClassEntry> entries = Lists.newArrayList();
95 StringBuilder buf = new StringBuilder();
96 for (String name : getClassChainNames()) {
97 if (buf.length() > 0) {
98 buf.append("$");
99 }
100 buf.append(name);
101 entries.add(new ClassEntry(buf.toString()));
102 }
103 return entries;
104 }
105
106 public String getOutermostClassName() {
107 if (isInnerClass()) {
108 return m_name.substring(0, m_name.indexOf('$'));
109 }
110 return m_name;
111 }
112
113 public ClassEntry getOutermostClassEntry() {
114 return new ClassEntry(getOutermostClassName());
115 }
116
117 public String getOuterClassName() {
118 if (!isInnerClass()) {
119 throw new Error("This is not an inner class!");
120 }
121 return m_name.substring(0, m_name.lastIndexOf('$'));
122 }
123
124 public ClassEntry getOuterClassEntry() {
125 return new ClassEntry(getOuterClassName());
126 }
127
128 public String getInnermostClassName() {
129 if (!isInnerClass()) {
130 throw new Error("This is not an inner class!");
131 }
132 return m_name.substring(m_name.lastIndexOf('$') + 1);
133 }
134
135 public boolean isInDefaultPackage() {
136 return m_name.indexOf('/') < 0;
137 }
138
139 public String getPackageName() {
140 int pos = m_name.lastIndexOf('/');
141 if (pos > 0) {
142 return m_name.substring(0, pos);
143 }
144 return null;
145 }
146
147 public String getSimpleName() {
148 int pos = m_name.lastIndexOf('/');
149 if (pos > 0) {
150 return m_name.substring(pos + 1);
151 }
152 return m_name;
153 }
154
155 public ClassEntry buildClassEntry(List<ClassEntry> classChain) {
156 assert (classChain.contains(this));
157 StringBuilder buf = new StringBuilder();
158 for (ClassEntry chainEntry : classChain) {
159 if (buf.length() == 0) {
160 buf.append(chainEntry.getName());
161 } else {
162 buf.append("$");
163 buf.append(chainEntry.isInnerClass() ? chainEntry.getInnermostClassName() : chainEntry.getSimpleName());
164 }
165
166 if (chainEntry == this) {
167 break;
168 }
169 }
170 return new ClassEntry(buf.toString());
171 }
172}
diff --git a/src/main/java/cuchaz/enigma/mapping/ClassMapping.java b/src/main/java/cuchaz/enigma/mapping/ClassMapping.java
new file mode 100644
index 00000000..9258ec78
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/ClassMapping.java
@@ -0,0 +1,460 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import com.google.common.collect.Maps;
14
15import java.io.Serializable;
16import java.util.ArrayList;
17import java.util.Map;
18
19public class ClassMapping implements Serializable, Comparable<ClassMapping> {
20
21 private static final long serialVersionUID = -5148491146902340107L;
22
23 private String m_obfFullName;
24 private String m_obfSimpleName;
25 private String m_deobfName;
26 private Map<String, ClassMapping> m_innerClassesByObfSimple;
27 private Map<String, ClassMapping> m_innerClassesByDeobf;
28 private Map<String, FieldMapping> m_fieldsByObf;
29 private Map<String, FieldMapping> m_fieldsByDeobf;
30 private Map<String, MethodMapping> m_methodsByObf;
31 private Map<String, MethodMapping> m_methodsByDeobf;
32
33 public ClassMapping(String obfFullName) {
34 this(obfFullName, null);
35 }
36
37 public ClassMapping(String obfFullName, String deobfName) {
38 m_obfFullName = obfFullName;
39 ClassEntry classEntry = new ClassEntry(obfFullName);
40 m_obfSimpleName = classEntry.isInnerClass() ? classEntry.getInnermostClassName() : classEntry.getSimpleName();
41 m_deobfName = NameValidator.validateClassName(deobfName, false);
42 m_innerClassesByObfSimple = Maps.newHashMap();
43 m_innerClassesByDeobf = Maps.newHashMap();
44 m_fieldsByObf = Maps.newHashMap();
45 m_fieldsByDeobf = Maps.newHashMap();
46 m_methodsByObf = Maps.newHashMap();
47 m_methodsByDeobf = Maps.newHashMap();
48 }
49
50 public String getObfFullName() {
51 return m_obfFullName;
52 }
53
54 public String getObfSimpleName() {
55 return m_obfSimpleName;
56 }
57
58 public String getDeobfName() {
59 return m_deobfName;
60 }
61
62 public void setDeobfName(String val) {
63 m_deobfName = NameValidator.validateClassName(val, false);
64 }
65
66 //// INNER CLASSES ////////
67
68 public Iterable<ClassMapping> innerClasses() {
69 assert (m_innerClassesByObfSimple.size() >= m_innerClassesByDeobf.size());
70 return m_innerClassesByObfSimple.values();
71 }
72
73 public void addInnerClassMapping(ClassMapping classMapping) {
74 boolean obfWasAdded = m_innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping) == null;
75 assert (obfWasAdded);
76 if (classMapping.getDeobfName() != null) {
77 assert (isSimpleClassName(classMapping.getDeobfName()));
78 boolean deobfWasAdded = m_innerClassesByDeobf.put(classMapping.getDeobfName(), classMapping) == null;
79 assert (deobfWasAdded);
80 }
81 }
82
83 public void removeInnerClassMapping(ClassMapping classMapping) {
84 boolean obfWasRemoved = m_innerClassesByObfSimple.remove(classMapping.getObfSimpleName()) != null;
85 assert (obfWasRemoved);
86 if (classMapping.getDeobfName() != null) {
87 boolean deobfWasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null;
88 assert (deobfWasRemoved);
89 }
90 }
91
92 public ClassMapping getOrCreateInnerClass(ClassEntry obfInnerClass) {
93 ClassMapping classMapping = m_innerClassesByObfSimple.get(obfInnerClass.getInnermostClassName());
94 if (classMapping == null) {
95 classMapping = new ClassMapping(obfInnerClass.getName());
96 boolean wasAdded = m_innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping) == null;
97 assert (wasAdded);
98 }
99 return classMapping;
100 }
101
102 public ClassMapping getInnerClassByObfSimple(String obfSimpleName) {
103 assert (isSimpleClassName(obfSimpleName));
104 return m_innerClassesByObfSimple.get(obfSimpleName);
105 }
106
107 public ClassMapping getInnerClassByDeobf(String deobfName) {
108 assert (isSimpleClassName(deobfName));
109 return m_innerClassesByDeobf.get(deobfName);
110 }
111
112 public ClassMapping getInnerClassByDeobfThenObfSimple(String name) {
113 ClassMapping classMapping = getInnerClassByDeobf(name);
114 if (classMapping == null) {
115 classMapping = getInnerClassByObfSimple(name);
116 }
117 return classMapping;
118 }
119
120 public String getDeobfInnerClassName(String obfSimpleName) {
121 assert (isSimpleClassName(obfSimpleName));
122 ClassMapping classMapping = m_innerClassesByObfSimple.get(obfSimpleName);
123 if (classMapping != null) {
124 return classMapping.getDeobfName();
125 }
126 return null;
127 }
128
129 public void setInnerClassName(ClassEntry obfInnerClass, String deobfName) {
130 ClassMapping classMapping = getOrCreateInnerClass(obfInnerClass);
131 if (classMapping.getDeobfName() != null) {
132 boolean wasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null;
133 assert (wasRemoved);
134 }
135 classMapping.setDeobfName(deobfName);
136 if (deobfName != null) {
137 assert (isSimpleClassName(deobfName));
138 boolean wasAdded = m_innerClassesByDeobf.put(deobfName, classMapping) == null;
139 assert (wasAdded);
140 }
141 }
142
143 public boolean hasInnerClassByObfSimple(String obfSimpleName) {
144 return m_innerClassesByObfSimple.containsKey(obfSimpleName);
145 }
146
147 public boolean hasInnerClassByDeobf(String deobfName) {
148 return m_innerClassesByDeobf.containsKey(deobfName);
149 }
150
151
152 //// FIELDS ////////
153
154 public Iterable<FieldMapping> fields() {
155 assert (m_fieldsByObf.size() == m_fieldsByDeobf.size());
156 return m_fieldsByObf.values();
157 }
158
159 public boolean containsObfField(String obfName, Type obfType) {
160 return m_fieldsByObf.containsKey(getFieldKey(obfName, obfType));
161 }
162
163 public boolean containsDeobfField(String deobfName, Type deobfType) {
164 return m_fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfType));
165 }
166
167 public void addFieldMapping(FieldMapping fieldMapping) {
168 String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType());
169 if (m_fieldsByObf.containsKey(obfKey)) {
170 throw new Error("Already have mapping for " + m_obfFullName + "." + obfKey);
171 }
172 String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType());
173 if (m_fieldsByDeobf.containsKey(deobfKey)) {
174 throw new Error("Already have mapping for " + m_deobfName + "." + deobfKey);
175 }
176 boolean obfWasAdded = m_fieldsByObf.put(obfKey, fieldMapping) == null;
177 assert (obfWasAdded);
178 boolean deobfWasAdded = m_fieldsByDeobf.put(deobfKey, fieldMapping) == null;
179 assert (deobfWasAdded);
180 assert (m_fieldsByObf.size() == m_fieldsByDeobf.size());
181 }
182
183 public void removeFieldMapping(FieldMapping fieldMapping) {
184 boolean obfWasRemoved = m_fieldsByObf.remove(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType())) != null;
185 assert (obfWasRemoved);
186 if (fieldMapping.getDeobfName() != null) {
187 boolean deobfWasRemoved = m_fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType())) != null;
188 assert (deobfWasRemoved);
189 }
190 }
191
192 public FieldMapping getFieldByObf(String obfName, Type obfType) {
193 return m_fieldsByObf.get(getFieldKey(obfName, obfType));
194 }
195
196 public FieldMapping getFieldByDeobf(String deobfName, Type obfType) {
197 return m_fieldsByDeobf.get(getFieldKey(deobfName, obfType));
198 }
199
200 public String getObfFieldName(String deobfName, Type obfType) {
201 FieldMapping fieldMapping = m_fieldsByDeobf.get(getFieldKey(deobfName, obfType));
202 if (fieldMapping != null) {
203 return fieldMapping.getObfName();
204 }
205 return null;
206 }
207
208 public String getDeobfFieldName(String obfName, Type obfType) {
209 FieldMapping fieldMapping = m_fieldsByObf.get(getFieldKey(obfName, obfType));
210 if (fieldMapping != null) {
211 return fieldMapping.getDeobfName();
212 }
213 return null;
214 }
215
216 private String getFieldKey(String name, Type type) {
217 if (name == null) {
218 throw new IllegalArgumentException("name cannot be null!");
219 }
220 if (type == null) {
221 throw new IllegalArgumentException("type cannot be null!");
222 }
223 return name + ":" + type;
224 }
225
226
227 public void setFieldName(String obfName, Type obfType, String deobfName) {
228 assert (deobfName != null);
229 FieldMapping fieldMapping = m_fieldsByObf.get(getFieldKey(obfName, obfType));
230 if (fieldMapping == null) {
231 fieldMapping = new FieldMapping(obfName, obfType, deobfName);
232 boolean obfWasAdded = m_fieldsByObf.put(getFieldKey(obfName, obfType), fieldMapping) == null;
233 assert (obfWasAdded);
234 } else {
235 boolean wasRemoved = m_fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), obfType)) != null;
236 assert (wasRemoved);
237 }
238 fieldMapping.setDeobfName(deobfName);
239 if (deobfName != null) {
240 boolean wasAdded = m_fieldsByDeobf.put(getFieldKey(deobfName, obfType), fieldMapping) == null;
241 assert (wasAdded);
242 }
243 }
244
245 public void setFieldObfNameAndType(String oldObfName, Type obfType, String newObfName, Type newObfType) {
246 assert (newObfName != null);
247 FieldMapping fieldMapping = m_fieldsByObf.remove(getFieldKey(oldObfName, obfType));
248 assert (fieldMapping != null);
249 fieldMapping.setObfName(newObfName);
250 fieldMapping.setObfType(newObfType);
251 boolean obfWasAdded = m_fieldsByObf.put(getFieldKey(newObfName, newObfType), fieldMapping) == null;
252 assert (obfWasAdded);
253 }
254
255
256 //// METHODS ////////
257
258 public Iterable<MethodMapping> methods() {
259 assert (m_methodsByObf.size() >= m_methodsByDeobf.size());
260 return m_methodsByObf.values();
261 }
262
263 public boolean containsObfMethod(String obfName, Signature obfSignature) {
264 return m_methodsByObf.containsKey(getMethodKey(obfName, obfSignature));
265 }
266
267 public boolean containsDeobfMethod(String deobfName, Signature obfSignature) {
268 return m_methodsByDeobf.containsKey(getMethodKey(deobfName, obfSignature));
269 }
270
271 public void addMethodMapping(MethodMapping methodMapping) {
272 String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature());
273 if (m_methodsByObf.containsKey(obfKey)) {
274 throw new Error("Already have mapping for " + m_obfFullName + "." + obfKey);
275 }
276 boolean wasAdded = m_methodsByObf.put(obfKey, methodMapping) == null;
277 assert (wasAdded);
278 if (methodMapping.getDeobfName() != null) {
279 String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature());
280 if (m_methodsByDeobf.containsKey(deobfKey)) {
281 throw new Error("Already have mapping for " + m_deobfName + "." + deobfKey);
282 }
283 boolean deobfWasAdded = m_methodsByDeobf.put(deobfKey, methodMapping) == null;
284 assert (deobfWasAdded);
285 }
286 assert (m_methodsByObf.size() >= m_methodsByDeobf.size());
287 }
288
289 public void removeMethodMapping(MethodMapping methodMapping) {
290 boolean obfWasRemoved = m_methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature())) != null;
291 assert (obfWasRemoved);
292 if (methodMapping.getDeobfName() != null) {
293 boolean deobfWasRemoved = m_methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null;
294 assert (deobfWasRemoved);
295 }
296 }
297
298 public MethodMapping getMethodByObf(String obfName, Signature obfSignature) {
299 return m_methodsByObf.get(getMethodKey(obfName, obfSignature));
300 }
301
302 public MethodMapping getMethodByDeobf(String deobfName, Signature obfSignature) {
303 return m_methodsByDeobf.get(getMethodKey(deobfName, obfSignature));
304 }
305
306 private String getMethodKey(String name, Signature signature) {
307 if (name == null) {
308 throw new IllegalArgumentException("name cannot be null!");
309 }
310 if (signature == null) {
311 throw new IllegalArgumentException("signature cannot be null!");
312 }
313 return name + signature;
314 }
315
316 public void setMethodName(String obfName, Signature obfSignature, String deobfName) {
317 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfName, obfSignature));
318 if (methodMapping == null) {
319 methodMapping = createMethodMapping(obfName, obfSignature);
320 } else if (methodMapping.getDeobfName() != null) {
321 boolean wasRemoved = m_methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null;
322 assert (wasRemoved);
323 }
324 methodMapping.setDeobfName(deobfName);
325 if (deobfName != null) {
326 boolean wasAdded = m_methodsByDeobf.put(getMethodKey(deobfName, obfSignature), methodMapping) == null;
327 assert (wasAdded);
328 }
329 }
330
331 public void setMethodObfNameAndSignature(String oldObfName, Signature obfSignature, String newObfName, Signature newObfSignature) {
332 assert (newObfName != null);
333 MethodMapping methodMapping = m_methodsByObf.remove(getMethodKey(oldObfName, obfSignature));
334 assert (methodMapping != null);
335 methodMapping.setObfName(newObfName);
336 methodMapping.setObfSignature(newObfSignature);
337 boolean obfWasAdded = m_methodsByObf.put(getMethodKey(newObfName, newObfSignature), methodMapping) == null;
338 assert (obfWasAdded);
339 }
340
341 //// ARGUMENTS ////////
342
343 public void setArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex, String argumentName) {
344 assert (argumentName != null);
345 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature));
346 if (methodMapping == null) {
347 methodMapping = createMethodMapping(obfMethodName, obfMethodSignature);
348 }
349 methodMapping.setArgumentName(argumentIndex, argumentName);
350 }
351
352 public void removeArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex) {
353 m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)).removeArgumentName(argumentIndex);
354 }
355
356 private MethodMapping createMethodMapping(String obfName, Signature obfSignature) {
357 MethodMapping methodMapping = new MethodMapping(obfName, obfSignature);
358 boolean wasAdded = m_methodsByObf.put(getMethodKey(obfName, obfSignature), methodMapping) == null;
359 assert (wasAdded);
360 return methodMapping;
361 }
362
363 @Override
364 public String toString() {
365 StringBuilder buf = new StringBuilder();
366 buf.append(m_obfFullName);
367 buf.append(" <-> ");
368 buf.append(m_deobfName);
369 buf.append("\n");
370 buf.append("Fields:\n");
371 for (FieldMapping fieldMapping : fields()) {
372 buf.append("\t");
373 buf.append(fieldMapping.getObfName());
374 buf.append(" <-> ");
375 buf.append(fieldMapping.getDeobfName());
376 buf.append("\n");
377 }
378 buf.append("Methods:\n");
379 for (MethodMapping methodMapping : m_methodsByObf.values()) {
380 buf.append(methodMapping.toString());
381 buf.append("\n");
382 }
383 buf.append("Inner Classes:\n");
384 for (ClassMapping classMapping : m_innerClassesByObfSimple.values()) {
385 buf.append("\t");
386 buf.append(classMapping.getObfSimpleName());
387 buf.append(" <-> ");
388 buf.append(classMapping.getDeobfName());
389 buf.append("\n");
390 }
391 return buf.toString();
392 }
393
394 @Override
395 public int compareTo(ClassMapping other) {
396 // sort by a, b, c, ... aa, ab, etc
397 if (m_obfFullName.length() != other.m_obfFullName.length()) {
398 return m_obfFullName.length() - other.m_obfFullName.length();
399 }
400 return m_obfFullName.compareTo(other.m_obfFullName);
401 }
402
403 public boolean renameObfClass(String oldObfClassName, String newObfClassName) {
404
405 // rename inner classes
406 for (ClassMapping innerClassMapping : new ArrayList<ClassMapping>(m_innerClassesByObfSimple.values())) {
407 if (innerClassMapping.renameObfClass(oldObfClassName, newObfClassName)) {
408 boolean wasRemoved = m_innerClassesByObfSimple.remove(oldObfClassName) != null;
409 assert (wasRemoved);
410 boolean wasAdded = m_innerClassesByObfSimple.put(newObfClassName, innerClassMapping) == null;
411 assert (wasAdded);
412 }
413 }
414
415 // rename field types
416 for (FieldMapping fieldMapping : new ArrayList<FieldMapping>(m_fieldsByObf.values())) {
417 String oldFieldKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType());
418 if (fieldMapping.renameObfClass(oldObfClassName, newObfClassName)) {
419 boolean wasRemoved = m_fieldsByObf.remove(oldFieldKey) != null;
420 assert (wasRemoved);
421 boolean wasAdded = m_fieldsByObf.put(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()), fieldMapping) == null;
422 assert (wasAdded);
423 }
424 }
425
426 // rename method signatures
427 for (MethodMapping methodMapping : new ArrayList<MethodMapping>(m_methodsByObf.values())) {
428 String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature());
429 if (methodMapping.renameObfClass(oldObfClassName, newObfClassName)) {
430 boolean wasRemoved = m_methodsByObf.remove(oldMethodKey) != null;
431 assert (wasRemoved);
432 boolean wasAdded = m_methodsByObf.put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()), methodMapping) == null;
433 assert (wasAdded);
434 }
435 }
436
437 if (m_obfFullName.equals(oldObfClassName)) {
438 // rename this class
439 m_obfFullName = newObfClassName;
440 return true;
441 }
442 return false;
443 }
444
445 public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) {
446 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfBehaviorEntry.getName(), obfBehaviorEntry.getSignature()));
447 if (methodMapping != null) {
448 return methodMapping.containsArgument(name);
449 }
450 return false;
451 }
452
453 public static boolean isSimpleClassName(String name) {
454 return name.indexOf('/') < 0 && name.indexOf('$') < 0;
455 }
456
457 public ClassEntry getObfEntry() {
458 return new ClassEntry(m_obfFullName);
459 }
460}
diff --git a/src/cuchaz/enigma/mapping/ClassNameReplacer.java b/src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java
index f00d811e..dc833bb8 100644
--- a/src/cuchaz/enigma/mapping/ClassNameReplacer.java
+++ b/src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java
@@ -4,12 +4,12 @@
4 * are made available under the terms of the GNU Lesser General Public 4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at 5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html 6 * http://www.gnu.org/licenses/lgpl.html
7 * 7 * <p>
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.mapping; 11package cuchaz.enigma.mapping;
12 12
13public interface ClassNameReplacer { 13public interface ClassNameReplacer {
14 String replace(String className); 14 String replace(String className);
15} 15}
diff --git a/src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java b/src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java
new file mode 100644
index 00000000..907bd4ce
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java
@@ -0,0 +1,116 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class ConstructorEntry implements BehaviorEntry, Serializable {
18
19 private static final long serialVersionUID = -868346075317366758L;
20
21 private ClassEntry m_classEntry;
22 private Signature m_signature;
23
24 public ConstructorEntry(ClassEntry classEntry) {
25 this(classEntry, null);
26 }
27
28 public ConstructorEntry(ClassEntry classEntry, Signature signature) {
29 if (classEntry == null) {
30 throw new IllegalArgumentException("Class cannot be null!");
31 }
32
33 m_classEntry = classEntry;
34 m_signature = signature;
35 }
36
37 public ConstructorEntry(ConstructorEntry other) {
38 m_classEntry = new ClassEntry(other.m_classEntry);
39 m_signature = other.m_signature;
40 }
41
42 public ConstructorEntry(ConstructorEntry other, String newClassName) {
43 m_classEntry = new ClassEntry(newClassName);
44 m_signature = other.m_signature;
45 }
46
47 @Override
48 public ClassEntry getClassEntry() {
49 return m_classEntry;
50 }
51
52 @Override
53 public String getName() {
54 if (isStatic()) {
55 return "<clinit>";
56 }
57 return "<init>";
58 }
59
60 public boolean isStatic() {
61 return m_signature == null;
62 }
63
64 @Override
65 public Signature getSignature() {
66 return m_signature;
67 }
68
69 @Override
70 public String getClassName() {
71 return m_classEntry.getName();
72 }
73
74 @Override
75 public ConstructorEntry cloneToNewClass(ClassEntry classEntry) {
76 return new ConstructorEntry(this, classEntry.getName());
77 }
78
79 @Override
80 public int hashCode() {
81 if (isStatic()) {
82 return Util.combineHashesOrdered(m_classEntry);
83 } else {
84 return Util.combineHashesOrdered(m_classEntry, m_signature);
85 }
86 }
87
88 @Override
89 public boolean equals(Object other) {
90 if (other instanceof ConstructorEntry) {
91 return equals((ConstructorEntry) other);
92 }
93 return false;
94 }
95
96 public boolean equals(ConstructorEntry other) {
97 if (isStatic() != other.isStatic()) {
98 return false;
99 }
100
101 if (isStatic()) {
102 return m_classEntry.equals(other.m_classEntry);
103 } else {
104 return m_classEntry.equals(other.m_classEntry) && m_signature.equals(other.m_signature);
105 }
106 }
107
108 @Override
109 public String toString() {
110 if (isStatic()) {
111 return m_classEntry.getName() + "." + getName();
112 } else {
113 return m_classEntry.getName() + "." + getName() + m_signature;
114 }
115 }
116}
diff --git a/src/main/java/cuchaz/enigma/mapping/Entry.java b/src/main/java/cuchaz/enigma/mapping/Entry.java
new file mode 100644
index 00000000..95747d55
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/Entry.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public interface Entry {
14 String getName();
15
16 String getClassName();
17
18 ClassEntry getClassEntry();
19
20 Entry cloneToNewClass(ClassEntry classEntry);
21}
diff --git a/src/main/java/cuchaz/enigma/mapping/EntryFactory.java b/src/main/java/cuchaz/enigma/mapping/EntryFactory.java
new file mode 100644
index 00000000..bd6ce4ec
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/EntryFactory.java
@@ -0,0 +1,162 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import cuchaz.enigma.analysis.JarIndex;
14import javassist.*;
15import javassist.bytecode.Descriptor;
16import javassist.expr.ConstructorCall;
17import javassist.expr.FieldAccess;
18import javassist.expr.MethodCall;
19import javassist.expr.NewExpr;
20
21public class EntryFactory {
22
23 public static ClassEntry getClassEntry(CtClass c) {
24 return new ClassEntry(Descriptor.toJvmName(c.getName()));
25 }
26
27 public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) {
28 ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfFullName());
29 return obfClassEntry.buildClassEntry(jarIndex.getObfClassChain(obfClassEntry));
30 }
31
32 private static ClassEntry getObfClassEntry(ClassMapping classMapping) {
33 return new ClassEntry(classMapping.getObfFullName());
34 }
35
36 public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) {
37 return new ClassEntry(classMapping.getDeobfName());
38 }
39
40 public static ClassEntry getSuperclassEntry(CtClass c) {
41 return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
42 }
43
44 public static FieldEntry getFieldEntry(CtField field) {
45 return new FieldEntry(
46 getClassEntry(field.getDeclaringClass()),
47 field.getName(),
48 new Type(field.getFieldInfo().getDescriptor())
49 );
50 }
51
52 public static FieldEntry getFieldEntry(FieldAccess call) {
53 return new FieldEntry(
54 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
55 call.getFieldName(),
56 new Type(call.getSignature())
57 );
58 }
59
60 public static FieldEntry getFieldEntry(String className, String name, String type) {
61 return new FieldEntry(new ClassEntry(className), name, new Type(type));
62 }
63
64 public static FieldEntry getObfFieldEntry(ClassMapping classMapping, FieldMapping fieldMapping) {
65 return new FieldEntry(
66 getObfClassEntry(classMapping),
67 fieldMapping.getObfName(),
68 fieldMapping.getObfType()
69 );
70 }
71
72 public static MethodEntry getMethodEntry(CtMethod method) {
73 return new MethodEntry(
74 getClassEntry(method.getDeclaringClass()),
75 method.getName(),
76 new Signature(method.getMethodInfo().getDescriptor())
77 );
78 }
79
80 public static MethodEntry getMethodEntry(MethodCall call) {
81 return new MethodEntry(
82 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
83 call.getMethodName(),
84 new Signature(call.getSignature())
85 );
86 }
87
88 public static ConstructorEntry getConstructorEntry(CtConstructor constructor) {
89 if (constructor.isClassInitializer()) {
90 return new ConstructorEntry(
91 getClassEntry(constructor.getDeclaringClass())
92 );
93 } else {
94 return new ConstructorEntry(
95 getClassEntry(constructor.getDeclaringClass()),
96 new Signature(constructor.getMethodInfo().getDescriptor())
97 );
98 }
99 }
100
101 public static ConstructorEntry getConstructorEntry(ConstructorCall call) {
102 return new ConstructorEntry(
103 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
104 new Signature(call.getSignature())
105 );
106 }
107
108 public static ConstructorEntry getConstructorEntry(NewExpr call) {
109 return new ConstructorEntry(
110 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
111 new Signature(call.getSignature())
112 );
113 }
114
115 public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) {
116 if (behavior instanceof CtMethod) {
117 return getMethodEntry((CtMethod) behavior);
118 } else if (behavior instanceof CtConstructor) {
119 return getConstructorEntry((CtConstructor) behavior);
120 }
121 throw new Error("behavior is neither Method nor Constructor!");
122 }
123
124 public static BehaviorEntry getBehaviorEntry(String className, String behaviorName, String behaviorSignature) {
125 return getBehaviorEntry(new ClassEntry(className), behaviorName, new Signature(behaviorSignature));
126 }
127
128 public static BehaviorEntry getBehaviorEntry(String className, String behaviorName) {
129 return getBehaviorEntry(new ClassEntry(className), behaviorName);
130 }
131
132 public static BehaviorEntry getBehaviorEntry(String className) {
133 return new ConstructorEntry(new ClassEntry(className));
134 }
135
136 public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName, Signature behaviorSignature) {
137 switch (behaviorName) {
138 case "<init>":
139 return new ConstructorEntry(classEntry, behaviorSignature);
140 case "<clinit>":
141 return new ConstructorEntry(classEntry);
142 default:
143 return new MethodEntry(classEntry, behaviorName, behaviorSignature);
144 }
145 }
146
147 public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName) {
148 if (behaviorName.equals("<clinit>")) {
149 return new ConstructorEntry(classEntry);
150 } else {
151 throw new IllegalArgumentException("Only class initializers don't have signatures");
152 }
153 }
154
155 public static BehaviorEntry getObfBehaviorEntry(ClassEntry classEntry, MethodMapping methodMapping) {
156 return getBehaviorEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature());
157 }
158
159 public static BehaviorEntry getObfBehaviorEntry(ClassMapping classMapping, MethodMapping methodMapping) {
160 return getObfBehaviorEntry(getObfClassEntry(classMapping), methodMapping);
161 }
162}
diff --git a/src/main/java/cuchaz/enigma/mapping/EntryPair.java b/src/main/java/cuchaz/enigma/mapping/EntryPair.java
new file mode 100644
index 00000000..1c93d532
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/EntryPair.java
@@ -0,0 +1,22 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public class EntryPair<T extends Entry> {
14
15 public T obf;
16 public T deobf;
17
18 public EntryPair(T obf, T deobf) {
19 this.obf = obf;
20 this.deobf = deobf;
21 }
22}
diff --git a/src/main/java/cuchaz/enigma/mapping/FieldEntry.java b/src/main/java/cuchaz/enigma/mapping/FieldEntry.java
new file mode 100644
index 00000000..3de72239
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/FieldEntry.java
@@ -0,0 +1,99 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class FieldEntry implements Entry, Serializable {
18
19 private static final long serialVersionUID = 3004663582802885451L;
20
21 private ClassEntry m_classEntry;
22 private String m_name;
23 private Type m_type;
24
25 // NOTE: this argument order is important for the MethodReader/MethodWriter
26 public FieldEntry(ClassEntry classEntry, String name, Type type) {
27 if (classEntry == null) {
28 throw new IllegalArgumentException("Class cannot be null!");
29 }
30 if (name == null) {
31 throw new IllegalArgumentException("Field name cannot be null!");
32 }
33 if (type == null) {
34 throw new IllegalArgumentException("Field type cannot be null!");
35 }
36
37 m_classEntry = classEntry;
38 m_name = name;
39 m_type = type;
40 }
41
42 public FieldEntry(FieldEntry other) {
43 this(other, new ClassEntry(other.m_classEntry));
44 }
45
46 public FieldEntry(FieldEntry other, ClassEntry newClassEntry) {
47 m_classEntry = newClassEntry;
48 m_name = other.m_name;
49 m_type = other.m_type;
50 }
51
52 @Override
53 public ClassEntry getClassEntry() {
54 return m_classEntry;
55 }
56
57 @Override
58 public String getName() {
59 return m_name;
60 }
61
62 @Override
63 public String getClassName() {
64 return m_classEntry.getName();
65 }
66
67 public Type getType() {
68 return m_type;
69 }
70
71 @Override
72 public FieldEntry cloneToNewClass(ClassEntry classEntry) {
73 return new FieldEntry(this, classEntry);
74 }
75
76 @Override
77 public int hashCode() {
78 return Util.combineHashesOrdered(m_classEntry, m_name, m_type);
79 }
80
81 @Override
82 public boolean equals(Object other) {
83 if (other instanceof FieldEntry) {
84 return equals((FieldEntry) other);
85 }
86 return false;
87 }
88
89 public boolean equals(FieldEntry other) {
90 return m_classEntry.equals(other.m_classEntry)
91 && m_name.equals(other.m_name)
92 && m_type.equals(other.m_type);
93 }
94
95 @Override
96 public String toString() {
97 return m_classEntry.getName() + "." + m_name + ":" + m_type;
98 }
99}
diff --git a/src/main/java/cuchaz/enigma/mapping/FieldMapping.java b/src/main/java/cuchaz/enigma/mapping/FieldMapping.java
new file mode 100644
index 00000000..3f5a3822
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/FieldMapping.java
@@ -0,0 +1,89 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15public class FieldMapping implements Serializable, Comparable<FieldMapping>, MemberMapping<FieldEntry> {
16
17 private static final long serialVersionUID = 8610742471440861315L;
18
19 private String m_obfName;
20 private String m_deobfName;
21 private Type m_obfType;
22
23 public FieldMapping(String obfName, Type obfType, String deobfName) {
24 m_obfName = obfName;
25 m_deobfName = NameValidator.validateFieldName(deobfName);
26 m_obfType = obfType;
27 }
28
29 public FieldMapping(FieldMapping other, ClassNameReplacer obfClassNameReplacer) {
30 m_obfName = other.m_obfName;
31 m_deobfName = other.m_deobfName;
32 m_obfType = new Type(other.m_obfType, obfClassNameReplacer);
33 }
34
35 @Override
36 public String getObfName() {
37 return m_obfName;
38 }
39
40 public void setObfName(String val) {
41 m_obfName = NameValidator.validateFieldName(val);
42 }
43
44 public String getDeobfName() {
45 return m_deobfName;
46 }
47
48 public void setDeobfName(String val) {
49 m_deobfName = NameValidator.validateFieldName(val);
50 }
51
52 public Type getObfType() {
53 return m_obfType;
54 }
55
56 public void setObfType(Type val) {
57 m_obfType = val;
58 }
59
60 @Override
61 public int compareTo(FieldMapping other) {
62 return (m_obfName + m_obfType).compareTo(other.m_obfName + other.m_obfType);
63 }
64
65 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
66
67 // rename obf classes in the type
68 Type newType = new Type(m_obfType, new ClassNameReplacer() {
69 @Override
70 public String replace(String className) {
71 if (className.equals(oldObfClassName)) {
72 return newObfClassName;
73 }
74 return null;
75 }
76 });
77
78 if (!newType.equals(m_obfType)) {
79 m_obfType = newType;
80 return true;
81 }
82 return false;
83 }
84
85 @Override
86 public FieldEntry getObfEntry(ClassEntry classEntry) {
87 return new FieldEntry(classEntry, m_obfName, new Type(m_obfType));
88 }
89}
diff --git a/src/main/java/cuchaz/enigma/mapping/IllegalNameException.java b/src/main/java/cuchaz/enigma/mapping/IllegalNameException.java
new file mode 100644
index 00000000..f2119d8d
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/IllegalNameException.java
@@ -0,0 +1,44 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public class IllegalNameException extends RuntimeException {
14
15 private static final long serialVersionUID = -2279910052561114323L;
16
17 private String m_name;
18 private String m_reason;
19
20 public IllegalNameException(String name) {
21 this(name, null);
22 }
23
24 public IllegalNameException(String name, String reason) {
25 m_name = name;
26 m_reason = reason;
27 }
28
29 public String getReason() {
30 return m_reason;
31 }
32
33 @Override
34 public String getMessage() {
35 StringBuilder buf = new StringBuilder();
36 buf.append("Illegal name: ");
37 buf.append(m_name);
38 if (m_reason != null) {
39 buf.append(" because ");
40 buf.append(m_reason);
41 }
42 return buf.toString();
43 }
44}
diff --git a/src/cuchaz/enigma/mapping/MappingParseException.java b/src/main/java/cuchaz/enigma/mapping/MappingParseException.java
index 73fca94a..3c25ea58 100644
--- a/src/cuchaz/enigma/mapping/MappingParseException.java
+++ b/src/main/java/cuchaz/enigma/mapping/MappingParseException.java
@@ -4,26 +4,26 @@
4 * are made available under the terms of the GNU Lesser General Public 4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at 5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html 6 * http://www.gnu.org/licenses/lgpl.html
7 * 7 * <p>
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.mapping; 11package cuchaz.enigma.mapping;
12 12
13public class MappingParseException extends Exception { 13public class MappingParseException extends Exception {
14 14
15 private static final long serialVersionUID = -5487280332892507236L; 15 private static final long serialVersionUID = -5487280332892507236L;
16 16
17 private int m_line; 17 private int m_line;
18 private String m_message; 18 private String m_message;
19 19
20 public MappingParseException(int line, String message) { 20 public MappingParseException(int line, String message) {
21 m_line = line; 21 m_line = line;
22 m_message = message; 22 m_message = message;
23 } 23 }
24 24
25 @Override 25 @Override
26 public String getMessage() { 26 public String getMessage() {
27 return "Line " + m_line + ": " + m_message; 27 return "Line " + m_line + ": " + m_message;
28 } 28 }
29} 29}
diff --git a/src/main/java/cuchaz/enigma/mapping/Mappings.java b/src/main/java/cuchaz/enigma/mapping/Mappings.java
new file mode 100644
index 00000000..a48ec3ff
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/Mappings.java
@@ -0,0 +1,201 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import com.google.common.collect.Lists;
14import com.google.common.collect.Maps;
15import com.google.common.collect.Sets;
16
17import java.io.Serializable;
18import java.util.*;
19
20import cuchaz.enigma.analysis.TranslationIndex;
21
22public class Mappings implements Serializable {
23
24 private static final long serialVersionUID = 4649790259460259026L;
25
26 protected Map<String, ClassMapping> m_classesByObf;
27 protected Map<String, ClassMapping> m_classesByDeobf;
28
29 public Mappings() {
30 m_classesByObf = Maps.newHashMap();
31 m_classesByDeobf = Maps.newHashMap();
32 }
33
34 public Mappings(Iterable<ClassMapping> classes) {
35 this();
36
37 for (ClassMapping classMapping : classes) {
38 m_classesByObf.put(classMapping.getObfFullName(), classMapping);
39 if (classMapping.getDeobfName() != null) {
40 m_classesByDeobf.put(classMapping.getDeobfName(), classMapping);
41 }
42 }
43 }
44
45 public Collection<ClassMapping> classes() {
46 assert (m_classesByObf.size() >= m_classesByDeobf.size());
47 return m_classesByObf.values();
48 }
49
50 public void addClassMapping(ClassMapping classMapping) {
51 if (m_classesByObf.containsKey(classMapping.getObfFullName())) {
52 throw new Error("Already have mapping for " + classMapping.getObfFullName());
53 }
54 boolean obfWasAdded = m_classesByObf.put(classMapping.getObfFullName(), classMapping) == null;
55 assert (obfWasAdded);
56 if (classMapping.getDeobfName() != null) {
57 if (m_classesByDeobf.containsKey(classMapping.getDeobfName())) {
58 throw new Error("Already have mapping for " + classMapping.getDeobfName());
59 }
60 boolean deobfWasAdded = m_classesByDeobf.put(classMapping.getDeobfName(), classMapping) == null;
61 assert (deobfWasAdded);
62 }
63 }
64
65 public void removeClassMapping(ClassMapping classMapping) {
66 boolean obfWasRemoved = m_classesByObf.remove(classMapping.getObfFullName()) != null;
67 assert (obfWasRemoved);
68 if (classMapping.getDeobfName() != null) {
69 boolean deobfWasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
70 assert (deobfWasRemoved);
71 }
72 }
73
74 public ClassMapping getClassByObf(ClassEntry entry) {
75 return getClassByObf(entry.getName());
76 }
77
78 public ClassMapping getClassByObf(String obfName) {
79 return m_classesByObf.get(obfName);
80 }
81
82 public ClassMapping getClassByDeobf(ClassEntry entry) {
83 return getClassByDeobf(entry.getName());
84 }
85
86 public ClassMapping getClassByDeobf(String deobfName) {
87 return m_classesByDeobf.get(deobfName);
88 }
89
90 public void setClassDeobfName(ClassMapping classMapping, String deobfName) {
91 if (classMapping.getDeobfName() != null) {
92 boolean wasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
93 assert (wasRemoved);
94 }
95 classMapping.setDeobfName(deobfName);
96 if (deobfName != null) {
97 boolean wasAdded = m_classesByDeobf.put(deobfName, classMapping) == null;
98 assert (wasAdded);
99 }
100 }
101
102 public Translator getTranslator(TranslationDirection direction, TranslationIndex index) {
103 switch (direction) {
104 case Deobfuscating:
105
106 return new Translator(direction, m_classesByObf, index);
107
108 case Obfuscating:
109
110 // fill in the missing deobf class entries with obf entries
111 Map<String, ClassMapping> classes = Maps.newHashMap();
112 for (ClassMapping classMapping : classes()) {
113 if (classMapping.getDeobfName() != null) {
114 classes.put(classMapping.getDeobfName(), classMapping);
115 } else {
116 classes.put(classMapping.getObfFullName(), classMapping);
117 }
118 }
119
120 // translate the translation index
121 // NOTE: this isn't actually recursive
122 TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.Deobfuscating, index));
123
124 return new Translator(direction, classes, deobfIndex);
125
126 default:
127 throw new Error("Invalid translation direction!");
128 }
129 }
130
131 @Override
132 public String toString() {
133 StringBuilder buf = new StringBuilder();
134 for (ClassMapping classMapping : m_classesByObf.values()) {
135 buf.append(classMapping.toString());
136 buf.append("\n");
137 }
138 return buf.toString();
139 }
140
141 public void renameObfClass(String oldObfName, String newObfName) {
142 new ArrayList<>(classes()).stream().filter(classMapping -> classMapping.renameObfClass(oldObfName, newObfName)).forEach(classMapping -> {
143 boolean wasRemoved = m_classesByObf.remove(oldObfName) != null;
144 assert (wasRemoved);
145 boolean wasAdded = m_classesByObf.put(newObfName, classMapping) == null;
146 assert (wasAdded);
147 });
148 }
149
150 public Set<String> getAllObfClassNames() {
151 final Set<String> classNames = Sets.newHashSet();
152 for (ClassMapping classMapping : classes()) {
153
154 // add the class name
155 classNames.add(classMapping.getObfFullName());
156
157 // add classes from method signatures
158 for (MethodMapping methodMapping : classMapping.methods()) {
159 for (Type type : methodMapping.getObfSignature().types()) {
160 if (type.hasClass()) {
161 classNames.add(type.getClassEntry().getClassName());
162 }
163 }
164 }
165 }
166 return classNames;
167 }
168
169 public boolean containsDeobfClass(String deobfName) {
170 return m_classesByDeobf.containsKey(deobfName);
171 }
172
173 public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName, Type obfType) {
174 ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
175 return classMapping != null && classMapping.containsDeobfField(deobfName, obfType);
176 }
177
178 public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, Signature deobfSignature) {
179 ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
180 return classMapping != null && classMapping.containsDeobfMethod(deobfName, deobfSignature);
181 }
182
183 public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) {
184 ClassMapping classMapping = m_classesByObf.get(obfBehaviorEntry.getClassName());
185 return classMapping != null && classMapping.containsArgument(obfBehaviorEntry, name);
186 }
187
188 public List<ClassMapping> getClassMappingChain(ClassEntry obfClass) {
189 List<ClassMapping> mappingChain = Lists.newArrayList();
190 ClassMapping classMapping = null;
191 for (ClassEntry obfClassEntry : obfClass.getClassChain()) {
192 if (mappingChain.isEmpty()) {
193 classMapping = m_classesByObf.get(obfClassEntry.getName());
194 } else if (classMapping != null) {
195 classMapping = classMapping.getInnerClassByObfSimple(obfClassEntry.getInnermostClassName());
196 }
197 mappingChain.add(classMapping);
198 }
199 return mappingChain;
200 }
201}
diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java b/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java
new file mode 100644
index 00000000..ab686827
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java
@@ -0,0 +1,107 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import com.google.common.collect.Lists;
14import com.google.common.collect.Maps;
15
16import java.util.Map;
17
18import cuchaz.enigma.analysis.JarIndex;
19import cuchaz.enigma.analysis.RelatedMethodChecker;
20
21
22public class MappingsChecker {
23
24 private JarIndex m_index;
25 private RelatedMethodChecker m_relatedMethodChecker;
26 private Map<ClassEntry, ClassMapping> m_droppedClassMappings;
27 private Map<ClassEntry, ClassMapping> m_droppedInnerClassMappings;
28 private Map<FieldEntry, FieldMapping> m_droppedFieldMappings;
29 private Map<BehaviorEntry, MethodMapping> m_droppedMethodMappings;
30
31 public MappingsChecker(JarIndex index) {
32 m_index = index;
33 m_relatedMethodChecker = new RelatedMethodChecker(m_index);
34 m_droppedClassMappings = Maps.newHashMap();
35 m_droppedInnerClassMappings = Maps.newHashMap();
36 m_droppedFieldMappings = Maps.newHashMap();
37 m_droppedMethodMappings = Maps.newHashMap();
38 }
39
40 public RelatedMethodChecker getRelatedMethodChecker() {
41 return m_relatedMethodChecker;
42 }
43
44 public Map<ClassEntry, ClassMapping> getDroppedClassMappings() {
45 return m_droppedClassMappings;
46 }
47
48 public Map<ClassEntry, ClassMapping> getDroppedInnerClassMappings() {
49 return m_droppedInnerClassMappings;
50 }
51
52 public Map<FieldEntry, FieldMapping> getDroppedFieldMappings() {
53 return m_droppedFieldMappings;
54 }
55
56 public Map<BehaviorEntry, MethodMapping> getDroppedMethodMappings() {
57 return m_droppedMethodMappings;
58 }
59
60 public void dropBrokenMappings(Mappings mappings) {
61 for (ClassMapping classMapping : Lists.newArrayList(mappings.classes())) {
62 if (!checkClassMapping(classMapping)) {
63 mappings.removeClassMapping(classMapping);
64 m_droppedClassMappings.put(EntryFactory.getObfClassEntry(m_index, classMapping), classMapping);
65 }
66 }
67 }
68
69 private boolean checkClassMapping(ClassMapping classMapping) {
70
71 // check the class
72 ClassEntry classEntry = EntryFactory.getObfClassEntry(m_index, classMapping);
73 if (!m_index.getObfClassEntries().contains(classEntry)) {
74 return false;
75 }
76
77 // check the fields
78 for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) {
79 FieldEntry obfFieldEntry = EntryFactory.getObfFieldEntry(classMapping, fieldMapping);
80 if (!m_index.containsObfField(obfFieldEntry)) {
81 classMapping.removeFieldMapping(fieldMapping);
82 m_droppedFieldMappings.put(obfFieldEntry, fieldMapping);
83 }
84 }
85
86 // check methods
87 for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) {
88 BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping);
89 if (!m_index.containsObfBehavior(obfBehaviorEntry)) {
90 classMapping.removeMethodMapping(methodMapping);
91 m_droppedMethodMappings.put(obfBehaviorEntry, methodMapping);
92 }
93
94 m_relatedMethodChecker.checkMethod(classEntry, methodMapping);
95 }
96
97 // check inner classes
98 for (ClassMapping innerClassMapping : Lists.newArrayList(classMapping.innerClasses())) {
99 if (!checkClassMapping(innerClassMapping)) {
100 classMapping.removeInnerClassMapping(innerClassMapping);
101 m_droppedInnerClassMappings.put(EntryFactory.getObfClassEntry(m_index, innerClassMapping), innerClassMapping);
102 }
103 }
104
105 return true;
106 }
107}
diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsReader.java b/src/main/java/cuchaz/enigma/mapping/MappingsReader.java
new file mode 100644
index 00000000..c790eed6
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/MappingsReader.java
@@ -0,0 +1,92 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import com.google.gson.Gson;
14import com.google.gson.GsonBuilder;
15
16import java.io.BufferedReader;
17import java.io.File;
18import java.io.FileReader;
19import java.io.IOException;
20
21import cuchaz.enigma.json.JsonClass;
22
23public class MappingsReader {
24
25 public Mappings read(File in) throws IOException, MappingParseException {
26 Mappings mappings = new Mappings();
27 readDirectory(mappings, in);
28 return mappings;
29 }
30
31 public void readDirectory(Mappings mappings, File in) throws IOException, MappingParseException {
32
33 File[] fList = in.listFiles();
34 for (File file : fList) {
35 if (file.isFile()) {
36 readFile(mappings, new BufferedReader(new FileReader(file)));
37 } else if (file.isDirectory()) {
38 readDirectory(mappings, file.getAbsoluteFile());
39 }
40 }
41 }
42
43 public void readFile(Mappings mappings, BufferedReader in) throws IOException, MappingParseException {
44
45 String builder = "";
46 String line = null;
47 while ((line = in.readLine()) != null) {
48 builder += line;
49 }
50
51 Gson gson = new GsonBuilder().setPrettyPrinting().create();
52 JsonClass jsonClass = gson.fromJson(builder, JsonClass.class);
53 load(null, jsonClass, mappings);
54 }
55
56 public void load(ClassMapping parent, JsonClass jsonClass, Mappings mappings) {
57 ClassMapping classMapping = readClass(jsonClass.getObf(), jsonClass.getName());
58 if (parent != null) {
59 parent.addInnerClassMapping(classMapping);
60 } else {
61 mappings.addClassMapping(classMapping);
62 }
63 jsonClass.getField().forEach(jsonField -> classMapping.addFieldMapping(readField(jsonField.getObf(), jsonField.getName(), jsonField.getType())));
64
65 jsonClass.getMethod().forEach(jsonMethod -> {
66 MethodMapping methodMapping = readMethod(jsonMethod.getObf(), jsonMethod.getName(), jsonMethod.getSignature());
67 jsonMethod.getArgs().forEach(jsonArgument -> methodMapping.addArgumentMapping(readArgument(jsonArgument.getIndex(), jsonArgument.getName())));
68 classMapping.addMethodMapping(methodMapping);
69 });
70
71 jsonClass.getInnerClass().forEach(jsonInnerClasses -> {
72 load(classMapping, jsonInnerClasses, mappings);
73 });
74 }
75
76 private ArgumentMapping readArgument(int index, String name) {
77 return new ArgumentMapping(index, name);
78 }
79
80 private ClassMapping readClass(String obf, String deobf) {
81 return new ClassMapping("none/" + obf, deobf);
82 }
83
84 /* TEMP */
85 protected FieldMapping readField(String obf, String deobf, String sig) {
86 return new FieldMapping(obf, new Type(sig), deobf);
87 }
88
89 private MethodMapping readMethod(String obf, String deobf, String sig) {
90 return new MethodMapping(obf, new Signature(sig), deobf);
91 }
92}
diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsReaderOld.java b/src/main/java/cuchaz/enigma/mapping/MappingsReaderOld.java
new file mode 100644
index 00000000..1a93604a
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/MappingsReaderOld.java
@@ -0,0 +1,122 @@
1package cuchaz.enigma.mapping;
2
3import com.google.common.collect.Queues;
4
5import java.io.BufferedReader;
6import java.io.IOException;
7import java.io.Reader;
8import java.util.Deque;
9
10public class MappingsReaderOld {
11
12 public Mappings read(Reader in) throws IOException, MappingParseException {
13 return read(new BufferedReader(in));
14 }
15
16 public Mappings read(BufferedReader in) throws IOException, MappingParseException {
17 Mappings mappings = new Mappings();
18 Deque<Object> mappingStack = Queues.newArrayDeque();
19
20 int lineNumber = 0;
21 String line = null;
22 while ((line = in.readLine()) != null) {
23 lineNumber++;
24
25 // strip comments
26 int commentPos = line.indexOf('#');
27 if (commentPos >= 0) {
28 line = line.substring(0, commentPos);
29 }
30
31 // skip blank lines
32 if (line.trim().length() <= 0) {
33 continue;
34 }
35
36 // get the indent of this line
37 int indent = 0;
38 for (int i = 0; i < line.length(); i++) {
39 if (line.charAt(i) != '\t') {
40 break;
41 }
42 indent++;
43 }
44
45 // handle stack pops
46 while (indent < mappingStack.size()) {
47 mappingStack.pop();
48 }
49
50 String[] parts = line.trim().split("\\s");
51 try {
52 // read the first token
53 String token = parts[0];
54
55 if (token.equalsIgnoreCase("CLASS")) {
56 ClassMapping classMapping;
57 if (indent <= 0) {
58 // outer class
59 classMapping = readClass(parts, false);
60 mappings.addClassMapping(classMapping);
61 } else {
62
63 // inner class
64 if (!(mappingStack.peek() instanceof ClassMapping)) {
65 throw new MappingParseException(lineNumber, "Unexpected CLASS entry here!");
66 }
67
68 classMapping = readClass(parts, true);
69 ((ClassMapping) mappingStack.peek()).addInnerClassMapping(classMapping);
70 }
71 mappingStack.push(classMapping);
72 } else if (token.equalsIgnoreCase("FIELD")) {
73 if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) {
74 throw new MappingParseException(lineNumber, "Unexpected FIELD entry here!");
75 }
76 ((ClassMapping) mappingStack.peek()).addFieldMapping(readField(parts));
77 } else if (token.equalsIgnoreCase("METHOD")) {
78 if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof ClassMapping)) {
79 throw new MappingParseException(lineNumber, "Unexpected METHOD entry here!");
80 }
81 MethodMapping methodMapping = readMethod(parts);
82 ((ClassMapping) mappingStack.peek()).addMethodMapping(methodMapping);
83 mappingStack.push(methodMapping);
84 } else if (token.equalsIgnoreCase("ARG")) {
85 if (mappingStack.isEmpty() || !(mappingStack.peek() instanceof MethodMapping)) {
86 throw new MappingParseException(lineNumber, "Unexpected ARG entry here!");
87 }
88 ((MethodMapping) mappingStack.peek()).addArgumentMapping(readArgument(parts));
89 }
90 } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) {
91 throw new MappingParseException(lineNumber, "Malformed line:\n" + line);
92 }
93 }
94
95 return mappings;
96 }
97
98 private ArgumentMapping readArgument(String[] parts) {
99 return new ArgumentMapping(Integer.parseInt(parts[1]), parts[2]);
100 }
101
102 private ClassMapping readClass(String[] parts, boolean makeSimple) {
103 if (parts.length == 2) {
104 return new ClassMapping(parts[1]);
105 } else {
106 return new ClassMapping(parts[1], parts[2]);
107 }
108 }
109
110 /* TEMP */
111 protected FieldMapping readField(String[] parts) {
112 return new FieldMapping(parts[1], new Type(parts[3]), parts[2]);
113 }
114
115 private MethodMapping readMethod(String[] parts) {
116 if (parts.length == 3) {
117 return new MethodMapping(parts[1], new Signature(parts[2]));
118 } else {
119 return new MethodMapping(parts[1], new Signature(parts[3]), parts[2]);
120 }
121 }
122}
diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java b/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java
new file mode 100644
index 00000000..de1635aa
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java
@@ -0,0 +1,237 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.IOException;
14import java.io.ObjectOutputStream;
15import java.io.OutputStream;
16import java.util.List;
17import java.util.Set;
18import java.util.zip.GZIPOutputStream;
19
20import cuchaz.enigma.analysis.JarIndex;
21
22public class MappingsRenamer {
23
24 private JarIndex m_index;
25 private Mappings m_mappings;
26
27 public MappingsRenamer(JarIndex index, Mappings mappings) {
28 m_index = index;
29 m_mappings = mappings;
30 }
31
32 public void setClassName(ClassEntry obf, String deobfName) {
33
34 deobfName = NameValidator.validateClassName(deobfName, !obf.isInnerClass());
35
36 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obf);
37 if (mappingChain.size() == 1) {
38
39 if (deobfName != null) {
40 // make sure we don't rename to an existing obf or deobf class
41 if (m_mappings.containsDeobfClass(deobfName) || m_index.containsObfClass(new ClassEntry(deobfName))) {
42 throw new IllegalNameException(deobfName, "There is already a class with that name");
43 }
44 }
45
46 ClassMapping classMapping = mappingChain.get(0);
47 m_mappings.setClassDeobfName(classMapping, deobfName);
48
49 } else {
50
51 ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2);
52
53 if (deobfName != null) {
54 // make sure we don't rename to an existing obf or deobf inner class
55 if (outerClassMapping.hasInnerClassByDeobf(deobfName) || outerClassMapping.hasInnerClassByObfSimple(deobfName)) {
56 throw new IllegalNameException(deobfName, "There is already a class with that name");
57 }
58 }
59
60 outerClassMapping.setInnerClassName(obf, deobfName);
61 }
62 }
63
64 public void removeClassMapping(ClassEntry obf) {
65 setClassName(obf, null);
66 }
67
68 public void markClassAsDeobfuscated(ClassEntry obf) {
69 String deobfName = obf.isInnerClass() ? obf.getInnermostClassName() : obf.getName();
70 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obf);
71 if (mappingChain.size() == 1) {
72 ClassMapping classMapping = mappingChain.get(0);
73 m_mappings.setClassDeobfName(classMapping, deobfName);
74 } else {
75 ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2);
76 outerClassMapping.setInnerClassName(obf, deobfName);
77 }
78 }
79
80 public void setFieldName(FieldEntry obf, String deobfName) {
81 deobfName = NameValidator.validateFieldName(deobfName);
82 FieldEntry targetEntry = new FieldEntry(obf.getClassEntry(), deobfName, obf.getType());
83 if (m_mappings.containsDeobfField(obf.getClassEntry(), deobfName, obf.getType()) || m_index.containsObfField(targetEntry)) {
84 throw new IllegalNameException(deobfName, "There is already a field with that name");
85 }
86
87 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
88 classMapping.setFieldName(obf.getName(), obf.getType(), deobfName);
89 }
90
91 public void removeFieldMapping(FieldEntry obf) {
92 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
93 classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getType()));
94 }
95
96 public void markFieldAsDeobfuscated(FieldEntry obf) {
97 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
98 classMapping.setFieldName(obf.getName(), obf.getType(), obf.getName());
99 }
100
101 public void setMethodTreeName(MethodEntry obf, String deobfName) {
102 Set<MethodEntry> implementations = m_index.getRelatedMethodImplementations(obf);
103
104 deobfName = NameValidator.validateMethodName(deobfName);
105 for (MethodEntry entry : implementations) {
106 Signature deobfSignature = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateSignature(obf.getSignature());
107 MethodEntry targetEntry = new MethodEntry(entry.getClassEntry(), deobfName, deobfSignature);
108 if (m_mappings.containsDeobfMethod(entry.getClassEntry(), deobfName, entry.getSignature()) || m_index.containsObfBehavior(targetEntry)) {
109 String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(entry.getClassName());
110 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName);
111 }
112 }
113
114 for (MethodEntry entry : implementations) {
115 setMethodName(entry, deobfName);
116 }
117 }
118
119 public void setMethodName(MethodEntry obf, String deobfName) {
120 deobfName = NameValidator.validateMethodName(deobfName);
121 MethodEntry targetEntry = new MethodEntry(obf.getClassEntry(), deobfName, obf.getSignature());
122 if (m_mappings.containsDeobfMethod(obf.getClassEntry(), deobfName, obf.getSignature()) || m_index.containsObfBehavior(targetEntry)) {
123 String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(obf.getClassName());
124 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName);
125 }
126
127 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
128 classMapping.setMethodName(obf.getName(), obf.getSignature(), deobfName);
129 }
130
131 public void removeMethodTreeMapping(MethodEntry obf) {
132 for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) {
133 removeMethodMapping(implementation);
134 }
135 }
136
137 public void removeMethodMapping(MethodEntry obf) {
138 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
139 classMapping.setMethodName(obf.getName(), obf.getSignature(), null);
140 }
141
142 public void markMethodTreeAsDeobfuscated(MethodEntry obf) {
143 for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) {
144 markMethodAsDeobfuscated(implementation);
145 }
146 }
147
148 public void markMethodAsDeobfuscated(MethodEntry obf) {
149 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
150 classMapping.setMethodName(obf.getName(), obf.getSignature(), obf.getName());
151 }
152
153 public void setArgumentName(ArgumentEntry obf, String deobfName) {
154 deobfName = NameValidator.validateArgumentName(deobfName);
155 // NOTE: don't need to check arguments for name collisions with names determined by Procyon
156 if (m_mappings.containsArgument(obf.getBehaviorEntry(), deobfName)) {
157 throw new IllegalNameException(deobfName, "There is already an argument with that name");
158 }
159
160 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
161 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), deobfName);
162 }
163
164 public void removeArgumentMapping(ArgumentEntry obf) {
165 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
166 classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex());
167 }
168
169 public void markArgumentAsDeobfuscated(ArgumentEntry obf) {
170 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
171 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), obf.getName());
172 }
173
174 public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) {
175 classMapping.removeFieldMapping(fieldMapping);
176 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
177 if (!targetClassMapping.containsObfField(fieldMapping.getObfName(), fieldMapping.getObfType())) {
178 if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfType())) {
179 targetClassMapping.addFieldMapping(fieldMapping);
180 return true;
181 } else {
182 System.err.println("WARNING: deobf field was already there: " + obfClass + "." + fieldMapping.getDeobfName());
183 }
184 }
185 return false;
186 }
187
188 public boolean moveMethodToObfClass(ClassMapping classMapping, MethodMapping methodMapping, ClassEntry obfClass) {
189 classMapping.removeMethodMapping(methodMapping);
190 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
191 if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfSignature())) {
192 if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfSignature())) {
193 targetClassMapping.addMethodMapping(methodMapping);
194 return true;
195 } else {
196 System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfSignature());
197 }
198 }
199 return false;
200 }
201
202 public void write(OutputStream out) throws IOException {
203 // TEMP: just use the object output for now. We can find a more efficient storage format later
204 GZIPOutputStream gzipout = new GZIPOutputStream(out);
205 ObjectOutputStream oout = new ObjectOutputStream(gzipout);
206 oout.writeObject(this);
207 gzipout.finish();
208 }
209
210 private ClassMapping getOrCreateClassMapping(ClassEntry obfClassEntry) {
211 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obfClassEntry);
212 return mappingChain.get(mappingChain.size() - 1);
213 }
214
215 private List<ClassMapping> getOrCreateClassMappingChain(ClassEntry obfClassEntry) {
216 List<ClassEntry> classChain = obfClassEntry.getClassChain();
217 List<ClassMapping> mappingChain = m_mappings.getClassMappingChain(obfClassEntry);
218 for (int i = 0; i < classChain.size(); i++) {
219 ClassEntry classEntry = classChain.get(i);
220 ClassMapping classMapping = mappingChain.get(i);
221 if (classMapping == null) {
222
223 // create it
224 classMapping = new ClassMapping(classEntry.getName());
225 mappingChain.set(i, classMapping);
226
227 // add it to the right parent
228 if (i == 0) {
229 m_mappings.addClassMapping(classMapping);
230 } else {
231 mappingChain.get(i - 1).addInnerClassMapping(classMapping);
232 }
233 }
234 }
235 return mappingChain;
236 }
237}
diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsWriter.java b/src/main/java/cuchaz/enigma/mapping/MappingsWriter.java
new file mode 100644
index 00000000..aa37f166
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/MappingsWriter.java
@@ -0,0 +1,82 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import com.google.gson.Gson;
14import com.google.gson.GsonBuilder;
15
16import java.io.File;
17import java.io.FileWriter;
18import java.io.IOException;
19import java.util.ArrayList;
20import java.util.Collections;
21import java.util.List;
22
23import cuchaz.enigma.json.*;
24
25public class MappingsWriter {
26
27
28 public void write(File file, Mappings mappings) throws IOException {
29 if (!file.isDirectory()) {
30 //TODO Error
31 }
32
33 Gson gson = new GsonBuilder().setPrettyPrinting().create();
34 for (ClassMapping classMapping : sorted(mappings.classes())) {
35 JsonClass jsonClass = new JsonClass(classMapping.getObfSimpleName(), classMapping.getDeobfName());
36 write(jsonClass, classMapping);
37
38 File f = new File(file, jsonClass.getName() + ".json");
39 f.getParentFile().mkdirs();
40 f.createNewFile();
41 FileWriter writer = new FileWriter(f);
42 writer.write(gson.toJson(jsonClass));
43 writer.close();
44 }
45 }
46
47 private void write(JsonClass jsonClass, ClassMapping classMapping) throws IOException {
48 if (classMapping.getDeobfName() != null && !classMapping.getDeobfName().equalsIgnoreCase("") && !classMapping.getDeobfName().equalsIgnoreCase("null")) {
49
50 for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) {
51 JsonClass innerClass = new JsonClass(classMapping.getObfSimpleName() + "$" + innerClassMapping.getObfSimpleName().replace("nome/", ""), innerClassMapping.getDeobfName());
52 write(innerClass, innerClassMapping);
53 jsonClass.addInnerClass(innerClass);
54 }
55
56 for (FieldMapping fieldMapping : sorted(classMapping.fields())) {
57 jsonClass.addField(new JsonField(fieldMapping.getObfName(), fieldMapping.getDeobfName(), fieldMapping.getObfType().toString()));
58 }
59
60 for (MethodMapping methodMapping : sorted(classMapping.methods())) {
61 List<JsonArgument> args = new ArrayList<>();
62 for (ArgumentMapping argumentMapping : sorted(methodMapping.arguments())) {
63 args.add(new JsonArgument(argumentMapping.getIndex(), argumentMapping.getName()));
64 }
65 if (methodMapping.getObfName().contains("<init>") || methodMapping.getObfName().contains("<clinit>")) {
66 jsonClass.addConstructor(new JsonConstructor(methodMapping.getObfSignature().toString(), args, methodMapping.getObfName().contains("<clinit>")));
67 } else {
68 jsonClass.addMethod(new JsonMethod(methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfSignature().toString(), args));
69 }
70 }
71 }
72 }
73
74 private <T extends Comparable<T>> List<T> sorted(Iterable<T> classes) {
75 List<T> out = new ArrayList<T>();
76 for (T t : classes) {
77 out.add(t);
78 }
79 Collections.sort(out);
80 return out;
81 }
82}
diff --git a/src/cuchaz/enigma/mapping/MemberMapping.java b/src/main/java/cuchaz/enigma/mapping/MemberMapping.java
index 83782975..90c096fa 100644
--- a/src/cuchaz/enigma/mapping/MemberMapping.java
+++ b/src/main/java/cuchaz/enigma/mapping/MemberMapping.java
@@ -4,14 +4,15 @@
4 * are made available under the terms of the GNU Lesser General Public 4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at 5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html 6 * http://www.gnu.org/licenses/lgpl.html
7 * 7 * <p>
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.mapping; 11package cuchaz.enigma.mapping;
12 12
13 13
14public interface MemberMapping<T extends Entry> { 14public interface MemberMapping<T extends Entry> {
15 T getObfEntry(ClassEntry classEntry); 15 T getObfEntry(ClassEntry classEntry);
16 String getObfName(); 16
17 String getObfName();
17} 18}
diff --git a/src/main/java/cuchaz/enigma/mapping/MethodEntry.java b/src/main/java/cuchaz/enigma/mapping/MethodEntry.java
new file mode 100644
index 00000000..301da615
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/MethodEntry.java
@@ -0,0 +1,104 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class MethodEntry implements BehaviorEntry, Serializable {
18
19 private static final long serialVersionUID = 4770915224467247458L;
20
21 private ClassEntry m_classEntry;
22 private String m_name;
23 private Signature m_signature;
24
25 public MethodEntry(ClassEntry classEntry, String name, Signature signature) {
26 if (classEntry == null) {
27 throw new IllegalArgumentException("Class cannot be null!");
28 }
29 if (name == null) {
30 throw new IllegalArgumentException("Method name cannot be null!");
31 }
32 if (signature == null) {
33 throw new IllegalArgumentException("Method signature cannot be null!");
34 }
35 if (name.startsWith("<")) {
36 throw new IllegalArgumentException("Don't use MethodEntry for a constructor!");
37 }
38
39 m_classEntry = classEntry;
40 m_name = name;
41 m_signature = signature;
42 }
43
44 public MethodEntry(MethodEntry other) {
45 m_classEntry = new ClassEntry(other.m_classEntry);
46 m_name = other.m_name;
47 m_signature = other.m_signature;
48 }
49
50 public MethodEntry(MethodEntry other, String newClassName) {
51 m_classEntry = new ClassEntry(newClassName);
52 m_name = other.m_name;
53 m_signature = other.m_signature;
54 }
55
56 @Override
57 public ClassEntry getClassEntry() {
58 return m_classEntry;
59 }
60
61 @Override
62 public String getName() {
63 return m_name;
64 }
65
66 @Override
67 public Signature getSignature() {
68 return m_signature;
69 }
70
71 @Override
72 public String getClassName() {
73 return m_classEntry.getName();
74 }
75
76 @Override
77 public MethodEntry cloneToNewClass(ClassEntry classEntry) {
78 return new MethodEntry(this, classEntry.getName());
79 }
80
81 @Override
82 public int hashCode() {
83 return Util.combineHashesOrdered(m_classEntry, m_name, m_signature);
84 }
85
86 @Override
87 public boolean equals(Object other) {
88 if (other instanceof MethodEntry) {
89 return equals((MethodEntry) other);
90 }
91 return false;
92 }
93
94 public boolean equals(MethodEntry other) {
95 return m_classEntry.equals(other.m_classEntry)
96 && m_name.equals(other.m_name)
97 && m_signature.equals(other.m_signature);
98 }
99
100 @Override
101 public String toString() {
102 return m_classEntry.getName() + "." + m_name + m_signature;
103 }
104}
diff --git a/src/main/java/cuchaz/enigma/mapping/MethodMapping.java b/src/main/java/cuchaz/enigma/mapping/MethodMapping.java
new file mode 100644
index 00000000..d1beddde
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/MethodMapping.java
@@ -0,0 +1,191 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import com.google.common.collect.Maps;
14
15import java.io.Serializable;
16import java.util.Map;
17import java.util.Map.Entry;
18
19public class MethodMapping implements Serializable, Comparable<MethodMapping>, MemberMapping<BehaviorEntry> {
20
21 private static final long serialVersionUID = -4409570216084263978L;
22
23 private String m_obfName;
24 private String m_deobfName;
25 private Signature m_obfSignature;
26 private Map<Integer, ArgumentMapping> m_arguments;
27
28 public MethodMapping(String obfName, Signature obfSignature) {
29 this(obfName, obfSignature, null);
30 }
31
32 public MethodMapping(String obfName, Signature obfSignature, String deobfName) {
33 if (obfName == null) {
34 throw new IllegalArgumentException("obf name cannot be null!");
35 }
36 if (obfSignature == null) {
37 throw new IllegalArgumentException("obf signature cannot be null!");
38 }
39 m_obfName = obfName;
40 m_deobfName = NameValidator.validateMethodName(deobfName);
41 m_obfSignature = obfSignature;
42 m_arguments = Maps.newTreeMap();
43 }
44
45 public MethodMapping(MethodMapping other, ClassNameReplacer obfClassNameReplacer) {
46 m_obfName = other.m_obfName;
47 m_deobfName = other.m_deobfName;
48 m_obfSignature = new Signature(other.m_obfSignature, obfClassNameReplacer);
49 m_arguments = Maps.newTreeMap();
50 for (Entry<Integer, ArgumentMapping> entry : other.m_arguments.entrySet()) {
51 m_arguments.put(entry.getKey(), new ArgumentMapping(entry.getValue()));
52 }
53 }
54
55 @Override
56 public String getObfName() {
57 return m_obfName;
58 }
59
60 public void setObfName(String val) {
61 m_obfName = NameValidator.validateMethodName(val);
62 }
63
64 public String getDeobfName() {
65 return m_deobfName;
66 }
67
68 public void setDeobfName(String val) {
69 m_deobfName = NameValidator.validateMethodName(val);
70 }
71
72 public Signature getObfSignature() {
73 return m_obfSignature;
74 }
75
76 public void setObfSignature(Signature val) {
77 m_obfSignature = val;
78 }
79
80 public Iterable<ArgumentMapping> arguments() {
81 return m_arguments.values();
82 }
83
84 public boolean isConstructor() {
85 return m_obfName.startsWith("<");
86 }
87
88 public void addArgumentMapping(ArgumentMapping argumentMapping) {
89 boolean wasAdded = m_arguments.put(argumentMapping.getIndex(), argumentMapping) == null;
90 assert (wasAdded);
91 }
92
93 public String getObfArgumentName(int index) {
94 ArgumentMapping argumentMapping = m_arguments.get(index);
95 if (argumentMapping != null) {
96 return argumentMapping.getName();
97 }
98
99 return null;
100 }
101
102 public String getDeobfArgumentName(int index) {
103 ArgumentMapping argumentMapping = m_arguments.get(index);
104 if (argumentMapping != null) {
105 return argumentMapping.getName();
106 }
107
108 return null;
109 }
110
111 public void setArgumentName(int index, String name) {
112 ArgumentMapping argumentMapping = m_arguments.get(index);
113 if (argumentMapping == null) {
114 argumentMapping = new ArgumentMapping(index, name);
115 boolean wasAdded = m_arguments.put(index, argumentMapping) == null;
116 assert (wasAdded);
117 } else {
118 argumentMapping.setName(name);
119 }
120 }
121
122 public void removeArgumentName(int index) {
123 boolean wasRemoved = m_arguments.remove(index) != null;
124 assert (wasRemoved);
125 }
126
127 @Override
128 public String toString() {
129 StringBuilder buf = new StringBuilder();
130 buf.append("\t");
131 buf.append(m_obfName);
132 buf.append(" <-> ");
133 buf.append(m_deobfName);
134 buf.append("\n");
135 buf.append("\t");
136 buf.append(m_obfSignature);
137 buf.append("\n");
138 buf.append("\tArguments:\n");
139 for (ArgumentMapping argumentMapping : m_arguments.values()) {
140 buf.append("\t\t");
141 buf.append(argumentMapping.getIndex());
142 buf.append(" -> ");
143 buf.append(argumentMapping.getName());
144 buf.append("\n");
145 }
146 return buf.toString();
147 }
148
149 @Override
150 public int compareTo(MethodMapping other) {
151 return (m_obfName + m_obfSignature).compareTo(other.m_obfName + other.m_obfSignature);
152 }
153
154 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
155
156 // rename obf classes in the signature
157 Signature newSignature = new Signature(m_obfSignature, new ClassNameReplacer() {
158 @Override
159 public String replace(String className) {
160 if (className.equals(oldObfClassName)) {
161 return newObfClassName;
162 }
163 return null;
164 }
165 });
166
167 if (!newSignature.equals(m_obfSignature)) {
168 m_obfSignature = newSignature;
169 return true;
170 }
171 return false;
172 }
173
174 public boolean containsArgument(String name) {
175 for (ArgumentMapping argumentMapping : m_arguments.values()) {
176 if (argumentMapping.getName().equals(name)) {
177 return true;
178 }
179 }
180 return false;
181 }
182
183 @Override
184 public BehaviorEntry getObfEntry(ClassEntry classEntry) {
185 if (isConstructor()) {
186 return new ConstructorEntry(classEntry, m_obfSignature);
187 } else {
188 return new MethodEntry(classEntry, m_obfName, m_obfSignature);
189 }
190 }
191}
diff --git a/src/main/java/cuchaz/enigma/mapping/NameValidator.java b/src/main/java/cuchaz/enigma/mapping/NameValidator.java
new file mode 100644
index 00000000..a3b9a78a
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/NameValidator.java
@@ -0,0 +1,80 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.util.Arrays;
14import java.util.List;
15import java.util.regex.Pattern;
16
17import javassist.bytecode.Descriptor;
18
19public class NameValidator {
20
21 private static final Pattern IdentifierPattern;
22 private static final Pattern ClassPattern;
23 private static final List<String> ReservedWords = Arrays.asList(
24 "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized",
25 "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte",
26 "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch",
27 "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally",
28 "long", "strictfp", "volatile", "const", "float", "native", "super", "while"
29 );
30
31 static {
32
33 // java allows all kinds of weird characters...
34 StringBuilder startChars = new StringBuilder();
35 StringBuilder partChars = new StringBuilder();
36 for (int i = Character.MIN_CODE_POINT; i <= Character.MAX_CODE_POINT; i++) {
37 if (Character.isJavaIdentifierStart(i)) {
38 startChars.appendCodePoint(i);
39 }
40 if (Character.isJavaIdentifierPart(i)) {
41 partChars.appendCodePoint(i);
42 }
43 }
44
45 String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*";
46 IdentifierPattern = Pattern.compile(identifierRegex);
47 ClassPattern = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex));
48 }
49
50 public static String validateClassName(String name, boolean packageRequired) {
51 if (name == null) {
52 return null;
53 }
54 if (!ClassPattern.matcher(name).matches() || ReservedWords.contains(name)) {
55 throw new IllegalNameException(name, "This doesn't look like a legal class name");
56 }
57 if (packageRequired && new ClassEntry(name).getPackageName() == null) {
58 throw new IllegalNameException(name, "Class must be in a package");
59 }
60 return Descriptor.toJvmName(name);
61 }
62
63 public static String validateFieldName(String name) {
64 if (name == null) {
65 return null;
66 }
67 if (!IdentifierPattern.matcher(name).matches() || ReservedWords.contains(name)) {
68 throw new IllegalNameException(name, "This doesn't look like a legal identifier");
69 }
70 return name;
71 }
72
73 public static String validateMethodName(String name) {
74 return validateFieldName(name);
75 }
76
77 public static String validateArgumentName(String name) {
78 return validateFieldName(name);
79 }
80}
diff --git a/src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java b/src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java
new file mode 100644
index 00000000..ac424996
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import com.strobel.assembler.metadata.FieldDefinition;
14import com.strobel.assembler.metadata.MethodDefinition;
15
16
17public class ProcyonEntryFactory {
18
19 public static FieldEntry getFieldEntry(FieldDefinition def) {
20 return new FieldEntry(
21 new ClassEntry(def.getDeclaringType().getInternalName()),
22 def.getName(),
23 new Type(def.getErasedSignature())
24 );
25 }
26
27 public static MethodEntry getMethodEntry(MethodDefinition def) {
28 return new MethodEntry(
29 new ClassEntry(def.getDeclaringType().getInternalName()),
30 def.getName(),
31 new Signature(def.getErasedSignature())
32 );
33 }
34
35 public static ConstructorEntry getConstructorEntry(MethodDefinition def) {
36 if (def.isTypeInitializer()) {
37 return new ConstructorEntry(
38 new ClassEntry(def.getDeclaringType().getInternalName())
39 );
40 } else {
41 return new ConstructorEntry(
42 new ClassEntry(def.getDeclaringType().getInternalName()),
43 new Signature(def.getErasedSignature())
44 );
45 }
46 }
47
48 public static BehaviorEntry getBehaviorEntry(MethodDefinition def) {
49 if (def.isConstructor() || def.isTypeInitializer()) {
50 return getConstructorEntry(def);
51 } else {
52 return getMethodEntry(def);
53 }
54 }
55}
diff --git a/src/main/java/cuchaz/enigma/mapping/Signature.java b/src/main/java/cuchaz/enigma/mapping/Signature.java
new file mode 100644
index 00000000..117018ad
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/Signature.java
@@ -0,0 +1,117 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import com.google.common.collect.Lists;
14
15import java.io.Serializable;
16import java.util.List;
17
18import cuchaz.enigma.Util;
19
20public class Signature implements Serializable {
21
22 private static final long serialVersionUID = -5843719505729497539L;
23
24 private List<Type> m_argumentTypes;
25 private Type m_returnType;
26
27 public Signature(String signature) {
28 try {
29 m_argumentTypes = Lists.newArrayList();
30 int i = 0;
31 while (i < signature.length()) {
32 char c = signature.charAt(i);
33 if (c == '(') {
34 assert (m_argumentTypes.isEmpty());
35 assert (m_returnType == null);
36 i++;
37 } else if (c == ')') {
38 i++;
39 break;
40 } else {
41 String type = Type.parseFirst(signature.substring(i));
42 m_argumentTypes.add(new Type(type));
43 i += type.length();
44 }
45 }
46 m_returnType = new Type(Type.parseFirst(signature.substring(i)));
47 } catch (Exception ex) {
48 throw new IllegalArgumentException("Unable to parse signature: " + signature, ex);
49 }
50 }
51
52 public Signature(Signature other) {
53 m_argumentTypes = Lists.newArrayList(other.m_argumentTypes);
54 m_returnType = new Type(other.m_returnType);
55 }
56
57 public Signature(Signature other, ClassNameReplacer replacer) {
58 m_argumentTypes = Lists.newArrayList(other.m_argumentTypes);
59 for (int i = 0; i < m_argumentTypes.size(); i++) {
60 m_argumentTypes.set(i, new Type(m_argumentTypes.get(i), replacer));
61 }
62 m_returnType = new Type(other.m_returnType, replacer);
63 }
64
65 public List<Type> getArgumentTypes() {
66 return m_argumentTypes;
67 }
68
69 public Type getReturnType() {
70 return m_returnType;
71 }
72
73 @Override
74 public String toString() {
75 StringBuilder buf = new StringBuilder();
76 buf.append("(");
77 for (Type type : m_argumentTypes) {
78 buf.append(type.toString());
79 }
80 buf.append(")");
81 buf.append(m_returnType.toString());
82 return buf.toString();
83 }
84
85 public Iterable<Type> types() {
86 List<Type> types = Lists.newArrayList();
87 types.addAll(m_argumentTypes);
88 types.add(m_returnType);
89 return types;
90 }
91
92 @Override
93 public boolean equals(Object other) {
94 if (other instanceof Signature) {
95 return equals((Signature) other);
96 }
97 return false;
98 }
99
100 public boolean equals(Signature other) {
101 return m_argumentTypes.equals(other.m_argumentTypes) && m_returnType.equals(other.m_returnType);
102 }
103
104 @Override
105 public int hashCode() {
106 return Util.combineHashesOrdered(m_argumentTypes.hashCode(), m_returnType.hashCode());
107 }
108
109 public boolean hasClass(ClassEntry classEntry) {
110 for (Type type : types()) {
111 if (type.hasClass() && type.getClassEntry().equals(classEntry)) {
112 return true;
113 }
114 }
115 return false;
116 }
117}
diff --git a/src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java b/src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java
new file mode 100644
index 00000000..dac692e4
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/SignatureUpdater.java
@@ -0,0 +1,94 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import com.google.common.collect.Lists;
14
15import java.io.IOException;
16import java.io.StringReader;
17import java.util.List;
18
19public class SignatureUpdater {
20
21 public interface ClassNameUpdater {
22 String update(String className);
23 }
24
25 public static String update(String signature, ClassNameUpdater updater) {
26 try {
27 StringBuilder buf = new StringBuilder();
28
29 // read the signature character-by-character
30 StringReader reader = new StringReader(signature);
31 int i = -1;
32 while ((i = reader.read()) != -1) {
33 char c = (char) i;
34
35 // does this character start a class name?
36 if (c == 'L') {
37 // update the class name and add it to the buffer
38 buf.append('L');
39 String className = readClass(reader);
40 if (className == null) {
41 throw new IllegalArgumentException("Malformed signature: " + signature);
42 }
43 buf.append(updater.update(className));
44 buf.append(';');
45 } else {
46 // copy the character into the buffer
47 buf.append(c);
48 }
49 }
50
51 return buf.toString();
52 } catch (IOException ex) {
53 // I'm pretty sure a StringReader will never throw one of these
54 throw new Error(ex);
55 }
56 }
57
58 private static String readClass(StringReader reader) throws IOException {
59 // read all the characters in the buffer until we hit a ';'
60 // remember to treat generics correctly
61 StringBuilder buf = new StringBuilder();
62 int depth = 0;
63 int i = -1;
64 while ((i = reader.read()) != -1) {
65 char c = (char) i;
66
67 if (c == '<') {
68 depth++;
69 } else if (c == '>') {
70 depth--;
71 } else if (depth == 0) {
72 if (c == ';') {
73 return buf.toString();
74 } else {
75 buf.append(c);
76 }
77 }
78 }
79
80 return null;
81 }
82
83 public static List<String> getClasses(String signature) {
84 final List<String> classNames = Lists.newArrayList();
85 update(signature, new ClassNameUpdater() {
86 @Override
87 public String update(String className) {
88 classNames.add(className);
89 return className;
90 }
91 });
92 return classNames;
93 }
94}
diff --git a/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java b/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java
new file mode 100644
index 00000000..8329d0d2
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java
@@ -0,0 +1,29 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public enum TranslationDirection {
14
15 Deobfuscating {
16 @Override
17 public <T> T choose(T deobfChoice, T obfChoice) {
18 return deobfChoice;
19 }
20 },
21 Obfuscating {
22 @Override
23 public <T> T choose(T deobfChoice, T obfChoice) {
24 return obfChoice;
25 }
26 };
27
28 public abstract <T> T choose(T deobfChoice, T obfChoice);
29}
diff --git a/src/main/java/cuchaz/enigma/mapping/Translator.java b/src/main/java/cuchaz/enigma/mapping/Translator.java
new file mode 100644
index 00000000..2829a75f
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/Translator.java
@@ -0,0 +1,289 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import com.google.common.collect.Lists;
14import com.google.common.collect.Maps;
15
16import java.util.List;
17import java.util.Map;
18
19import cuchaz.enigma.analysis.TranslationIndex;
20
21public class Translator {
22
23 private TranslationDirection m_direction;
24 private Map<String, ClassMapping> m_classes;
25 private TranslationIndex m_index;
26
27 private ClassNameReplacer m_classNameReplacer = new ClassNameReplacer() {
28 @Override
29 public String replace(String className) {
30 return translateEntry(new ClassEntry(className)).getName();
31 }
32 };
33
34 public Translator() {
35 m_direction = null;
36 m_classes = Maps.newHashMap();
37 m_index = new TranslationIndex();
38 }
39
40 public Translator(TranslationDirection direction, Map<String, ClassMapping> classes, TranslationIndex index) {
41 m_direction = direction;
42 m_classes = classes;
43 m_index = index;
44 }
45
46 public TranslationDirection getDirection() {
47 return m_direction;
48 }
49
50 public TranslationIndex getTranslationIndex() {
51 return m_index;
52 }
53
54 @SuppressWarnings("unchecked")
55 public <T extends Entry> T translateEntry(T entry) {
56 if (entry instanceof ClassEntry) {
57 return (T) translateEntry((ClassEntry) entry);
58 } else if (entry instanceof FieldEntry) {
59 return (T) translateEntry((FieldEntry) entry);
60 } else if (entry instanceof MethodEntry) {
61 return (T) translateEntry((MethodEntry) entry);
62 } else if (entry instanceof ConstructorEntry) {
63 return (T) translateEntry((ConstructorEntry) entry);
64 } else if (entry instanceof ArgumentEntry) {
65 return (T) translateEntry((ArgumentEntry) entry);
66 } else {
67 throw new Error("Unknown entry type: " + entry.getClass().getName());
68 }
69 }
70
71 public <T extends Entry> String translate(T entry) {
72 if (entry instanceof ClassEntry) {
73 return translate((ClassEntry) entry);
74 } else if (entry instanceof FieldEntry) {
75 return translate((FieldEntry) entry);
76 } else if (entry instanceof MethodEntry) {
77 return translate((MethodEntry) entry);
78 } else if (entry instanceof ConstructorEntry) {
79 return translate((ConstructorEntry) entry);
80 } else if (entry instanceof ArgumentEntry) {
81 return translate((ArgumentEntry) entry);
82 } else {
83 throw new Error("Unknown entry type: " + entry.getClass().getName());
84 }
85 }
86
87 public String translate(ClassEntry in) {
88 ClassEntry translated = translateEntry(in);
89 if (translated.equals(in)) {
90 return null;
91 }
92 return translated.getName();
93 }
94
95 public String translateClass(String className) {
96 return translate(new ClassEntry(className));
97 }
98
99 public ClassEntry translateEntry(ClassEntry in) {
100
101 if (in.isInnerClass()) {
102
103 // translate as much of the class chain as we can
104 List<ClassMapping> mappingsChain = getClassMappingChain(in);
105 String[] obfClassNames = in.getName().split("\\$");
106 StringBuilder buf = new StringBuilder();
107 for (int i = 0; i < obfClassNames.length; i++) {
108 boolean isFirstClass = buf.length() == 0;
109 String className = null;
110 ClassMapping classMapping = mappingsChain.get(i);
111 if (classMapping != null) {
112 className = m_direction.choose(
113 classMapping.getDeobfName(),
114 isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName()
115 );
116 }
117 if (className == null) {
118 className = obfClassNames[i];
119 }
120 if (!isFirstClass) {
121 buf.append("$");
122 }
123 buf.append(className);
124 }
125 return new ClassEntry(buf.toString());
126
127 } else {
128
129 // normal classes are easy
130 ClassMapping classMapping = m_classes.get(in.getName());
131 if (classMapping == null) {
132 return in;
133 }
134 return m_direction.choose(
135 classMapping.getDeobfName() != null ? new ClassEntry(classMapping.getDeobfName()) : in,
136 new ClassEntry(classMapping.getObfFullName())
137 );
138 }
139 }
140
141 public String translate(FieldEntry in) {
142
143 // resolve the class entry
144 ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in);
145 if (resolvedClassEntry != null) {
146
147 // look for the class
148 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
149 if (classMapping != null) {
150
151 // look for the field
152 String translatedName = m_direction.choose(
153 classMapping.getDeobfFieldName(in.getName(), in.getType()),
154 classMapping.getObfFieldName(in.getName(), translateType(in.getType()))
155 );
156 if (translatedName != null) {
157 return translatedName;
158 }
159 }
160 }
161 return null;
162 }
163
164 public FieldEntry translateEntry(FieldEntry in) {
165 String name = translate(in);
166 if (name == null) {
167 name = in.getName();
168 }
169 return new FieldEntry(translateEntry(in.getClassEntry()), name, translateType(in.getType()));
170 }
171
172 public String translate(MethodEntry in) {
173
174 // resolve the class entry
175 ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in);
176 if (resolvedClassEntry != null) {
177
178 // look for class
179 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
180 if (classMapping != null) {
181
182 // look for the method
183 MethodMapping methodMapping = m_direction.choose(
184 classMapping.getMethodByObf(in.getName(), in.getSignature()),
185 classMapping.getMethodByDeobf(in.getName(), translateSignature(in.getSignature()))
186 );
187 if (methodMapping != null) {
188 return m_direction.choose(methodMapping.getDeobfName(), methodMapping.getObfName());
189 }
190 }
191 }
192 return null;
193 }
194
195 public MethodEntry translateEntry(MethodEntry in) {
196 String name = translate(in);
197 if (name == null) {
198 name = in.getName();
199 }
200 return new MethodEntry(translateEntry(in.getClassEntry()), name, translateSignature(in.getSignature()));
201 }
202
203 public ConstructorEntry translateEntry(ConstructorEntry in) {
204 if (in.isStatic()) {
205 return new ConstructorEntry(translateEntry(in.getClassEntry()));
206 } else {
207 return new ConstructorEntry(translateEntry(in.getClassEntry()), translateSignature(in.getSignature()));
208 }
209 }
210
211 public BehaviorEntry translateEntry(BehaviorEntry in) {
212 if (in instanceof MethodEntry) {
213 return translateEntry((MethodEntry) in);
214 } else if (in instanceof ConstructorEntry) {
215 return translateEntry((ConstructorEntry) in);
216 }
217 throw new Error("Wrong entry type!");
218 }
219
220 public String translate(ArgumentEntry in) {
221
222 // look for the class
223 ClassMapping classMapping = findClassMapping(in.getClassEntry());
224 if (classMapping != null) {
225
226 // look for the method
227 MethodMapping methodMapping = m_direction.choose(
228 classMapping.getMethodByObf(in.getMethodName(), in.getMethodSignature()),
229 classMapping.getMethodByDeobf(in.getMethodName(), translateSignature(in.getMethodSignature()))
230 );
231 if (methodMapping != null) {
232 return m_direction.choose(
233 methodMapping.getDeobfArgumentName(in.getIndex()),
234 methodMapping.getObfArgumentName(in.getIndex())
235 );
236 }
237 }
238 return null;
239 }
240
241 public ArgumentEntry translateEntry(ArgumentEntry in) {
242 String name = translate(in);
243 if (name == null) {
244 name = in.getName();
245 }
246 return new ArgumentEntry(translateEntry(in.getBehaviorEntry()), in.getIndex(), name);
247 }
248
249 public Type translateType(Type type) {
250 return new Type(type, m_classNameReplacer);
251 }
252
253 public Signature translateSignature(Signature signature) {
254 return new Signature(signature, m_classNameReplacer);
255 }
256
257 private ClassMapping findClassMapping(ClassEntry in) {
258 List<ClassMapping> mappingChain = getClassMappingChain(in);
259 return mappingChain.get(mappingChain.size() - 1);
260 }
261
262 private List<ClassMapping> getClassMappingChain(ClassEntry in) {
263
264 // get a list of all the classes in the hierarchy
265 String[] parts = in.getName().split("\\$");
266 List<ClassMapping> mappingsChain = Lists.newArrayList();
267
268 // get mappings for the outer class
269 ClassMapping outerClassMapping = m_classes.get(parts[0]);
270 mappingsChain.add(outerClassMapping);
271
272 for (int i = 1; i < parts.length; i++) {
273
274 // get mappings for the inner class
275 ClassMapping innerClassMapping = null;
276 if (outerClassMapping != null) {
277 innerClassMapping = m_direction.choose(
278 outerClassMapping.getInnerClassByObfSimple(parts[i]),
279 outerClassMapping.getInnerClassByDeobfThenObfSimple(parts[i])
280 );
281 }
282 mappingsChain.add(innerClassMapping);
283 outerClassMapping = innerClassMapping;
284 }
285
286 assert (mappingsChain.size() == parts.length);
287 return mappingsChain;
288 }
289}
diff --git a/src/main/java/cuchaz/enigma/mapping/Type.java b/src/main/java/cuchaz/enigma/mapping/Type.java
new file mode 100644
index 00000000..bfd836c6
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/Type.java
@@ -0,0 +1,249 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import com.google.common.collect.Maps;
14
15import java.io.Serializable;
16import java.util.Map;
17
18public class Type implements Serializable {
19
20 private static final long serialVersionUID = 7862257669347104063L;
21
22 public enum Primitive {
23 Byte('B'),
24 Character('C'),
25 Short('S'),
26 Integer('I'),
27 Long('J'),
28 Float('F'),
29 Double('D'),
30 Boolean('Z');
31
32 private static final Map<Character, Primitive> m_lookup;
33
34 static {
35 m_lookup = Maps.newTreeMap();
36 for (Primitive val : values()) {
37 m_lookup.put(val.getCode(), val);
38 }
39 }
40
41 public static Primitive get(char code) {
42 return m_lookup.get(code);
43 }
44
45 private char m_code;
46
47 Primitive(char code) {
48 m_code = code;
49 }
50
51 public char getCode() {
52 return m_code;
53 }
54 }
55
56 public static String parseFirst(String in) {
57
58 if (in == null || in.length() <= 0) {
59 throw new IllegalArgumentException("No type to parse, input is empty!");
60 }
61
62 // read one type from the input
63
64 char c = in.charAt(0);
65
66 // first check for void
67 if (c == 'V') {
68 return "V";
69 }
70
71 // then check for primitives
72 Primitive primitive = Primitive.get(c);
73 if (primitive != null) {
74 return in.substring(0, 1);
75 }
76
77 // then check for classes
78 if (c == 'L') {
79 return readClass(in);
80 }
81
82 // then check for templates
83 if (c == 'T') {
84 return readClass(in);
85 }
86
87 // then check for arrays
88 int dim = countArrayDimension(in);
89 if (dim > 0) {
90 String arrayType = Type.parseFirst(in.substring(dim));
91 return in.substring(0, dim + arrayType.length());
92 }
93
94 throw new IllegalArgumentException("don't know how to parse: " + in);
95 }
96
97 protected String m_name;
98
99 public Type(String name) {
100
101 // don't deal with generics
102 // this is just for raw jvm types
103 if (name.charAt(0) == 'T' || name.indexOf('<') >= 0 || name.indexOf('>') >= 0) {
104 throw new IllegalArgumentException("don't use with generic types or templates: " + name);
105 }
106
107 m_name = name;
108 }
109
110 public Type(Type other) {
111 m_name = other.m_name;
112 }
113
114 public Type(ClassEntry classEntry) {
115 m_name = "L" + classEntry.getClassName() + ";";
116 }
117
118 public Type(Type other, ClassNameReplacer replacer) {
119 m_name = other.m_name;
120 if (other.isClass()) {
121 String replacedName = replacer.replace(other.getClassEntry().getClassName());
122 if (replacedName != null) {
123 m_name = "L" + replacedName + ";";
124 }
125 } else if (other.isArray() && other.hasClass()) {
126 String replacedName = replacer.replace(other.getClassEntry().getClassName());
127 if (replacedName != null) {
128 m_name = Type.getArrayPrefix(other.getArrayDimension()) + "L" + replacedName + ";";
129 }
130 }
131 }
132
133 @Override
134 public String toString() {
135 return m_name;
136 }
137
138 public boolean isVoid() {
139 return m_name.length() == 1 && m_name.charAt(0) == 'V';
140 }
141
142 public boolean isPrimitive() {
143 return m_name.length() == 1 && Primitive.get(m_name.charAt(0)) != null;
144 }
145
146 public Primitive getPrimitive() {
147 if (!isPrimitive()) {
148 throw new IllegalStateException("not a primitive");
149 }
150 return Primitive.get(m_name.charAt(0));
151 }
152
153 public boolean isClass() {
154 return m_name.charAt(0) == 'L' && m_name.charAt(m_name.length() - 1) == ';';
155 }
156
157 public ClassEntry getClassEntry() {
158 if (isClass()) {
159 String name = m_name.substring(1, m_name.length() - 1);
160
161 int pos = name.indexOf('<');
162 if (pos >= 0) {
163 // remove the parameters from the class name
164 name = name.substring(0, pos);
165 }
166
167 return new ClassEntry(name);
168
169 } else if (isArray() && getArrayType().isClass()) {
170 return getArrayType().getClassEntry();
171 } else {
172 throw new IllegalStateException("type doesn't have a class");
173 }
174 }
175
176 public boolean isArray() {
177 return m_name.charAt(0) == '[';
178 }
179
180 public int getArrayDimension() {
181 if (!isArray()) {
182 throw new IllegalStateException("not an array");
183 }
184 return countArrayDimension(m_name);
185 }
186
187 public Type getArrayType() {
188 if (!isArray()) {
189 throw new IllegalStateException("not an array");
190 }
191 return new Type(m_name.substring(getArrayDimension(), m_name.length()));
192 }
193
194 private static String getArrayPrefix(int dimension) {
195 StringBuilder buf = new StringBuilder();
196 for (int i = 0; i < dimension; i++) {
197 buf.append("[");
198 }
199 return buf.toString();
200 }
201
202 public boolean hasClass() {
203 return isClass() || (isArray() && getArrayType().hasClass());
204 }
205
206 @Override
207 public boolean equals(Object other) {
208 if (other instanceof Type) {
209 return equals((Type) other);
210 }
211 return false;
212 }
213
214 public boolean equals(Type other) {
215 return m_name.equals(other.m_name);
216 }
217
218 public int hashCode() {
219 return m_name.hashCode();
220 }
221
222 private static int countArrayDimension(String in) {
223 int i = 0;
224 for (; i < in.length() && in.charAt(i) == '['; i++) {
225 ;
226 }
227 return i;
228 }
229
230 private static String readClass(String in) {
231 // read all the characters in the buffer until we hit a ';'
232 // include the parameters too
233 StringBuilder buf = new StringBuilder();
234 int depth = 0;
235 for (int i = 0; i < in.length(); i++) {
236 char c = in.charAt(i);
237 buf.append(c);
238
239 if (c == '<') {
240 depth++;
241 } else if (c == '>') {
242 depth--;
243 } else if (depth == 0 && c == ';') {
244 return buf.toString();
245 }
246 }
247 return null;
248 }
249}
diff --git a/src/test/java/cuchaz/enigma/TestDeobfed.java b/src/test/java/cuchaz/enigma/TestDeobfed.java
new file mode 100644
index 00000000..5f3ef8cf
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/TestDeobfed.java
@@ -0,0 +1,95 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13
14import static cuchaz.enigma.TestEntryFactory.*;
15import static org.hamcrest.MatcherAssert.*;
16import static org.hamcrest.Matchers.*;
17
18import java.util.jar.JarFile;
19
20import org.junit.BeforeClass;
21import org.junit.Test;
22
23import cuchaz.enigma.analysis.JarIndex;
24
25
26public class TestDeobfed {
27
28 private static JarFile m_jar;
29 private static JarIndex m_index;
30
31 @BeforeClass
32 public static void beforeClass()
33 throws Exception {
34 m_jar = new JarFile("build/test-deobf/translation.jar");
35 m_index = new JarIndex();
36 m_index.indexJar(m_jar, true);
37 }
38
39 @Test
40 public void obfEntries() {
41 assertThat(m_index.getObfClassEntries(), containsInAnyOrder(
42 newClass("cuchaz/enigma/inputs/Keep"),
43 newClass("none/a"),
44 newClass("none/b"),
45 newClass("none/c"),
46 newClass("none/d"),
47 newClass("none/d$1"),
48 newClass("none/e"),
49 newClass("none/f"),
50 newClass("none/g"),
51 newClass("none/g$a"),
52 newClass("none/g$a$a"),
53 newClass("none/g$b"),
54 newClass("none/g$b$a"),
55 newClass("none/h"),
56 newClass("none/h$a"),
57 newClass("none/h$a$a"),
58 newClass("none/h$b"),
59 newClass("none/h$b$a"),
60 newClass("none/h$b$a$a"),
61 newClass("none/h$b$a$b"),
62 newClass("none/i"),
63 newClass("none/i$a"),
64 newClass("none/i$b")
65 ));
66 }
67
68 @Test
69 public void decompile()
70 throws Exception {
71 Deobfuscator deobfuscator = new Deobfuscator(m_jar);
72 deobfuscator.getSourceTree("none/a");
73 deobfuscator.getSourceTree("none/b");
74 deobfuscator.getSourceTree("none/c");
75 deobfuscator.getSourceTree("none/d");
76 deobfuscator.getSourceTree("none/d$1");
77 deobfuscator.getSourceTree("none/e");
78 deobfuscator.getSourceTree("none/f");
79 deobfuscator.getSourceTree("none/g");
80 deobfuscator.getSourceTree("none/g$a");
81 deobfuscator.getSourceTree("none/g$a$a");
82 deobfuscator.getSourceTree("none/g$b");
83 deobfuscator.getSourceTree("none/g$b$a");
84 deobfuscator.getSourceTree("none/h");
85 deobfuscator.getSourceTree("none/h$a");
86 deobfuscator.getSourceTree("none/h$a$a");
87 deobfuscator.getSourceTree("none/h$b");
88 deobfuscator.getSourceTree("none/h$b$a");
89 deobfuscator.getSourceTree("none/h$b$a$a");
90 deobfuscator.getSourceTree("none/h$b$a$b");
91 deobfuscator.getSourceTree("none/i");
92 deobfuscator.getSourceTree("none/i$a");
93 deobfuscator.getSourceTree("none/i$b");
94 }
95}
diff --git a/src/test/java/cuchaz/enigma/TestDeobfuscator.java b/src/test/java/cuchaz/enigma/TestDeobfuscator.java
new file mode 100644
index 00000000..1b0aa74d
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/TestDeobfuscator.java
@@ -0,0 +1,57 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static org.junit.Assert.*;
14
15import java.io.IOException;
16import java.util.List;
17import java.util.jar.JarFile;
18
19import org.junit.Test;
20
21import com.google.common.collect.Lists;
22
23import cuchaz.enigma.mapping.ClassEntry;
24
25public class TestDeobfuscator {
26
27 private Deobfuscator getDeobfuscator()
28 throws IOException {
29 return new Deobfuscator(new JarFile("build/test-obf/loneClass.jar"));
30 }
31
32 @Test
33 public void loadJar()
34 throws Exception {
35 getDeobfuscator();
36 }
37
38 @Test
39 public void getClasses()
40 throws Exception {
41 Deobfuscator deobfuscator = getDeobfuscator();
42 List<ClassEntry> obfClasses = Lists.newArrayList();
43 List<ClassEntry> deobfClasses = Lists.newArrayList();
44 deobfuscator.getSeparatedClasses(obfClasses, deobfClasses);
45 assertEquals(1, obfClasses.size());
46 assertEquals("none/a", obfClasses.get(0).getName());
47 assertEquals(1, deobfClasses.size());
48 assertEquals("cuchaz/enigma/inputs/Keep", deobfClasses.get(0).getName());
49 }
50
51 @Test
52 public void decompileClass()
53 throws Exception {
54 Deobfuscator deobfuscator = getDeobfuscator();
55 deobfuscator.getSource(deobfuscator.getSourceTree("none/a"));
56 }
57}
diff --git a/src/test/java/cuchaz/enigma/TestEntryFactory.java b/src/test/java/cuchaz/enigma/TestEntryFactory.java
new file mode 100644
index 00000000..4aa773b6
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/TestEntryFactory.java
@@ -0,0 +1,67 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import cuchaz.enigma.analysis.EntryReference;
14import cuchaz.enigma.mapping.BehaviorEntry;
15import cuchaz.enigma.mapping.ClassEntry;
16import cuchaz.enigma.mapping.ConstructorEntry;
17import cuchaz.enigma.mapping.FieldEntry;
18import cuchaz.enigma.mapping.MethodEntry;
19import cuchaz.enigma.mapping.Signature;
20import cuchaz.enigma.mapping.Type;
21
22public class TestEntryFactory {
23
24 public static ClassEntry newClass(String name) {
25 return new ClassEntry(name);
26 }
27
28 public static FieldEntry newField(String className, String fieldName, String fieldType) {
29 return newField(newClass(className), fieldName, fieldType);
30 }
31
32 public static FieldEntry newField(ClassEntry classEntry, String fieldName, String fieldType) {
33 return new FieldEntry(classEntry, fieldName, new Type(fieldType));
34 }
35
36 public static MethodEntry newMethod(String className, String methodName, String methodSignature) {
37 return newMethod(newClass(className), methodName, methodSignature);
38 }
39
40 public static MethodEntry newMethod(ClassEntry classEntry, String methodName, String methodSignature) {
41 return new MethodEntry(classEntry, methodName, new Signature(methodSignature));
42 }
43
44 public static ConstructorEntry newConstructor(String className, String signature) {
45 return newConstructor(newClass(className), signature);
46 }
47
48 public static ConstructorEntry newConstructor(ClassEntry classEntry, String signature) {
49 return new ConstructorEntry(classEntry, new Signature(signature));
50 }
51
52 public static EntryReference<FieldEntry,BehaviorEntry> newFieldReferenceByMethod(FieldEntry fieldEntry, String callerClassName, String callerName, String callerSignature) {
53 return new EntryReference<FieldEntry,BehaviorEntry>(fieldEntry, "", newMethod(callerClassName, callerName, callerSignature));
54 }
55
56 public static EntryReference<FieldEntry,BehaviorEntry> newFieldReferenceByConstructor(FieldEntry fieldEntry, String callerClassName, String callerSignature) {
57 return new EntryReference<FieldEntry,BehaviorEntry>(fieldEntry, "", newConstructor(callerClassName, callerSignature));
58 }
59
60 public static EntryReference<BehaviorEntry,BehaviorEntry> newBehaviorReferenceByMethod(BehaviorEntry behaviorEntry, String callerClassName, String callerName, String callerSignature) {
61 return new EntryReference<BehaviorEntry,BehaviorEntry>(behaviorEntry, "", newMethod(callerClassName, callerName, callerSignature));
62 }
63
64 public static EntryReference<BehaviorEntry,BehaviorEntry> newBehaviorReferenceByConstructor(BehaviorEntry behaviorEntry, String callerClassName, String callerSignature) {
65 return new EntryReference<BehaviorEntry,BehaviorEntry>(behaviorEntry, "", newConstructor(callerClassName, callerSignature));
66 }
67}
diff --git a/src/test/java/cuchaz/enigma/TestInnerClasses.java b/src/test/java/cuchaz/enigma/TestInnerClasses.java
new file mode 100644
index 00000000..a4f90217
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/TestInnerClasses.java
@@ -0,0 +1,132 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static org.hamcrest.MatcherAssert.*;
14import static org.hamcrest.Matchers.*;
15
16import java.util.jar.JarFile;
17
18import org.junit.Test;
19
20import static cuchaz.enigma.TestEntryFactory.*;
21
22import cuchaz.enigma.analysis.JarIndex;
23import cuchaz.enigma.mapping.ClassEntry;
24
25public class TestInnerClasses {
26
27 private JarIndex m_index;
28 private Deobfuscator m_deobfuscator;
29
30 private static final ClassEntry AnonymousOuter = newClass("none/a");
31 private static final ClassEntry AnonymousInner = newClass("none/a$1");
32 private static final ClassEntry SimpleOuter = newClass("none/d");
33 private static final ClassEntry SimpleInner = newClass("none/d$a");
34 private static final ClassEntry ConstructorArgsOuter = newClass("none/c");
35 private static final ClassEntry ConstructorArgsInner = newClass("none/c$a");
36 private static final ClassEntry AnonymousWithScopeArgsOuter = newClass("none/b");
37 private static final ClassEntry AnonymousWithScopeArgsInner = newClass("none/b$1");
38 private static final ClassEntry AnonymousWithOuterAccessOuter = newClass("none/e");
39 private static final ClassEntry AnonymousWithOuterAccessInner = newClass("none/e$1");
40 private static final ClassEntry ClassTreeRoot = newClass("none/f");
41 private static final ClassEntry ClassTreeLevel1 = newClass("none/f$a");
42 private static final ClassEntry ClassTreeLevel2 = newClass("none/f$a$a");
43 private static final ClassEntry ClassTreeLevel3 = newClass("none/f$a$a$a");
44
45 public TestInnerClasses()
46 throws Exception {
47 m_index = new JarIndex();
48 JarFile jar = new JarFile("build/test-obf/innerClasses.jar");
49 m_index.indexJar(jar, true);
50 m_deobfuscator = new Deobfuscator(jar);
51 }
52
53 @Test
54 public void simple() {
55 assertThat(m_index.getOuterClass(SimpleInner), is(SimpleOuter));
56 assertThat(m_index.getInnerClasses(SimpleOuter), containsInAnyOrder(SimpleInner));
57 assertThat(m_index.isAnonymousClass(SimpleInner), is(false));
58 decompile(SimpleOuter);
59 }
60
61 @Test
62 public void anonymous() {
63 assertThat(m_index.getOuterClass(AnonymousInner), is(AnonymousOuter));
64 assertThat(m_index.getInnerClasses(AnonymousOuter), containsInAnyOrder(AnonymousInner));
65 assertThat(m_index.isAnonymousClass(AnonymousInner), is(true));
66 decompile(AnonymousOuter);
67 }
68
69 @Test
70 public void constructorArgs() {
71 assertThat(m_index.getOuterClass(ConstructorArgsInner), is(ConstructorArgsOuter));
72 assertThat(m_index.getInnerClasses(ConstructorArgsOuter), containsInAnyOrder(ConstructorArgsInner));
73 assertThat(m_index.isAnonymousClass(ConstructorArgsInner), is(false));
74 decompile(ConstructorArgsOuter);
75 }
76
77 @Test
78 public void anonymousWithScopeArgs() {
79 assertThat(m_index.getOuterClass(AnonymousWithScopeArgsInner), is(AnonymousWithScopeArgsOuter));
80 assertThat(m_index.getInnerClasses(AnonymousWithScopeArgsOuter), containsInAnyOrder(AnonymousWithScopeArgsInner));
81 assertThat(m_index.isAnonymousClass(AnonymousWithScopeArgsInner), is(true));
82 decompile(AnonymousWithScopeArgsOuter);
83 }
84
85 @Test
86 public void anonymousWithOuterAccess() {
87 assertThat(m_index.getOuterClass(AnonymousWithOuterAccessInner), is(AnonymousWithOuterAccessOuter));
88 assertThat(m_index.getInnerClasses(AnonymousWithOuterAccessOuter), containsInAnyOrder(AnonymousWithOuterAccessInner));
89 assertThat(m_index.isAnonymousClass(AnonymousWithOuterAccessInner), is(true));
90 decompile(AnonymousWithOuterAccessOuter);
91 }
92
93 @Test
94 public void classTree() {
95
96 // root level
97 assertThat(m_index.containsObfClass(ClassTreeRoot), is(true));
98 assertThat(m_index.getOuterClass(ClassTreeRoot), is(nullValue()));
99 assertThat(m_index.getInnerClasses(ClassTreeRoot), containsInAnyOrder(ClassTreeLevel1));
100
101 // level 1
102 ClassEntry fullClassEntry = new ClassEntry(ClassTreeRoot.getName()
103 + "$" + ClassTreeLevel1.getInnermostClassName()
104 );
105 assertThat(m_index.containsObfClass(fullClassEntry), is(true));
106 assertThat(m_index.getOuterClass(ClassTreeLevel1), is(ClassTreeRoot));
107 assertThat(m_index.getInnerClasses(ClassTreeLevel1), containsInAnyOrder(ClassTreeLevel2));
108
109 // level 2
110 fullClassEntry = new ClassEntry(ClassTreeRoot.getName()
111 + "$" + ClassTreeLevel1.getInnermostClassName()
112 + "$" + ClassTreeLevel2.getInnermostClassName()
113 );
114 assertThat(m_index.containsObfClass(fullClassEntry), is(true));
115 assertThat(m_index.getOuterClass(ClassTreeLevel2), is(ClassTreeLevel1));
116 assertThat(m_index.getInnerClasses(ClassTreeLevel2), containsInAnyOrder(ClassTreeLevel3));
117
118 // level 3
119 fullClassEntry = new ClassEntry(ClassTreeRoot.getName()
120 + "$" + ClassTreeLevel1.getInnermostClassName()
121 + "$" + ClassTreeLevel2.getInnermostClassName()
122 + "$" + ClassTreeLevel3.getInnermostClassName()
123 );
124 assertThat(m_index.containsObfClass(fullClassEntry), is(true));
125 assertThat(m_index.getOuterClass(ClassTreeLevel3), is(ClassTreeLevel2));
126 assertThat(m_index.getInnerClasses(ClassTreeLevel3), is(empty()));
127 }
128
129 private void decompile(ClassEntry classEntry) {
130 m_deobfuscator.getSourceTree(classEntry.getName());
131 }
132}
diff --git a/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java b/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java
new file mode 100644
index 00000000..606801bd
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java
@@ -0,0 +1,124 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static cuchaz.enigma.TestEntryFactory.*;
14import static org.hamcrest.MatcherAssert.*;
15import static org.hamcrest.Matchers.*;
16
17import java.io.File;
18import java.util.Collection;
19import java.util.jar.JarFile;
20
21import org.junit.Test;
22
23import cuchaz.enigma.analysis.EntryReference;
24import cuchaz.enigma.analysis.JarIndex;
25import cuchaz.enigma.mapping.BehaviorEntry;
26import cuchaz.enigma.mapping.ClassEntry;
27
28public class TestJarIndexConstructorReferences {
29
30 private JarIndex m_index;
31
32 private ClassEntry m_baseClass = newClass("none/a");
33 private ClassEntry m_subClass = newClass("none/d");
34 private ClassEntry m_subsubClass = newClass("none/e");
35 private ClassEntry m_defaultClass = newClass("none/c");
36 private ClassEntry m_callerClass = newClass("none/b");
37
38 public TestJarIndexConstructorReferences()
39 throws Exception {
40 File jarFile = new File("build/test-obf/constructors.jar");
41 m_index = new JarIndex();
42 m_index.indexJar(new JarFile(jarFile), false);
43 }
44
45 @Test
46 public void obfEntries() {
47 assertThat(m_index.getObfClassEntries(), containsInAnyOrder(newClass("cuchaz/enigma/inputs/Keep"), m_baseClass, m_subClass, m_subsubClass, m_defaultClass, m_callerClass));
48 }
49
50 @Test
51 @SuppressWarnings("unchecked")
52 public void baseDefault() {
53 BehaviorEntry source = newConstructor(m_baseClass, "()V");
54 Collection<EntryReference<BehaviorEntry,BehaviorEntry>> references = m_index.getBehaviorReferences(source);
55 assertThat(references, containsInAnyOrder(
56 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "a", "()V"),
57 newBehaviorReferenceByConstructor(source, m_subClass.getName(), "()V"),
58 newBehaviorReferenceByConstructor(source, m_subClass.getName(), "(III)V")
59 ));
60 }
61
62 @Test
63 @SuppressWarnings("unchecked")
64 public void baseInt() {
65 BehaviorEntry source = newConstructor(m_baseClass, "(I)V");
66 assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder(
67 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "b", "()V")
68 ));
69 }
70
71 @Test
72 @SuppressWarnings("unchecked")
73 public void subDefault() {
74 BehaviorEntry source = newConstructor(m_subClass, "()V");
75 assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder(
76 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "c", "()V"),
77 newBehaviorReferenceByConstructor(source, m_subClass.getName(), "(I)V")
78 ));
79 }
80
81 @Test
82 @SuppressWarnings("unchecked")
83 public void subInt() {
84 BehaviorEntry source = newConstructor(m_subClass, "(I)V");
85 assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder(
86 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "d", "()V"),
87 newBehaviorReferenceByConstructor(source, m_subClass.getName(), "(II)V"),
88 newBehaviorReferenceByConstructor(source, m_subsubClass.getName(), "(I)V")
89 ));
90 }
91
92 @Test
93 @SuppressWarnings("unchecked")
94 public void subIntInt() {
95 BehaviorEntry source = newConstructor(m_subClass, "(II)V");
96 assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder(
97 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "e", "()V")
98 ));
99 }
100
101 @Test
102 public void subIntIntInt() {
103 BehaviorEntry source = newConstructor(m_subClass, "(III)V");
104 assertThat(m_index.getBehaviorReferences(source), is(empty()));
105 }
106
107 @Test
108 @SuppressWarnings("unchecked")
109 public void subsubInt() {
110 BehaviorEntry source = newConstructor(m_subsubClass, "(I)V");
111 assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder(
112 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "f", "()V")
113 ));
114 }
115
116 @Test
117 @SuppressWarnings("unchecked")
118 public void defaultConstructable() {
119 BehaviorEntry source = newConstructor(m_defaultClass, "()V");
120 assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder(
121 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "g", "()V")
122 ));
123 }
124}
diff --git a/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java b/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java
new file mode 100644
index 00000000..84d21158
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java
@@ -0,0 +1,239 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByConstructor;
14import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByMethod;
15import static cuchaz.enigma.TestEntryFactory.newClass;
16import static cuchaz.enigma.TestEntryFactory.newConstructor;
17import static cuchaz.enigma.TestEntryFactory.newField;
18import static cuchaz.enigma.TestEntryFactory.newFieldReferenceByConstructor;
19import static cuchaz.enigma.TestEntryFactory.newFieldReferenceByMethod;
20import static cuchaz.enigma.TestEntryFactory.newMethod;
21import static org.hamcrest.MatcherAssert.assertThat;
22import static org.hamcrest.Matchers.contains;
23import static org.hamcrest.Matchers.containsInAnyOrder;
24import static org.hamcrest.Matchers.empty;
25import static org.hamcrest.Matchers.is;
26
27import java.util.Collection;
28import java.util.Set;
29import java.util.jar.JarFile;
30
31import org.junit.Test;
32
33import cuchaz.enigma.analysis.Access;
34import cuchaz.enigma.analysis.EntryReference;
35import cuchaz.enigma.analysis.JarIndex;
36import cuchaz.enigma.analysis.TranslationIndex;
37import cuchaz.enigma.mapping.BehaviorEntry;
38import cuchaz.enigma.mapping.ClassEntry;
39import cuchaz.enigma.mapping.FieldEntry;
40import cuchaz.enigma.mapping.MethodEntry;
41
42public class TestJarIndexInheritanceTree {
43
44 private JarIndex m_index;
45
46 private ClassEntry m_objectClass = newClass("java/lang/Object");
47 private ClassEntry m_baseClass = newClass("none/a");
48 private ClassEntry m_subClassA = newClass("none/b");
49 private ClassEntry m_subClassAA = newClass("none/d");
50 private ClassEntry m_subClassB = newClass("none/c");
51 private FieldEntry m_nameField = newField(m_baseClass, "a", "Ljava/lang/String;");
52 private FieldEntry m_numThingsField = newField(m_subClassB, "a", "I");
53
54 public TestJarIndexInheritanceTree()
55 throws Exception {
56 m_index = new JarIndex();
57 m_index.indexJar(new JarFile("build/test-obf/inheritanceTree.jar"), false);
58 }
59
60 @Test
61 public void obfEntries() {
62 assertThat(m_index.getObfClassEntries(), containsInAnyOrder(
63 newClass("cuchaz/enigma/inputs/Keep"),
64 m_baseClass,
65 m_subClassA,
66 m_subClassAA,
67 m_subClassB
68 ));
69 }
70
71 @Test
72 public void translationIndex() {
73
74 TranslationIndex index = m_index.getTranslationIndex();
75
76 // base class
77 assertThat(index.getSuperclass(m_baseClass), is(m_objectClass));
78 assertThat(index.getAncestry(m_baseClass), contains(m_objectClass));
79 assertThat(index.getSubclass(m_baseClass), containsInAnyOrder(
80 m_subClassA,
81 m_subClassB
82 ));
83
84 // subclass a
85 assertThat(index.getSuperclass(m_subClassA), is(m_baseClass));
86 assertThat(index.getAncestry(m_subClassA), contains(m_baseClass, m_objectClass));
87 assertThat(index.getSubclass(m_subClassA), contains(m_subClassAA));
88
89 // subclass aa
90 assertThat(index.getSuperclass(m_subClassAA), is(m_subClassA));
91 assertThat(index.getAncestry(m_subClassAA), contains(m_subClassA, m_baseClass, m_objectClass));
92 assertThat(index.getSubclass(m_subClassAA), is(empty()));
93
94 // subclass b
95 assertThat(index.getSuperclass(m_subClassB), is(m_baseClass));
96 assertThat(index.getAncestry(m_subClassB), contains(m_baseClass, m_objectClass));
97 assertThat(index.getSubclass(m_subClassB), is(empty()));
98 }
99
100 @Test
101 public void access() {
102 assertThat(m_index.getAccess(m_nameField), is(Access.Private));
103 assertThat(m_index.getAccess(m_numThingsField), is(Access.Private));
104 }
105
106 @Test
107 public void relatedMethodImplementations() {
108
109 Set<MethodEntry> entries;
110
111 // getName()
112 entries = m_index.getRelatedMethodImplementations(newMethod(m_baseClass, "a", "()Ljava/lang/String;"));
113 assertThat(entries, containsInAnyOrder(
114 newMethod(m_baseClass, "a", "()Ljava/lang/String;"),
115 newMethod(m_subClassAA, "a", "()Ljava/lang/String;")
116 ));
117 entries = m_index.getRelatedMethodImplementations(newMethod(m_subClassAA, "a", "()Ljava/lang/String;"));
118 assertThat(entries, containsInAnyOrder(
119 newMethod(m_baseClass, "a", "()Ljava/lang/String;"),
120 newMethod(m_subClassAA, "a", "()Ljava/lang/String;")
121 ));
122
123 // doBaseThings()
124 entries = m_index.getRelatedMethodImplementations(newMethod(m_baseClass, "a", "()V"));
125 assertThat(entries, containsInAnyOrder(
126 newMethod(m_baseClass, "a", "()V"),
127 newMethod(m_subClassAA, "a", "()V"),
128 newMethod(m_subClassB, "a", "()V")
129 ));
130 entries = m_index.getRelatedMethodImplementations(newMethod(m_subClassAA, "a", "()V"));
131 assertThat(entries, containsInAnyOrder(
132 newMethod(m_baseClass, "a", "()V"),
133 newMethod(m_subClassAA, "a", "()V"),
134 newMethod(m_subClassB, "a", "()V")
135 ));
136 entries = m_index.getRelatedMethodImplementations(newMethod(m_subClassB, "a", "()V"));
137 assertThat(entries, containsInAnyOrder(
138 newMethod(m_baseClass, "a", "()V"),
139 newMethod(m_subClassAA, "a", "()V"),
140 newMethod(m_subClassB, "a", "()V")
141 ));
142
143 // doBThings
144 entries = m_index.getRelatedMethodImplementations(newMethod(m_subClassB, "b", "()V"));
145 assertThat(entries, containsInAnyOrder(newMethod(m_subClassB, "b", "()V")));
146 }
147
148 @Test
149 @SuppressWarnings("unchecked")
150 public void fieldReferences() {
151 Collection<EntryReference<FieldEntry,BehaviorEntry>> references;
152
153 // name
154 references = m_index.getFieldReferences(m_nameField);
155 assertThat(references, containsInAnyOrder(
156 newFieldReferenceByConstructor(m_nameField, m_baseClass.getName(), "(Ljava/lang/String;)V"),
157 newFieldReferenceByMethod(m_nameField, m_baseClass.getName(), "a", "()Ljava/lang/String;")
158 ));
159
160 // numThings
161 references = m_index.getFieldReferences(m_numThingsField);
162 assertThat(references, containsInAnyOrder(
163 newFieldReferenceByConstructor(m_numThingsField, m_subClassB.getName(), "()V"),
164 newFieldReferenceByMethod(m_numThingsField, m_subClassB.getName(), "b", "()V")
165 ));
166 }
167
168 @Test
169 @SuppressWarnings("unchecked")
170 public void behaviorReferences() {
171
172 BehaviorEntry source;
173 Collection<EntryReference<BehaviorEntry,BehaviorEntry>> references;
174
175 // baseClass constructor
176 source = newConstructor(m_baseClass, "(Ljava/lang/String;)V");
177 references = m_index.getBehaviorReferences(source);
178 assertThat(references, containsInAnyOrder(
179 newBehaviorReferenceByConstructor(source, m_subClassA.getName(), "(Ljava/lang/String;)V"),
180 newBehaviorReferenceByConstructor(source, m_subClassB.getName(), "()V")
181 ));
182
183 // subClassA constructor
184 source = newConstructor(m_subClassA, "(Ljava/lang/String;)V");
185 references = m_index.getBehaviorReferences(source);
186 assertThat(references, containsInAnyOrder(
187 newBehaviorReferenceByConstructor(source, m_subClassAA.getName(), "()V")
188 ));
189
190 // baseClass.getName()
191 source = newMethod(m_baseClass, "a", "()Ljava/lang/String;");
192 references = m_index.getBehaviorReferences(source);
193 assertThat(references, containsInAnyOrder(
194 newBehaviorReferenceByMethod(source, m_subClassAA.getName(), "a", "()Ljava/lang/String;"),
195 newBehaviorReferenceByMethod(source, m_subClassB.getName(), "a", "()V")
196 ));
197
198 // subclassAA.getName()
199 source = newMethod(m_subClassAA, "a", "()Ljava/lang/String;");
200 references = m_index.getBehaviorReferences(source);
201 assertThat(references, containsInAnyOrder(
202 newBehaviorReferenceByMethod(source, m_subClassAA.getName(), "a", "()V")
203 ));
204 }
205
206 @Test
207 public void containsEntries() {
208
209 // classes
210 assertThat(m_index.containsObfClass(m_baseClass), is(true));
211 assertThat(m_index.containsObfClass(m_subClassA), is(true));
212 assertThat(m_index.containsObfClass(m_subClassAA), is(true));
213 assertThat(m_index.containsObfClass(m_subClassB), is(true));
214
215 // fields
216 assertThat(m_index.containsObfField(m_nameField), is(true));
217 assertThat(m_index.containsObfField(m_numThingsField), is(true));
218
219 // methods
220 // getName()
221 assertThat(m_index.containsObfBehavior(newMethod(m_baseClass, "a", "()Ljava/lang/String;")), is(true));
222 assertThat(m_index.containsObfBehavior(newMethod(m_subClassA, "a", "()Ljava/lang/String;")), is(false));
223 assertThat(m_index.containsObfBehavior(newMethod(m_subClassAA, "a", "()Ljava/lang/String;")), is(true));
224 assertThat(m_index.containsObfBehavior(newMethod(m_subClassB, "a", "()Ljava/lang/String;")), is(false));
225
226 // doBaseThings()
227 assertThat(m_index.containsObfBehavior(newMethod(m_baseClass, "a", "()V")), is(true));
228 assertThat(m_index.containsObfBehavior(newMethod(m_subClassA, "a", "()V")), is(false));
229 assertThat(m_index.containsObfBehavior(newMethod(m_subClassAA, "a", "()V")), is(true));
230 assertThat(m_index.containsObfBehavior(newMethod(m_subClassB, "a", "()V")), is(true));
231
232 // doBThings()
233 assertThat(m_index.containsObfBehavior(newMethod(m_baseClass, "b", "()V")), is(false));
234 assertThat(m_index.containsObfBehavior(newMethod(m_subClassA, "b", "()V")), is(false));
235 assertThat(m_index.containsObfBehavior(newMethod(m_subClassAA, "b", "()V")), is(false));
236 assertThat(m_index.containsObfBehavior(newMethod(m_subClassB, "b", "()V")), is(true));
237
238 }
239}
diff --git a/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java b/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java
new file mode 100644
index 00000000..bd7b03a0
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java
@@ -0,0 +1,164 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static cuchaz.enigma.TestEntryFactory.*;
14import static org.hamcrest.MatcherAssert.*;
15import static org.hamcrest.Matchers.*;
16
17import java.util.Collection;
18import java.util.Set;
19import java.util.jar.JarFile;
20
21import org.junit.Test;
22
23import cuchaz.enigma.analysis.Access;
24import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
25import cuchaz.enigma.analysis.ClassInheritanceTreeNode;
26import cuchaz.enigma.analysis.EntryReference;
27import cuchaz.enigma.analysis.JarIndex;
28import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
29import cuchaz.enigma.mapping.BehaviorEntry;
30import cuchaz.enigma.mapping.ClassEntry;
31import cuchaz.enigma.mapping.FieldEntry;
32import cuchaz.enigma.mapping.MethodEntry;
33import cuchaz.enigma.mapping.Translator;
34
35public class TestJarIndexLoneClass {
36
37 private JarIndex m_index;
38
39 public TestJarIndexLoneClass()
40 throws Exception {
41 m_index = new JarIndex();
42 m_index.indexJar(new JarFile("build/test-obf/loneClass.jar"), false);
43 }
44
45 @Test
46 public void obfEntries() {
47 assertThat(m_index.getObfClassEntries(), containsInAnyOrder(
48 newClass("cuchaz/enigma/inputs/Keep"),
49 newClass("none/a")
50 ));
51 }
52
53 @Test
54 public void translationIndex() {
55 assertThat(m_index.getTranslationIndex().getSuperclass(new ClassEntry("none/a")), is(new ClassEntry("java/lang/Object")));
56 assertThat(m_index.getTranslationIndex().getSuperclass(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(new ClassEntry("java/lang/Object")));
57 assertThat(m_index.getTranslationIndex().getAncestry(new ClassEntry("none/a")), contains(new ClassEntry("java/lang/Object")));
58 assertThat(m_index.getTranslationIndex().getAncestry(new ClassEntry("cuchaz/enigma/inputs/Keep")), contains(new ClassEntry("java/lang/Object")));
59 assertThat(m_index.getTranslationIndex().getSubclass(new ClassEntry("none/a")), is(empty()));
60 assertThat(m_index.getTranslationIndex().getSubclass(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(empty()));
61 }
62
63 @Test
64 public void access() {
65 assertThat(m_index.getAccess(newField("none/a", "a", "Ljava/lang/String;")), is(Access.Private));
66 assertThat(m_index.getAccess(newMethod("none/a", "a", "()Ljava/lang/String;")), is(Access.Public));
67 assertThat(m_index.getAccess(newField("none/a", "b", "Ljava/lang/String;")), is(nullValue()));
68 assertThat(m_index.getAccess(newField("none/a", "a", "LFoo;")), is(nullValue()));
69 }
70
71 @Test
72 public void classInheritance() {
73 ClassInheritanceTreeNode node = m_index.getClassInheritance(new Translator(), newClass("none/a"));
74 assertThat(node, is(not(nullValue())));
75 assertThat(node.getObfClassName(), is("none/a"));
76 assertThat(node.getChildCount(), is(0));
77 }
78
79 @Test
80 public void methodInheritance() {
81 MethodEntry source = newMethod("none/a", "a", "()Ljava/lang/String;");
82 MethodInheritanceTreeNode node = m_index.getMethodInheritance(new Translator(), source);
83 assertThat(node, is(not(nullValue())));
84 assertThat(node.getMethodEntry(), is(source));
85 assertThat(node.getChildCount(), is(0));
86 }
87
88 @Test
89 public void classImplementations() {
90 ClassImplementationsTreeNode node = m_index.getClassImplementations(new Translator(), newClass("none/a"));
91 assertThat(node, is(nullValue()));
92 }
93
94 @Test
95 public void methodImplementations() {
96 MethodEntry source = newMethod("none/a", "a", "()Ljava/lang/String;");
97 assertThat(m_index.getMethodImplementations(new Translator(), source), is(empty()));
98 }
99
100 @Test
101 public void relatedMethodImplementations() {
102 Set<MethodEntry> entries = m_index.getRelatedMethodImplementations(newMethod("none/a", "a", "()Ljava/lang/String;"));
103 assertThat(entries, containsInAnyOrder(
104 newMethod("none/a", "a", "()Ljava/lang/String;")
105 ));
106 }
107
108 @Test
109 @SuppressWarnings("unchecked")
110 public void fieldReferences() {
111 FieldEntry source = newField("none/a", "a", "Ljava/lang/String;");
112 Collection<EntryReference<FieldEntry,BehaviorEntry>> references = m_index.getFieldReferences(source);
113 assertThat(references, containsInAnyOrder(
114 newFieldReferenceByConstructor(source, "none/a", "(Ljava/lang/String;)V"),
115 newFieldReferenceByMethod(source, "none/a", "a", "()Ljava/lang/String;")
116 ));
117 }
118
119 @Test
120 public void behaviorReferences() {
121 assertThat(m_index.getBehaviorReferences(newMethod("none/a", "a", "()Ljava/lang/String;")), is(empty()));
122 }
123
124 @Test
125 public void innerClasses() {
126 assertThat(m_index.getInnerClasses(newClass("none/a")), is(empty()));
127 }
128
129 @Test
130 public void outerClass() {
131 assertThat(m_index.getOuterClass(newClass("a")), is(nullValue()));
132 }
133
134 @Test
135 public void isAnonymousClass() {
136 assertThat(m_index.isAnonymousClass(newClass("none/a")), is(false));
137 }
138
139 @Test
140 public void interfaces() {
141 assertThat(m_index.getInterfaces("none/a"), is(empty()));
142 }
143
144 @Test
145 public void implementingClasses() {
146 assertThat(m_index.getImplementingClasses("none/a"), is(empty()));
147 }
148
149 @Test
150 public void isInterface() {
151 assertThat(m_index.isInterface("none/a"), is(false));
152 }
153
154 @Test
155 public void testContains() {
156 assertThat(m_index.containsObfClass(newClass("none/a")), is(true));
157 assertThat(m_index.containsObfClass(newClass("none/b")), is(false));
158 assertThat(m_index.containsObfField(newField("none/a", "a", "Ljava/lang/String;")), is(true));
159 assertThat(m_index.containsObfField(newField("none/a", "b", "Ljava/lang/String;")), is(false));
160 assertThat(m_index.containsObfField(newField("none/a", "a", "LFoo;")), is(false));
161 assertThat(m_index.containsObfBehavior(newMethod("none/a", "a", "()Ljava/lang/String;")), is(true));
162 assertThat(m_index.containsObfBehavior(newMethod("none/a", "b", "()Ljava/lang/String;")), is(false));
163 }
164}
diff --git a/src/test/java/cuchaz/enigma/TestSignature.java b/src/test/java/cuchaz/enigma/TestSignature.java
new file mode 100644
index 00000000..8537adfb
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/TestSignature.java
@@ -0,0 +1,268 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static org.hamcrest.MatcherAssert.*;
14import static org.hamcrest.Matchers.*;
15
16import org.junit.Test;
17
18import cuchaz.enigma.mapping.ClassNameReplacer;
19import cuchaz.enigma.mapping.Signature;
20import cuchaz.enigma.mapping.Type;
21
22
23public class TestSignature {
24
25 @Test
26 public void easiest() {
27 final Signature sig = new Signature("()V");
28 assertThat(sig.getArgumentTypes(), is(empty()));
29 assertThat(sig.getReturnType(), is(new Type("V")));
30 }
31
32 @Test
33 public void primitives() {
34 {
35 final Signature sig = new Signature("(I)V");
36 assertThat(sig.getArgumentTypes(), contains(
37 new Type("I")
38 ));
39 assertThat(sig.getReturnType(), is(new Type("V")));
40 }
41 {
42 final Signature sig = new Signature("(I)I");
43 assertThat(sig.getArgumentTypes(), contains(
44 new Type("I")
45 ));
46 assertThat(sig.getReturnType(), is(new Type("I")));
47 }
48 {
49 final Signature sig = new Signature("(IBCJ)Z");
50 assertThat(sig.getArgumentTypes(), contains(
51 new Type("I"),
52 new Type("B"),
53 new Type("C"),
54 new Type("J")
55 ));
56 assertThat(sig.getReturnType(), is(new Type("Z")));
57 }
58 }
59
60 @Test
61 public void classes() {
62 {
63 final Signature sig = new Signature("([LFoo;)V");
64 assertThat(sig.getArgumentTypes().size(), is(1));
65 assertThat(sig.getArgumentTypes().get(0), is(new Type("[LFoo;")));
66 assertThat(sig.getReturnType(), is(new Type("V")));
67 }
68 {
69 final Signature sig = new Signature("(LFoo;)LBar;");
70 assertThat(sig.getArgumentTypes(), contains(
71 new Type("LFoo;")
72 ));
73 assertThat(sig.getReturnType(), is(new Type("LBar;")));
74 }
75 {
76 final Signature sig = new Signature("(LFoo;LMoo;LZoo;)LBar;");
77 assertThat(sig.getArgumentTypes(), contains(
78 new Type("LFoo;"),
79 new Type("LMoo;"),
80 new Type("LZoo;")
81 ));
82 assertThat(sig.getReturnType(), is(new Type("LBar;")));
83 }
84 }
85
86 @Test
87 public void arrays() {
88 {
89 final Signature sig = new Signature("([I)V");
90 assertThat(sig.getArgumentTypes(), contains(
91 new Type("[I")
92 ));
93 assertThat(sig.getReturnType(), is(new Type("V")));
94 }
95 {
96 final Signature sig = new Signature("([I)[J");
97 assertThat(sig.getArgumentTypes(), contains(
98 new Type("[I")
99 ));
100 assertThat(sig.getReturnType(), is(new Type("[J")));
101 }
102 {
103 final Signature sig = new Signature("([I[Z[F)[D");
104 assertThat(sig.getArgumentTypes(), contains(
105 new Type("[I"),
106 new Type("[Z"),
107 new Type("[F")
108 ));
109 assertThat(sig.getReturnType(), is(new Type("[D")));
110 }
111 }
112
113 @Test
114 public void mixed() {
115 {
116 final Signature sig = new Signature("(I[JLFoo;)Z");
117 assertThat(sig.getArgumentTypes(), contains(
118 new Type("I"),
119 new Type("[J"),
120 new Type("LFoo;")
121 ));
122 assertThat(sig.getReturnType(), is(new Type("Z")));
123 }
124 {
125 final Signature sig = new Signature("(III)[LFoo;");
126 assertThat(sig.getArgumentTypes(), contains(
127 new Type("I"),
128 new Type("I"),
129 new Type("I")
130 ));
131 assertThat(sig.getReturnType(), is(new Type("[LFoo;")));
132 }
133 }
134
135 @Test
136 public void replaceClasses() {
137 {
138 final Signature oldSig = new Signature("()V");
139 final Signature sig = new Signature(oldSig, new ClassNameReplacer() {
140 @Override
141 public String replace(String val) {
142 return null;
143 }
144 });
145 assertThat(sig.getArgumentTypes(), is(empty()));
146 assertThat(sig.getReturnType(), is(new Type("V")));
147 }
148 {
149 final Signature oldSig = new Signature("(IJLFoo;)V");
150 final Signature sig = new Signature(oldSig, new ClassNameReplacer() {
151 @Override
152 public String replace(String val) {
153 return null;
154 }
155 });
156 assertThat(sig.getArgumentTypes(), contains(
157 new Type("I"),
158 new Type("J"),
159 new Type("LFoo;")
160 ));
161 assertThat(sig.getReturnType(), is(new Type("V")));
162 }
163 {
164 final Signature oldSig = new Signature("(LFoo;LBar;)LMoo;");
165 final Signature sig = new Signature(oldSig, new ClassNameReplacer() {
166 @Override
167 public String replace(String val) {
168 if (val.equals("Foo")) {
169 return "Bar";
170 }
171 return null;
172 }
173 });
174 assertThat(sig.getArgumentTypes(), contains(
175 new Type("LBar;"),
176 new Type("LBar;")
177 ));
178 assertThat(sig.getReturnType(), is(new Type("LMoo;")));
179 }
180 {
181 final Signature oldSig = new Signature("(LFoo;LBar;)LMoo;");
182 final Signature sig = new Signature(oldSig, new ClassNameReplacer() {
183 @Override
184 public String replace(String val) {
185 if (val.equals("Moo")) {
186 return "Cow";
187 }
188 return null;
189 }
190 });
191 assertThat(sig.getArgumentTypes(), contains(
192 new Type("LFoo;"),
193 new Type("LBar;")
194 ));
195 assertThat(sig.getReturnType(), is(new Type("LCow;")));
196 }
197 }
198
199 @Test
200 public void replaceArrayClasses() {
201 {
202 final Signature oldSig = new Signature("([LFoo;)[[[LBar;");
203 final Signature sig = new Signature(oldSig, new ClassNameReplacer() {
204 @Override
205 public String replace(String val) {
206 if (val.equals("Foo")) {
207 return "Food";
208 } else if (val.equals("Bar")) {
209 return "Beer";
210 }
211 return null;
212 }
213 });
214 assertThat(sig.getArgumentTypes(), contains(
215 new Type("[LFood;")
216 ));
217 assertThat(sig.getReturnType(), is(new Type("[[[LBeer;")));
218 }
219 }
220
221 @Test
222 public void equals() {
223
224 // base
225 assertThat(new Signature("()V"), is(new Signature("()V")));
226
227 // arguments
228 assertThat(new Signature("(I)V"), is(new Signature("(I)V")));
229 assertThat(new Signature("(ZIZ)V"), is(new Signature("(ZIZ)V")));
230 assertThat(new Signature("(LFoo;)V"), is(new Signature("(LFoo;)V")));
231 assertThat(new Signature("(LFoo;LBar;)V"), is(new Signature("(LFoo;LBar;)V")));
232 assertThat(new Signature("([I)V"), is(new Signature("([I)V")));
233 assertThat(new Signature("([[D[[[J)V"), is(new Signature("([[D[[[J)V")));
234
235 assertThat(new Signature("()V"), is(not(new Signature("(I)V"))));
236 assertThat(new Signature("(I)V"), is(not(new Signature("()V"))));
237 assertThat(new Signature("(IJ)V"), is(not(new Signature("(JI)V"))));
238 assertThat(new Signature("([[Z)V"), is(not(new Signature("([[LFoo;)V"))));
239 assertThat(new Signature("(LFoo;LBar;)V"), is(not(new Signature("(LFoo;LCow;)V"))));
240 assertThat(new Signature("([LFoo;LBar;)V"), is(not(new Signature("(LFoo;LCow;)V"))));
241
242 // return type
243 assertThat(new Signature("()I"), is(new Signature("()I")));
244 assertThat(new Signature("()Z"), is(new Signature("()Z")));
245 assertThat(new Signature("()[D"), is(new Signature("()[D")));
246 assertThat(new Signature("()[[[Z"), is(new Signature("()[[[Z")));
247 assertThat(new Signature("()LFoo;"), is(new Signature("()LFoo;")));
248 assertThat(new Signature("()[LFoo;"), is(new Signature("()[LFoo;")));
249
250 assertThat(new Signature("()I"), is(not(new Signature("()Z"))));
251 assertThat(new Signature("()Z"), is(not(new Signature("()I"))));
252 assertThat(new Signature("()[D"), is(not(new Signature("()[J"))));
253 assertThat(new Signature("()[[[Z"), is(not(new Signature("()[[Z"))));
254 assertThat(new Signature("()LFoo;"), is(not(new Signature("()LBar;"))));
255 assertThat(new Signature("()[LFoo;"), is(not(new Signature("()[LBar;"))));
256 }
257
258 @Test
259 public void testToString() {
260 assertThat(new Signature("()V").toString(), is("()V"));
261 assertThat(new Signature("(I)V").toString(), is("(I)V"));
262 assertThat(new Signature("(ZIZ)V").toString(), is("(ZIZ)V"));
263 assertThat(new Signature("(LFoo;)V").toString(), is("(LFoo;)V"));
264 assertThat(new Signature("(LFoo;LBar;)V").toString(), is("(LFoo;LBar;)V"));
265 assertThat(new Signature("([I)V").toString(), is("([I)V"));
266 assertThat(new Signature("([[D[[[J)V").toString(), is("([[D[[[J)V"));
267 }
268}
diff --git a/src/test/java/cuchaz/enigma/TestSourceIndex.java b/src/test/java/cuchaz/enigma/TestSourceIndex.java
new file mode 100644
index 00000000..58d9ca91
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/TestSourceIndex.java
@@ -0,0 +1,67 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.util.Set;
15import java.util.jar.JarFile;
16
17import org.junit.Test;
18
19import com.google.common.collect.Sets;
20import com.strobel.decompiler.languages.java.ast.CompilationUnit;
21
22import cuchaz.enigma.mapping.ClassEntry;
23
24public class TestSourceIndex {
25 @Test
26 public void indexEverything()
27 throws Exception {
28 // Figure out where Minecraft is...
29 final String mcDir = System.getProperty("enigma.test.minecraftdir");
30 File mcJar = null;
31 if (mcDir == null) {
32 String osname = System.getProperty("os.name").toLowerCase();
33 if (osname.contains("nix") || osname.contains("nux") || osname.contains("solaris")) {
34 mcJar = new File(System.getProperty("user.home"), ".minecraft/versions/1.8.3/1.8.3.jar");
35 }
36 else if (osname.contains("mac") || osname.contains("darwin")) {
37 mcJar = new File(System.getProperty("user.home"), "Library/Application Support/minecraft/versions/1.8.3/1.8.3.jar");
38 }
39 else if (osname.contains("win")) {
40 mcJar = new File(System.getenv("AppData"), ".minecraft/versions/1.8.3/1.8.3.jar");
41 }
42 }
43 else {
44 mcJar = new File(mcDir, "versions/1.8.3/1.8.3.jar");
45 }
46
47 Deobfuscator deobfuscator = new Deobfuscator(new JarFile(mcJar));
48
49 // get all classes that aren't inner classes
50 Set<ClassEntry> classEntries = Sets.newHashSet();
51 for (ClassEntry obfClassEntry : deobfuscator.getJarIndex().getObfClassEntries()) {
52 if (!obfClassEntry.isInnerClass()) {
53 classEntries.add(obfClassEntry);
54 }
55 }
56
57 for (ClassEntry obfClassEntry : classEntries) {
58 try {
59 CompilationUnit tree = deobfuscator.getSourceTree(obfClassEntry.getName());
60 String source = deobfuscator.getSource(tree);
61 deobfuscator.getSourceIndex(tree, source);
62 } catch (Throwable t) {
63 throw new Error("Unable to index " + obfClassEntry, t);
64 }
65 }
66 }
67}
diff --git a/src/test/java/cuchaz/enigma/TestTokensConstructors.java b/src/test/java/cuchaz/enigma/TestTokensConstructors.java
new file mode 100644
index 00000000..66c6fd1b
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/TestTokensConstructors.java
@@ -0,0 +1,136 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static cuchaz.enigma.TestEntryFactory.*;
14import static org.hamcrest.MatcherAssert.*;
15import static org.hamcrest.Matchers.*;
16
17import java.util.jar.JarFile;
18
19import org.junit.Test;
20
21import cuchaz.enigma.mapping.BehaviorEntry;
22
23public class TestTokensConstructors extends TokenChecker {
24
25 public TestTokensConstructors()
26 throws Exception {
27 super(new JarFile("build/test-obf/constructors.jar"));
28 }
29
30 @Test
31 public void baseDeclarations() {
32 assertThat(getDeclarationToken(newConstructor("none/a", "()V")), is("a"));
33 assertThat(getDeclarationToken(newConstructor("none/a", "(I)V")), is("a"));
34 }
35
36 @Test
37 public void subDeclarations() {
38 assertThat(getDeclarationToken(newConstructor("none/d", "()V")), is("d"));
39 assertThat(getDeclarationToken(newConstructor("none/d", "(I)V")), is("d"));
40 assertThat(getDeclarationToken(newConstructor("none/d", "(II)V")), is("d"));
41 assertThat(getDeclarationToken(newConstructor("none/d", "(III)V")), is("d"));
42 }
43
44 @Test
45 public void subsubDeclarations() {
46 assertThat(getDeclarationToken(newConstructor("none/e", "(I)V")), is("e"));
47 }
48
49 @Test
50 public void defaultDeclarations() {
51 assertThat(getDeclarationToken(newConstructor("none/c", "()V")), nullValue());
52 }
53
54 @Test
55 public void baseDefaultReferences() {
56 BehaviorEntry source = newConstructor("none/a", "()V");
57 assertThat(
58 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "a", "()V")),
59 containsInAnyOrder("a")
60 );
61 assertThat(
62 getReferenceTokens(newBehaviorReferenceByConstructor(source, "none/d", "()V")),
63 is(empty()) // implicit call, not decompiled to token
64 );
65 assertThat(
66 getReferenceTokens(newBehaviorReferenceByConstructor(source, "none/d", "(III)V")),
67 is(empty()) // implicit call, not decompiled to token
68 );
69 }
70
71 @Test
72 public void baseIntReferences() {
73 BehaviorEntry source = newConstructor("none/a", "(I)V");
74 assertThat(
75 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "b", "()V")),
76 containsInAnyOrder("a")
77 );
78 }
79
80 @Test
81 public void subDefaultReferences() {
82 BehaviorEntry source = newConstructor("none/d", "()V");
83 assertThat(
84 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "c", "()V")),
85 containsInAnyOrder("d")
86 );
87 assertThat(
88 getReferenceTokens(newBehaviorReferenceByConstructor(source, "none/d", "(I)V")),
89 containsInAnyOrder("this")
90 );
91 }
92
93 @Test
94 public void subIntReferences() {
95 BehaviorEntry source = newConstructor("none/d", "(I)V");
96 assertThat(getReferenceTokens(
97 newBehaviorReferenceByMethod(source, "none/b", "d", "()V")),
98 containsInAnyOrder("d")
99 );
100 assertThat(getReferenceTokens(
101 newBehaviorReferenceByConstructor(source, "none/d", "(II)V")),
102 containsInAnyOrder("this")
103 );
104 assertThat(getReferenceTokens(
105 newBehaviorReferenceByConstructor(source, "none/e", "(I)V")),
106 containsInAnyOrder("super")
107 );
108 }
109
110 @Test
111 public void subIntIntReferences() {
112 BehaviorEntry source = newConstructor("none/d", "(II)V");
113 assertThat(
114 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "e", "()V")),
115 containsInAnyOrder("d")
116 );
117 }
118
119 @Test
120 public void subsubIntReferences() {
121 BehaviorEntry source = newConstructor("none/e", "(I)V");
122 assertThat(
123 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "f", "()V")),
124 containsInAnyOrder("e")
125 );
126 }
127
128 @Test
129 public void defaultConstructableReferences() {
130 BehaviorEntry source = newConstructor("none/c", "()V");
131 assertThat(
132 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "g", "()V")),
133 containsInAnyOrder("c")
134 );
135 }
136}
diff --git a/src/test/java/cuchaz/enigma/TestTranslator.java b/src/test/java/cuchaz/enigma/TestTranslator.java
new file mode 100644
index 00000000..7a430d38
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/TestTranslator.java
@@ -0,0 +1,171 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static cuchaz.enigma.TestEntryFactory.*;
14import static org.hamcrest.MatcherAssert.*;
15import static org.hamcrest.Matchers.*;
16
17import java.io.InputStream;
18import java.io.InputStreamReader;
19import java.util.jar.JarFile;
20
21import org.junit.BeforeClass;
22import org.junit.Test;
23
24import cuchaz.enigma.mapping.Entry;
25import cuchaz.enigma.mapping.Mappings;
26import cuchaz.enigma.mapping.MappingsReader;
27import cuchaz.enigma.mapping.TranslationDirection;
28import cuchaz.enigma.mapping.Translator;
29
30
31public class TestTranslator {
32
33 private static Deobfuscator m_deobfuscator;
34 private static Mappings m_mappings;
35 private static Translator m_deobfTranslator;
36 private static Translator m_obfTranslator;
37
38 @BeforeClass
39 public static void beforeClass()
40 throws Exception {
41 //TODO FIx
42// m_deobfuscator = new Deobfuscator(new JarFile("build/test-obf/translation.jar"));
43// try (InputStream in = TestTranslator.class.getResourceAsStream("/cuchaz/enigma/resources/translation.mappings")) {
44// m_mappings = new MappingsReader().read(new InputStreamReader(in));
45// m_deobfuscator.setMappings(m_mappings);
46// m_deobfTranslator = m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating);
47// m_obfTranslator = m_deobfuscator.getTranslator(TranslationDirection.Obfuscating);
48// }
49 }
50
51 @Test
52 public void basicClasses() {
53 assertMapping(newClass("none/a"), newClass("deobf/A_Basic"));
54 assertMapping(newClass("none/b"), newClass("deobf/B_BaseClass"));
55 assertMapping(newClass("none/c"), newClass("deobf/C_SubClass"));
56 }
57
58 @Test
59 public void basicFields() {
60 assertMapping(newField("none/a", "a", "I"), newField("deobf/A_Basic", "f1", "I"));
61 assertMapping(newField("none/a", "a", "F"), newField("deobf/A_Basic", "f2", "F"));
62 assertMapping(newField("none/a", "a", "Ljava/lang/String;"), newField("deobf/A_Basic", "f3", "Ljava/lang/String;"));
63 }
64
65 @Test
66 public void basicMethods() {
67 assertMapping(newMethod("none/a", "a", "()V"), newMethod("deobf/A_Basic", "m1", "()V"));
68 assertMapping(newMethod("none/a", "a", "()I"), newMethod("deobf/A_Basic", "m2", "()I"));
69 assertMapping(newMethod("none/a", "a", "(I)V"), newMethod("deobf/A_Basic", "m3", "(I)V"));
70 assertMapping(newMethod("none/a", "a", "(I)I"), newMethod("deobf/A_Basic", "m4", "(I)I"));
71 }
72
73 // TODO: basic constructors
74
75 @Test
76 public void inheritanceFields() {
77 assertMapping(newField("none/b", "a", "I"), newField("deobf/B_BaseClass", "f1", "I"));
78 assertMapping(newField("none/b", "a", "C"), newField("deobf/B_BaseClass", "f2", "C"));
79 assertMapping(newField("none/c", "b", "I"), newField("deobf/C_SubClass", "f3", "I"));
80 assertMapping(newField("none/c", "c", "I"), newField("deobf/C_SubClass", "f4", "I"));
81 }
82
83 @Test
84 public void inheritanceFieldsShadowing() {
85 assertMapping(newField("none/c", "b", "C"), newField("deobf/C_SubClass", "f2", "C"));
86 }
87
88 @Test
89 public void inheritanceFieldsBySubClass() {
90 assertMapping(newField("none/c", "a", "I"), newField("deobf/C_SubClass", "f1", "I"));
91 // NOTE: can't reference b.C by subclass since it's shadowed
92 }
93
94 @Test
95 public void inheritanceMethods() {
96 assertMapping(newMethod("none/b", "a", "()I"), newMethod("deobf/B_BaseClass", "m1", "()I"));
97 assertMapping(newMethod("none/b", "b", "()I"), newMethod("deobf/B_BaseClass", "m2", "()I"));
98 assertMapping(newMethod("none/c", "c", "()I"), newMethod("deobf/C_SubClass", "m3", "()I"));
99 }
100
101 @Test
102 public void inheritanceMethodsOverrides() {
103 assertMapping(newMethod("none/c", "a", "()I"), newMethod("deobf/C_SubClass", "m1", "()I"));
104 }
105
106 @Test
107 public void inheritanceMethodsBySubClass() {
108 assertMapping(newMethod("none/c", "b", "()I"), newMethod("deobf/C_SubClass", "m2", "()I"));
109 }
110
111 @Test
112 public void innerClasses() {
113
114 // classes
115 assertMapping(newClass("none/g"), newClass("deobf/G_OuterClass"));
116 assertMapping(newClass("none/g$a"), newClass("deobf/G_OuterClass$A_InnerClass"));
117 assertMapping(newClass("none/g$a$a"), newClass("deobf/G_OuterClass$A_InnerClass$A_InnerInnerClass"));
118 assertMapping(newClass("none/g$b"), newClass("deobf/G_OuterClass$b"));
119 assertMapping(newClass("none/g$b$a"), newClass("deobf/G_OuterClass$b$A_NamedInnerClass"));
120
121 // fields
122 assertMapping(newField("none/g$a", "a", "I"), newField("deobf/G_OuterClass$A_InnerClass", "f1", "I"));
123 assertMapping(newField("none/g$a", "a", "Ljava/lang/String;"), newField("deobf/G_OuterClass$A_InnerClass", "f2", "Ljava/lang/String;"));
124 assertMapping(newField("none/g$a$a", "a", "I"), newField("deobf/G_OuterClass$A_InnerClass$A_InnerInnerClass", "f3", "I"));
125 assertMapping(newField("none/g$b$a", "a", "I"), newField("deobf/G_OuterClass$b$A_NamedInnerClass", "f4", "I"));
126
127 // methods
128 assertMapping(newMethod("none/g$a", "a", "()V"), newMethod("deobf/G_OuterClass$A_InnerClass", "m1", "()V"));
129 assertMapping(newMethod("none/g$a$a", "a", "()V"), newMethod("deobf/G_OuterClass$A_InnerClass$A_InnerInnerClass", "m2", "()V"));
130 }
131
132 @Test
133 public void namelessClass() {
134 assertMapping(newClass("none/h"), newClass("none/h"));
135 }
136
137 @Test
138 public void testGenerics() {
139
140 // classes
141 assertMapping(newClass("none/i"), newClass("deobf/I_Generics"));
142 assertMapping(newClass("none/i$a"), newClass("deobf/I_Generics$A_Type"));
143 assertMapping(newClass("none/i$b"), newClass("deobf/I_Generics$B_Generic"));
144
145 // fields
146 assertMapping(newField("none/i", "a", "Ljava/util/List;"), newField("deobf/I_Generics", "f1", "Ljava/util/List;"));
147 assertMapping(newField("none/i", "b", "Ljava/util/List;"), newField("deobf/I_Generics", "f2", "Ljava/util/List;"));
148 assertMapping(newField("none/i", "a", "Ljava/util/Map;"), newField("deobf/I_Generics", "f3", "Ljava/util/Map;"));
149 assertMapping(newField("none/i$b", "a", "Ljava/lang/Object;"), newField("deobf/I_Generics$B_Generic", "f4", "Ljava/lang/Object;"));
150 assertMapping(newField("none/i", "a", "Lnone/i$b;"), newField("deobf/I_Generics", "f5", "Ldeobf/I_Generics$B_Generic;"));
151 assertMapping(newField("none/i", "b", "Lnone/i$b;"), newField("deobf/I_Generics", "f6", "Ldeobf/I_Generics$B_Generic;"));
152
153 // methods
154 assertMapping(newMethod("none/i$b", "a", "()Ljava/lang/Object;"), newMethod("deobf/I_Generics$B_Generic", "m1", "()Ljava/lang/Object;"));
155 }
156
157 private void assertMapping(Entry obf, Entry deobf) {
158 assertThat(m_deobfTranslator.translateEntry(obf), is(deobf));
159 assertThat(m_obfTranslator.translateEntry(deobf), is(obf));
160
161 String deobfName = m_deobfTranslator.translate(obf);
162 if (deobfName != null) {
163 assertThat(deobfName, is(deobf.getName()));
164 }
165
166 String obfName = m_obfTranslator.translate(deobf);
167 if (obfName != null) {
168 assertThat(obfName, is(obf.getName()));
169 }
170 }
171}
diff --git a/src/test/java/cuchaz/enigma/TestType.java b/src/test/java/cuchaz/enigma/TestType.java
new file mode 100644
index 00000000..01c235b8
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/TestType.java
@@ -0,0 +1,243 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static cuchaz.enigma.TestEntryFactory.*;
14import static org.hamcrest.MatcherAssert.*;
15import static org.hamcrest.Matchers.*;
16
17import org.junit.Test;
18
19import cuchaz.enigma.mapping.Type;
20
21
22public class TestType {
23
24 @Test
25 public void isVoid() {
26 assertThat(new Type("V").isVoid(), is(true));
27 assertThat(new Type("Z").isVoid(), is(false));
28 assertThat(new Type("B").isVoid(), is(false));
29 assertThat(new Type("C").isVoid(), is(false));
30 assertThat(new Type("I").isVoid(), is(false));
31 assertThat(new Type("J").isVoid(), is(false));
32 assertThat(new Type("F").isVoid(), is(false));
33 assertThat(new Type("D").isVoid(), is(false));
34 assertThat(new Type("LFoo;").isVoid(), is(false));
35 assertThat(new Type("[I").isVoid(), is(false));
36 }
37
38 @Test
39 public void isPrimitive() {
40 assertThat(new Type("V").isPrimitive(), is(false));
41 assertThat(new Type("Z").isPrimitive(), is(true));
42 assertThat(new Type("B").isPrimitive(), is(true));
43 assertThat(new Type("C").isPrimitive(), is(true));
44 assertThat(new Type("I").isPrimitive(), is(true));
45 assertThat(new Type("J").isPrimitive(), is(true));
46 assertThat(new Type("F").isPrimitive(), is(true));
47 assertThat(new Type("D").isPrimitive(), is(true));
48 assertThat(new Type("LFoo;").isPrimitive(), is(false));
49 assertThat(new Type("[I").isPrimitive(), is(false));
50 }
51
52 @Test
53 public void getPrimitive() {
54 assertThat(new Type("Z").getPrimitive(), is(Type.Primitive.Boolean));
55 assertThat(new Type("B").getPrimitive(), is(Type.Primitive.Byte));
56 assertThat(new Type("C").getPrimitive(), is(Type.Primitive.Character));
57 assertThat(new Type("I").getPrimitive(), is(Type.Primitive.Integer));
58 assertThat(new Type("J").getPrimitive(), is(Type.Primitive.Long));
59 assertThat(new Type("F").getPrimitive(), is(Type.Primitive.Float));
60 assertThat(new Type("D").getPrimitive(), is(Type.Primitive.Double));
61 }
62
63 @Test
64 public void isClass() {
65 assertThat(new Type("V").isClass(), is(false));
66 assertThat(new Type("Z").isClass(), is(false));
67 assertThat(new Type("B").isClass(), is(false));
68 assertThat(new Type("C").isClass(), is(false));
69 assertThat(new Type("I").isClass(), is(false));
70 assertThat(new Type("J").isClass(), is(false));
71 assertThat(new Type("F").isClass(), is(false));
72 assertThat(new Type("D").isClass(), is(false));
73 assertThat(new Type("LFoo;").isClass(), is(true));
74 assertThat(new Type("[I").isClass(), is(false));
75 }
76
77 @Test
78 public void getClassEntry() {
79 assertThat(new Type("LFoo;").getClassEntry(), is(newClass("Foo")));
80 assertThat(new Type("Ljava/lang/String;").getClassEntry(), is(newClass("java/lang/String")));
81 }
82
83 @Test
84 public void getArrayClassEntry() {
85 assertThat(new Type("[LFoo;").getClassEntry(), is(newClass("Foo")));
86 assertThat(new Type("[[[Ljava/lang/String;").getClassEntry(), is(newClass("java/lang/String")));
87 }
88
89 @Test
90 public void isArray() {
91 assertThat(new Type("V").isArray(), is(false));
92 assertThat(new Type("Z").isArray(), is(false));
93 assertThat(new Type("B").isArray(), is(false));
94 assertThat(new Type("C").isArray(), is(false));
95 assertThat(new Type("I").isArray(), is(false));
96 assertThat(new Type("J").isArray(), is(false));
97 assertThat(new Type("F").isArray(), is(false));
98 assertThat(new Type("D").isArray(), is(false));
99 assertThat(new Type("LFoo;").isArray(), is(false));
100 assertThat(new Type("[I").isArray(), is(true));
101 }
102
103 @Test
104 public void getArrayDimension() {
105 assertThat(new Type("[I").getArrayDimension(), is(1));
106 assertThat(new Type("[[I").getArrayDimension(), is(2));
107 assertThat(new Type("[[[I").getArrayDimension(), is(3));
108 }
109
110 @Test
111 public void getArrayType() {
112 assertThat(new Type("[I").getArrayType(), is(new Type("I")));
113 assertThat(new Type("[[I").getArrayType(), is(new Type("I")));
114 assertThat(new Type("[[[I").getArrayType(), is(new Type("I")));
115 assertThat(new Type("[Ljava/lang/String;").getArrayType(), is(new Type("Ljava/lang/String;")));
116 }
117
118 @Test
119 public void hasClass() {
120 assertThat(new Type("LFoo;").hasClass(), is(true));
121 assertThat(new Type("Ljava/lang/String;").hasClass(), is(true));
122 assertThat(new Type("[LBar;").hasClass(), is(true));
123 assertThat(new Type("[[[LCat;").hasClass(), is(true));
124
125 assertThat(new Type("V").hasClass(), is(false));
126 assertThat(new Type("[I").hasClass(), is(false));
127 assertThat(new Type("[[[I").hasClass(), is(false));
128 assertThat(new Type("Z").hasClass(), is(false));
129 }
130
131 @Test
132 public void parseVoid() {
133 final String answer = "V";
134 assertThat(Type.parseFirst("V"), is(answer));
135 assertThat(Type.parseFirst("VVV"), is(answer));
136 assertThat(Type.parseFirst("VIJ"), is(answer));
137 assertThat(Type.parseFirst("V[I"), is(answer));
138 assertThat(Type.parseFirst("VLFoo;"), is(answer));
139 assertThat(Type.parseFirst("V[LFoo;"), is(answer));
140 }
141
142 @Test
143 public void parsePrimitive() {
144 final String answer = "I";
145 assertThat(Type.parseFirst("I"), is(answer));
146 assertThat(Type.parseFirst("III"), is(answer));
147 assertThat(Type.parseFirst("IJZ"), is(answer));
148 assertThat(Type.parseFirst("I[I"), is(answer));
149 assertThat(Type.parseFirst("ILFoo;"), is(answer));
150 assertThat(Type.parseFirst("I[LFoo;"), is(answer));
151 }
152
153 @Test
154 public void parseClass() {
155 {
156 final String answer = "LFoo;";
157 assertThat(Type.parseFirst("LFoo;"), is(answer));
158 assertThat(Type.parseFirst("LFoo;I"), is(answer));
159 assertThat(Type.parseFirst("LFoo;JZ"), is(answer));
160 assertThat(Type.parseFirst("LFoo;[I"), is(answer));
161 assertThat(Type.parseFirst("LFoo;LFoo;"), is(answer));
162 assertThat(Type.parseFirst("LFoo;[LFoo;"), is(answer));
163 }
164 {
165 final String answer = "Ljava/lang/String;";
166 assertThat(Type.parseFirst("Ljava/lang/String;"), is(answer));
167 assertThat(Type.parseFirst("Ljava/lang/String;I"), is(answer));
168 assertThat(Type.parseFirst("Ljava/lang/String;JZ"), is(answer));
169 assertThat(Type.parseFirst("Ljava/lang/String;[I"), is(answer));
170 assertThat(Type.parseFirst("Ljava/lang/String;LFoo;"), is(answer));
171 assertThat(Type.parseFirst("Ljava/lang/String;[LFoo;"), is(answer));
172 }
173 }
174
175 @Test
176 public void parseArray() {
177 {
178 final String answer = "[I";
179 assertThat(Type.parseFirst("[I"), is(answer));
180 assertThat(Type.parseFirst("[III"), is(answer));
181 assertThat(Type.parseFirst("[IJZ"), is(answer));
182 assertThat(Type.parseFirst("[I[I"), is(answer));
183 assertThat(Type.parseFirst("[ILFoo;"), is(answer));
184 }
185 {
186 final String answer = "[[I";
187 assertThat(Type.parseFirst("[[I"), is(answer));
188 assertThat(Type.parseFirst("[[III"), is(answer));
189 assertThat(Type.parseFirst("[[IJZ"), is(answer));
190 assertThat(Type.parseFirst("[[I[I"), is(answer));
191 assertThat(Type.parseFirst("[[ILFoo;"), is(answer));
192 }
193 {
194 final String answer = "[LFoo;";
195 assertThat(Type.parseFirst("[LFoo;"), is(answer));
196 assertThat(Type.parseFirst("[LFoo;II"), is(answer));
197 assertThat(Type.parseFirst("[LFoo;JZ"), is(answer));
198 assertThat(Type.parseFirst("[LFoo;[I"), is(answer));
199 assertThat(Type.parseFirst("[LFoo;LFoo;"), is(answer));
200 }
201 }
202
203 @Test
204 public void equals() {
205 assertThat(new Type("V"), is(new Type("V")));
206 assertThat(new Type("Z"), is(new Type("Z")));
207 assertThat(new Type("B"), is(new Type("B")));
208 assertThat(new Type("C"), is(new Type("C")));
209 assertThat(new Type("I"), is(new Type("I")));
210 assertThat(new Type("J"), is(new Type("J")));
211 assertThat(new Type("F"), is(new Type("F")));
212 assertThat(new Type("D"), is(new Type("D")));
213 assertThat(new Type("LFoo;"), is(new Type("LFoo;")));
214 assertThat(new Type("[I"), is(new Type("[I")));
215 assertThat(new Type("[[[I"), is(new Type("[[[I")));
216 assertThat(new Type("[LFoo;"), is(new Type("[LFoo;")));
217
218 assertThat(new Type("V"), is(not(new Type("I"))));
219 assertThat(new Type("I"), is(not(new Type("J"))));
220 assertThat(new Type("I"), is(not(new Type("LBar;"))));
221 assertThat(new Type("I"), is(not(new Type("[I"))));
222 assertThat(new Type("LFoo;"), is(not(new Type("LBar;"))));
223 assertThat(new Type("[I"), is(not(new Type("[Z"))));
224 assertThat(new Type("[[[I"), is(not(new Type("[I"))));
225 assertThat(new Type("[LFoo;"), is(not(new Type("[LBar;"))));
226 }
227
228 @Test
229 public void testToString() {
230 assertThat(new Type("V").toString(), is("V"));
231 assertThat(new Type("Z").toString(), is("Z"));
232 assertThat(new Type("B").toString(), is("B"));
233 assertThat(new Type("C").toString(), is("C"));
234 assertThat(new Type("I").toString(), is("I"));
235 assertThat(new Type("J").toString(), is("J"));
236 assertThat(new Type("F").toString(), is("F"));
237 assertThat(new Type("D").toString(), is("D"));
238 assertThat(new Type("LFoo;").toString(), is("LFoo;"));
239 assertThat(new Type("[I").toString(), is("[I"));
240 assertThat(new Type("[[[I").toString(), is("[[[I"));
241 assertThat(new Type("[LFoo;").toString(), is("[LFoo;"));
242 }
243}
diff --git a/src/test/java/cuchaz/enigma/TokenChecker.java b/src/test/java/cuchaz/enigma/TokenChecker.java
new file mode 100644
index 00000000..7afb4cfe
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/TokenChecker.java
@@ -0,0 +1,65 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.IOException;
14import java.util.Collection;
15import java.util.List;
16import java.util.jar.JarFile;
17
18import com.google.common.collect.Lists;
19import com.strobel.decompiler.languages.java.ast.CompilationUnit;
20
21import cuchaz.enigma.analysis.EntryReference;
22import cuchaz.enigma.analysis.SourceIndex;
23import cuchaz.enigma.analysis.Token;
24import cuchaz.enigma.mapping.Entry;
25
26public class TokenChecker {
27
28 private Deobfuscator m_deobfuscator;
29
30 protected TokenChecker(JarFile jarFile)
31 throws IOException {
32 m_deobfuscator = new Deobfuscator(jarFile);
33 }
34
35 protected String getDeclarationToken(Entry entry) {
36 // decompile the class
37 CompilationUnit tree = m_deobfuscator.getSourceTree(entry.getClassName());
38 // DEBUG
39 // tree.acceptVisitor( new TreeDumpVisitor( new File( "tree." + entry.getClassName().replace( '/', '.' ) + ".txt" ) ), null );
40 String source = m_deobfuscator.getSource(tree);
41 SourceIndex index = m_deobfuscator.getSourceIndex(tree, source);
42
43 // get the token value
44 Token token = index.getDeclarationToken(entry);
45 if (token == null) {
46 return null;
47 }
48 return source.substring(token.start, token.end);
49 }
50
51 @SuppressWarnings("unchecked")
52 protected Collection<String> getReferenceTokens(EntryReference<? extends Entry,? extends Entry> reference) {
53 // decompile the class
54 CompilationUnit tree = m_deobfuscator.getSourceTree(reference.context.getClassName());
55 String source = m_deobfuscator.getSource(tree);
56 SourceIndex index = m_deobfuscator.getSourceIndex(tree, source);
57
58 // get the token values
59 List<String> values = Lists.newArrayList();
60 for (Token token : index.getReferenceTokens((EntryReference<Entry,Entry>)reference)) {
61 values.add(source.substring(token.start, token.end));
62 }
63 return values;
64 }
65}
diff --git a/src/test/java/cuchaz/enigma/inputs/Keep.java b/src/test/java/cuchaz/enigma/inputs/Keep.java
new file mode 100644
index 00000000..f04875f5
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/Keep.java
@@ -0,0 +1,17 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs;
12
13public class Keep {
14 public static void main(String[] args) {
15 System.out.println("Keep me!");
16 }
17}
diff --git a/src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java b/src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java
new file mode 100644
index 00000000..65e782a2
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/constructors/BaseClass.java
@@ -0,0 +1,25 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.constructors;
12
13// none/a
14public class BaseClass {
15
16 // <init>()V
17 public BaseClass() {
18 System.out.println("Default constructor");
19 }
20
21 // <init>(I)V
22 public BaseClass(int i) {
23 System.out.println("Int constructor " + i);
24 }
25}
diff --git a/src/test/java/cuchaz/enigma/inputs/constructors/Caller.java b/src/test/java/cuchaz/enigma/inputs/constructors/Caller.java
new file mode 100644
index 00000000..75096ec1
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/constructors/Caller.java
@@ -0,0 +1,57 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.constructors;
12
13// none/b
14public class Caller {
15
16 // a()V
17 public void callBaseDefault() {
18 // none/a.<init>()V
19 System.out.println(new BaseClass());
20 }
21
22 // b()V
23 public void callBaseInt() {
24 // none/a.<init>(I)V
25 System.out.println(new BaseClass(5));
26 }
27
28 // c()V
29 public void callSubDefault() {
30 // none/d.<init>()V
31 System.out.println(new SubClass());
32 }
33
34 // d()V
35 public void callSubInt() {
36 // none/d.<init>(I)V
37 System.out.println(new SubClass(6));
38 }
39
40 // e()V
41 public void callSubIntInt() {
42 // none/d.<init>(II)V
43 System.out.println(new SubClass(4, 2));
44 }
45
46 // f()V
47 public void callSubSubInt() {
48 // none/e.<init>(I)V
49 System.out.println(new SubSubClass(3));
50 }
51
52 // g()V
53 public void callDefaultConstructable() {
54 // none/c.<init>()V
55 System.out.println(new DefaultConstructable());
56 }
57}
diff --git a/src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java b/src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java
new file mode 100644
index 00000000..655f4da3
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/constructors/DefaultConstructable.java
@@ -0,0 +1,15 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.constructors;
12
13public class DefaultConstructable {
14 // only default constructor
15}
diff --git a/src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java b/src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java
new file mode 100644
index 00000000..b0fb3e9b
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/constructors/SubClass.java
@@ -0,0 +1,38 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.constructors;
12
13// none/d extends none/a
14public class SubClass extends BaseClass {
15
16 // <init>()V
17 public SubClass() {
18 // none/a.<init>()V
19 }
20
21 // <init>(I)V
22 public SubClass(int num) {
23 // <init>()V
24 this();
25 System.out.println("SubClass " + num);
26 }
27
28 // <init>(II)V
29 public SubClass(int a, int b) {
30 // <init>(I)V
31 this(a + b);
32 }
33
34 // <init>(III)V
35 public SubClass(int a, int b, int c) {
36 // none/a.<init>()V
37 }
38}
diff --git a/src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java b/src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java
new file mode 100644
index 00000000..50314050
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/constructors/SubSubClass.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.constructors;
12
13// none/e extends none/d
14public class SubSubClass extends SubClass {
15
16 // <init>(I)V
17 public SubSubClass(int i) {
18 // none/c.<init>(I)V
19 super(i);
20 }
21}
diff --git a/src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java
new file mode 100644
index 00000000..4f9c5b0e
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java
@@ -0,0 +1,31 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.inheritanceTree;
12
13// none/a
14public abstract class BaseClass {
15
16 // a
17 private String m_name;
18
19 // <init>(Ljava/lang/String;)V
20 protected BaseClass(String name) {
21 m_name = name;
22 }
23
24 // a()Ljava/lang/String;
25 public String getName() {
26 return m_name;
27 }
28
29 // a()V
30 public abstract void doBaseThings();
31}
diff --git a/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java
new file mode 100644
index 00000000..140d2a81
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.inheritanceTree;
12
13// none/b extends none/a
14public abstract class SubclassA extends BaseClass {
15
16 // <init>(Ljava/lang/String;)V
17 protected SubclassA(String name) {
18 // call to none/a.<init>(Ljava/lang/String)V
19 super(name);
20 }
21}
diff --git a/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java
new file mode 100644
index 00000000..99d149bb
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java
@@ -0,0 +1,40 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.inheritanceTree;
12
13// none/c extends none/a
14public class SubclassB extends BaseClass {
15
16 // a
17 private int m_numThings;
18
19 // <init>()V
20 protected SubclassB() {
21 // none/a.<init>(Ljava/lang/String;)V
22 super("B");
23
24 // access to a
25 m_numThings = 4;
26 }
27
28 @Override
29 // a()V
30 public void doBaseThings() {
31 // call to none/a.a()Ljava/lang/String;
32 System.out.println("Base things by B! " + getName());
33 }
34
35 // b()V
36 public void doBThings() {
37 // access to a
38 System.out.println("" + m_numThings + " B things!");
39 }
40}
diff --git a/src/cuchaz/enigma/gui/ReadableToken.java b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java
index 0741af39..2e414b75 100644
--- a/src/cuchaz/enigma/gui/ReadableToken.java
+++ b/src/test/java/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java
@@ -8,29 +8,27 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.gui; 11package cuchaz.enigma.inputs.inheritanceTree;
12 12
13public class ReadableToken { 13// none/d extends none/b
14public class SubsubclassAA extends SubclassA {
14 15
15 public int line; 16 protected SubsubclassAA() {
16 public int startColumn; 17 // call to none/b.<init>(Ljava/lang/String;)V
17 public int endColumn; 18 super("AA");
19 }
18 20
19 public ReadableToken(int line, int startColumn, int endColumn) { 21 @Override
20 this.line = line; 22 // a()Ljava/lang/String;
21 this.startColumn = startColumn; 23 public String getName() {
22 this.endColumn = endColumn; 24 // call to none/b.a()Ljava/lang/String;
25 return "subsub" + super.getName();
23 } 26 }
24 27
25 @Override 28 @Override
26 public String toString() { 29 // a()V
27 StringBuilder buf = new StringBuilder(); 30 public void doBaseThings() {
28 buf.append("line "); 31 // call to none/d.a()Ljava/lang/String;
29 buf.append(line); 32 System.out.println("Base things by " + getName());
30 buf.append(" columns ");
31 buf.append(startColumn);
32 buf.append("-");
33 buf.append(endColumn);
34 return buf.toString();
35 } 33 }
36} 34}
diff --git a/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java b/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java
new file mode 100644
index 00000000..f6444396
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java
@@ -0,0 +1,24 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.innerClasses;
12
13public class A_Anonymous {
14
15 public void foo() {
16 Runnable runnable = new Runnable() {
17 @Override
18 public void run() {
19 // don't care
20 }
21 };
22 runnable.run();
23 }
24}
diff --git a/src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java b/src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java
new file mode 100644
index 00000000..d78be847
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java
@@ -0,0 +1,23 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.innerClasses;
12
13public class B_AnonymousWithScopeArgs {
14
15 public static void foo(final D_Simple arg) {
16 System.out.println(new Object() {
17 @Override
18 public String toString() {
19 return arg.toString();
20 }
21 });
22 }
23}
diff --git a/src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java b/src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java
new file mode 100644
index 00000000..eb03489d
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java
@@ -0,0 +1,30 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.innerClasses;
12
13@SuppressWarnings("unused")
14public class C_ConstructorArgs {
15
16 class Inner {
17
18 private int a;
19
20 public Inner(int a) {
21 this.a = a;
22 }
23 }
24
25 Inner i;
26
27 public void foo() {
28 i = new Inner(5);
29 }
30}
diff --git a/src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java b/src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java
new file mode 100644
index 00000000..0e9bf827
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/innerClasses/D_Simple.java
@@ -0,0 +1,18 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.innerClasses;
12
13public class D_Simple {
14
15 class Inner {
16 // nothing to do
17 }
18}
diff --git a/src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java b/src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java
new file mode 100644
index 00000000..255434d1
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java
@@ -0,0 +1,31 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.innerClasses;
12
13public class E_AnonymousWithOuterAccess {
14
15 // reproduction of error case documented at:
16 // https://bitbucket.org/cuchaz/enigma/issue/61/stackoverflowerror-when-deobfuscating
17
18 public Object makeInner() {
19 outerMethod();
20 return new Object() {
21 @Override
22 public String toString() {
23 return outerMethod();
24 }
25 };
26 }
27
28 private String outerMethod() {
29 return "foo";
30 }
31}
diff --git a/src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java b/src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java
new file mode 100644
index 00000000..7d1dab41
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java
@@ -0,0 +1,30 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.innerClasses;
12
13
14public class F_ClassTree {
15
16 public class Level1 {
17
18 public int f1;
19
20 public class Level2 {
21
22 public int f2;
23
24 public class Level3 {
25
26 public int f3;
27 }
28 }
29 }
30}
diff --git a/src/cuchaz/enigma/mapping/EntryPair.java b/src/test/java/cuchaz/enigma/inputs/loneClass/LoneClass.java
index 82b28cd1..bf264fa5 100644
--- a/src/cuchaz/enigma/mapping/EntryPair.java
+++ b/src/test/java/cuchaz/enigma/inputs/loneClass/LoneClass.java
@@ -8,15 +8,17 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.mapping; 11package cuchaz.enigma.inputs.loneClass;
12 12
13public class EntryPair<T extends Entry> { 13public class LoneClass {
14 14
15 public T obf; 15 private String m_name;
16 public T deobf;
17 16
18 public EntryPair(T obf, T deobf) { 17 public LoneClass(String name) {
19 this.obf = obf; 18 m_name = name;
20 this.deobf = deobf; 19 }
20
21 public String getName() {
22 return m_name;
21 } 23 }
22} 24}
diff --git a/src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java b/src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java
new file mode 100644
index 00000000..26acac8a
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/translation/A_Basic.java
@@ -0,0 +1,32 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.translation;
12
13public class A_Basic {
14
15 public int one;
16 public float two;
17 public String three;
18
19 public void m1() {
20 }
21
22 public int m2() {
23 return 42;
24 }
25
26 public void m3(int a1) {
27 }
28
29 public int m4(int a1) {
30 return 5; // chosen by fair die roll, guaranteed to be random
31 }
32}
diff --git a/src/cuchaz/enigma/mapping/Entry.java b/src/test/java/cuchaz/enigma/inputs/translation/B_BaseClass.java
index 3c94a95a..035e3299 100644
--- a/src/cuchaz/enigma/mapping/Entry.java
+++ b/src/test/java/cuchaz/enigma/inputs/translation/B_BaseClass.java
@@ -8,11 +8,18 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.mapping; 11package cuchaz.enigma.inputs.translation;
12 12
13public interface Entry { 13public class B_BaseClass {
14 String getName(); 14
15 String getClassName(); 15 public int f1;
16 ClassEntry getClassEntry(); 16 public char f2;
17 Entry cloneToNewClass(ClassEntry classEntry); 17
18 public int m1() {
19 return 5;
20 }
21
22 public int m2() {
23 return 42;
24 }
18} 25}
diff --git a/src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java b/src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java
new file mode 100644
index 00000000..6026a8d5
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/translation/C_SubClass.java
@@ -0,0 +1,27 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.translation;
12
13public class C_SubClass extends B_BaseClass {
14
15 public char f2; // shadows B_BaseClass.f2
16 public int f3;
17 public int f4;
18
19 @Override
20 public int m1() {
21 return 32;
22 }
23
24 public int m3() {
25 return 7;
26 }
27}
diff --git a/src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java b/src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java
new file mode 100644
index 00000000..a1827f98
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java
@@ -0,0 +1,28 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.translation;
12
13import java.util.ArrayList;
14import java.util.List;
15
16public class D_AnonymousTesting {
17
18 public List<Object> getObjs() {
19 List<Object> objs = new ArrayList<Object>();
20 objs.add(new Object() {
21 @Override
22 public String toString() {
23 return "Object!";
24 }
25 });
26 return objs;
27 }
28}
diff --git a/src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java b/src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java
new file mode 100644
index 00000000..769eb70e
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/translation/E_Bridges.java
@@ -0,0 +1,32 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.translation;
12
13import java.util.Iterator;
14
15
16public class E_Bridges implements Iterator<Object> {
17
18 @Override
19 public boolean hasNext() {
20 return false;
21 }
22
23 @Override
24 public String next() {
25 // the compiler will generate a bridge for this method
26 return "foo";
27 }
28
29 @Override
30 public void remove() {
31 }
32}
diff --git a/src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java b/src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java
new file mode 100644
index 00000000..32c246cb
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/translation/F_ObjectMethods.java
@@ -0,0 +1,29 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.translation;
12
13public class F_ObjectMethods {
14
15 public void callEmAll()
16 throws Throwable {
17 clone();
18 equals(this);
19 finalize();
20 getClass();
21 hashCode();
22 notify();
23 notifyAll();
24 toString();
25 wait();
26 wait(0);
27 wait(0, 0);
28 }
29}
diff --git a/src/cuchaz/enigma/mapping/TranslationDirection.java b/src/test/java/cuchaz/enigma/inputs/translation/G_OuterClass.java
index bc3aaa13..a2e0dafb 100644
--- a/src/cuchaz/enigma/mapping/TranslationDirection.java
+++ b/src/test/java/cuchaz/enigma/inputs/translation/G_OuterClass.java
@@ -8,22 +8,29 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.mapping; 11package cuchaz.enigma.inputs.translation;
12 12
13public enum TranslationDirection { 13
14public class G_OuterClass {
14 15
15 Deobfuscating { 16 public class A_InnerClass {
16 @Override 17
17 public <T> T choose(T deobfChoice, T obfChoice) { 18 public int f1;
18 return deobfChoice; 19 public String f2;
19 } 20
20 }, 21 public void m1() {}
21 Obfuscating { 22
22 @Override 23 public class A_InnerInnerClass {
23 public <T> T choose(T deobfChoice, T obfChoice) { 24
24 return obfChoice; 25 public int f3;
26
27 public void m2() {}
25 } 28 }
26 }; 29 }
27 30
28 public abstract <T> T choose(T deobfChoice, T obfChoice); 31 public class B_NamelessClass {
32 public class A_NamedInnerClass {
33 public int f4;
34 }
35 }
29} 36}
diff --git a/src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java b/src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java
new file mode 100644
index 00000000..1b718a54
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/inputs/translation/H_NamelessClass.java
@@ -0,0 +1,38 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.translation;
12
13
14public class H_NamelessClass {
15
16 public class A_InnerClass {
17
18 public int f1;
19 public String f2;
20
21 public void m1() {}
22
23 public class A_InnerInnerClass {
24
25 public int f3;
26
27 public void m2() {}
28 }
29 }
30
31 public class B_NamelessClass {
32 public class A_NamedInnerClass {
33 public int f4;
34 public class A_AnotherInnerClass {}
35 public class B_YetAnotherInnerClass {}
36 }
37 }
38}
diff --git a/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java b/src/test/java/cuchaz/enigma/inputs/translation/I_Generics.java
index 38e8ff99..3490f9d9 100644
--- a/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java
+++ b/src/test/java/cuchaz/enigma/inputs/translation/I_Generics.java
@@ -8,21 +8,28 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors; 11package cuchaz.enigma.inputs.translation;
12 12
13public class Utf8InfoAccessor { 13import java.util.List;
14import java.util.Map;
15
16
17public class I_Generics {
14 18
15 private static Class<?> m_class; 19 public class A_Type {
20 }
16 21
17 static { 22 public List<Integer> f1;
18 try { 23 public List<A_Type> f2;
19 m_class = Class.forName("javassist.bytecode.Utf8Info"); 24 public Map<A_Type,A_Type> f3;
20 } catch (Exception ex) { 25
21 throw new Error(ex); 26 public class B_Generic<T> {
27 public T f4;
28 public T m1() {
29 return null;
22 } 30 }
23 } 31 }
24 32
25 public static boolean isType(ConstInfoAccessor accessor) { 33 public B_Generic<Integer> f5;
26 return m_class.isAssignableFrom(accessor.getItem().getClass()); 34 public B_Generic<A_Type> f6;
27 }
28} 35}
diff --git a/src/test/java/cuchaz/enigma/resources/translation.mappings b/src/test/java/cuchaz/enigma/resources/translation.mappings
new file mode 100644
index 00000000..db78c19d
--- /dev/null
+++ b/src/test/java/cuchaz/enigma/resources/translation.mappings
@@ -0,0 +1,41 @@
1CLASS none/a deobf/A_Basic
2 FIELD a f1 I
3 FIELD a f2 F
4 FIELD a f3 Ljava/lang/String;
5 METHOD a m1 ()V
6 METHOD a m2 ()I
7 METHOD a m3 (I)V
8 METHOD a m4 (I)I
9CLASS none/b deobf/B_BaseClass
10 FIELD a f1 I
11 FIELD a f2 C
12 METHOD a m1 ()I
13 METHOD b m2 ()I
14CLASS none/c deobf/C_SubClass
15 FIELD b f2 C
16 FIELD b f3 I
17 FIELD c f4 I
18 METHOD a m1 ()I
19 METHOD c m3 ()I
20CLASS none/g deobf/G_OuterClass
21 CLASS none/g$a A_InnerClass
22 CLASS none/g$a$a A_InnerInnerClass
23 FIELD a f3 I
24 METHOD a m2 ()V
25 FIELD a f1 I
26 FIELD a f2 Ljava/lang/String;
27 METHOD a m1 ()V
28 CLASS none/g$b
29 CLASS none/g$b$a A_NamedInnerClass
30 FIELD a f4 I
31CLASS none/h
32CLASS none/i deobf/I_Generics
33 CLASS none/i$a A_Type
34 CLASS none/i$b B_Generic
35 FIELD a f4 Ljava/lang/Object;
36 METHOD a m1 ()Ljava/lang/Object;
37 FIELD a f1 Ljava/util/List;
38 FIELD b f2 Ljava/util/List;
39 FIELD a f3 Ljava/util/Map;
40 FIELD a f5 Lnone/i$b;
41 FIELD b f6 Lnone/i$b;