summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.classpath10
-rw-r--r--.hgignore9
-rw-r--r--.project17
-rw-r--r--.settings/org.eclipse.jdt.core.prefs13
-rw-r--r--build.py119
-rw-r--r--conf/about.html6
-rw-r--r--license.APL2.txt55
-rw-r--r--license.GPL3.txt674
-rw-r--r--proguard.conf7
-rw-r--r--readme.txt28
-rw-r--r--src/cuchaz/enigma/CommandMain.java136
-rw-r--r--src/cuchaz/enigma/Constants.java20
-rw-r--r--src/cuchaz/enigma/Deobfuscator.java539
-rw-r--r--src/cuchaz/enigma/Main.java51
-rw-r--r--src/cuchaz/enigma/TranslatingTypeLoader.java211
-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/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.java171
-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.java828
-rw-r--r--src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java100
-rw-r--r--src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java114
-rw-r--r--src/cuchaz/enigma/analysis/ReferenceTreeNode.java18
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndex.java173
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java163
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java114
-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.java227
-rw-r--r--src/cuchaz/enigma/analysis/TreeDumpVisitor.java512
-rw-r--r--src/cuchaz/enigma/bytecode/CheckCastIterator.java126
-rw-r--r--src/cuchaz/enigma/bytecode/ClassRenamer.java110
-rw-r--r--src/cuchaz/enigma/bytecode/ClassTranslator.java141
-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.java102
-rw-r--r--src/cuchaz/enigma/bytecode/MethodParameterWriter.java66
-rw-r--r--src/cuchaz/enigma/bytecode/MethodParametersAttribute.java85
-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/bytecode/accessors/Utf8InfoAccessor.java28
-rw-r--r--src/cuchaz/enigma/convert/ClassIdentity.java411
-rw-r--r--src/cuchaz/enigma/convert/ClassMatcher.java415
-rw-r--r--src/cuchaz/enigma/convert/ClassMatching.java173
-rw-r--r--src/cuchaz/enigma/convert/ClassNamer.java64
-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/ClassListCellRenderer.java36
-rw-r--r--src/cuchaz/enigma/gui/ClassSelector.java164
-rw-r--r--src/cuchaz/enigma/gui/ClassSelectorClassNode.java35
-rw-r--r--src/cuchaz/enigma/gui/ClassSelectorPackageNode.java33
-rw-r--r--src/cuchaz/enigma/gui/CrashDialog.java101
-rw-r--r--src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java21
-rw-r--r--src/cuchaz/enigma/gui/Gui.java1164
-rw-r--r--src/cuchaz/enigma/gui/GuiController.java355
-rw-r--r--src/cuchaz/enigma/gui/GuiTricks.java36
-rw-r--r--src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java21
-rw-r--r--src/cuchaz/enigma/gui/OtherHighlightPainter.java21
-rw-r--r--src/cuchaz/enigma/gui/ProgressDialog.java105
-rw-r--r--src/cuchaz/enigma/gui/ReadableToken.java36
-rw-r--r--src/cuchaz/enigma/gui/RenameListener.java17
-rw-r--r--src/cuchaz/enigma/gui/SelectionHighlightPainter.java34
-rw-r--r--src/cuchaz/enigma/gui/TokenListCellRenderer.java38
-rw-r--r--src/cuchaz/enigma/mapping/ArgumentEntry.java116
-rw-r--r--src/cuchaz/enigma/mapping/ArgumentMapping.java44
-rw-r--r--src/cuchaz/enigma/mapping/BehaviorEntry.java15
-rw-r--r--src/cuchaz/enigma/mapping/BehaviorEntryFactory.java57
-rw-r--r--src/cuchaz/enigma/mapping/ClassEntry.java123
-rw-r--r--src/cuchaz/enigma/mapping/ClassMapping.java405
-rw-r--r--src/cuchaz/enigma/mapping/ConstructorEntry.java116
-rw-r--r--src/cuchaz/enigma/mapping/Entry.java18
-rw-r--r--src/cuchaz/enigma/mapping/EntryPair.java22
-rw-r--r--src/cuchaz/enigma/mapping/FieldEntry.java88
-rw-r--r--src/cuchaz/enigma/mapping/FieldMapping.java43
-rw-r--r--src/cuchaz/enigma/mapping/IllegalNameException.java44
-rw-r--r--src/cuchaz/enigma/mapping/JavassistUtil.java55
-rw-r--r--src/cuchaz/enigma/mapping/MappingParseException.java29
-rw-r--r--src/cuchaz/enigma/mapping/Mappings.java213
-rw-r--r--src/cuchaz/enigma/mapping/MappingsReader.java176
-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.java162
-rw-r--r--src/cuchaz/enigma/mapping/NameValidator.java80
-rw-r--r--src/cuchaz/enigma/mapping/SignatureUpdater.java94
-rw-r--r--src/cuchaz/enigma/mapping/TranslationDirection.java29
-rw-r--r--src/cuchaz/enigma/mapping/Translator.java235
-rw-r--r--test/cuchaz/enigma/EntryFactory.java54
-rw-r--r--test/cuchaz/enigma/TestDeobfuscator.java54
-rw-r--r--test/cuchaz/enigma/TestInnerClasses.java89
-rw-r--r--test/cuchaz/enigma/TestJarIndexConstructorReferences.java124
-rw-r--r--test/cuchaz/enigma/TestJarIndexInheritanceTree.java228
-rw-r--r--test/cuchaz/enigma/TestJarIndexLoneClass.java169
-rw-r--r--test/cuchaz/enigma/TestSourceIndex.java49
-rw-r--r--test/cuchaz/enigma/TestTokensConstructors.java135
-rw-r--r--test/cuchaz/enigma/TokenChecker.java64
-rw-r--r--test/cuchaz/enigma/inputs/Keep.java7
-rw-r--r--test/cuchaz/enigma/inputs/constructors/BaseClass.java15
-rw-r--r--test/cuchaz/enigma/inputs/constructors/Caller.java47
-rw-r--r--test/cuchaz/enigma/inputs/constructors/DefaultConstructable.java5
-rw-r--r--test/cuchaz/enigma/inputs/constructors/SubClass.java28
-rw-r--r--test/cuchaz/enigma/inputs/constructors/SubSubClass.java11
-rw-r--r--test/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java21
-rw-r--r--test/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java11
-rw-r--r--test/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java30
-rw-r--r--test/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java24
-rw-r--r--test/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java14
-rw-r--r--test/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java13
-rw-r--r--test/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java20
-rw-r--r--test/cuchaz/enigma/inputs/innerClasses/D_Simple.java8
-rw-r--r--test/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java21
-rw-r--r--test/cuchaz/enigma/inputs/loneClass/LoneClass.java14
124 files changed, 14750 insertions, 0 deletions
diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..7a9ca3d
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<classpath>
3 <classpathentry kind="src" path="src"/>
4 <classpathentry kind="src" path="conf"/>
5 <classpathentry kind="src" path="test"/>
6 <classpathentry exported="true" kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
7 <classpathentry kind="lib" path="lib/deps.jar"/>
8 <classpathentry kind="lib" path="lib/test-deps.jar"/>
9 <classpathentry kind="output" path="bin"/>
10</classpath>
diff --git a/.hgignore b/.hgignore
new file mode 100644
index 0000000..659df81
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,9 @@
1
2syntax: glob
3bin
4lib
5build
6data
7input
8ivy
9*.pyc \ No newline at end of file
diff --git a/.project b/.project
new file mode 100644
index 0000000..08dff6c
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<projectDescription>
3 <name>Enigma</name>
4 <comment></comment>
5 <projects>
6 </projects>
7 <buildSpec>
8 <buildCommand>
9 <name>org.eclipse.jdt.core.javabuilder</name>
10 <arguments>
11 </arguments>
12 </buildCommand>
13 </buildSpec>
14 <natures>
15 <nature>org.eclipse.jdt.core.javanature</nature>
16 </natures>
17</projectDescription>
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..b5d234f
--- /dev/null
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,13 @@
1#
2#Mon Sep 22 22:51:23 EDT 2014
3org.eclipse.jdt.core.compiler.debug.localVariable=generate
4org.eclipse.jdt.core.compiler.compliance=1.7
5org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
6org.eclipse.jdt.core.compiler.debug.sourceFile=generate
7org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
8org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
9org.eclipse.jdt.core.compiler.debug.lineNumber=generate
10eclipse.preferences.version=1
11org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
12org.eclipse.jdt.core.compiler.source=1.7
13org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
diff --git a/build.py b/build.py
new file mode 100644
index 0000000..8e37d0f
--- /dev/null
+++ b/build.py
@@ -0,0 +1,119 @@
1
2import os
3import sys
4
5# settings
6PathSsjb = "../ssjb"
7Author = "Cuchaz"
8
9DirBin = "bin"
10DirLib = "lib"
11DirBuild = "build"
12PathLocalMavenRepo = "../maven"
13
14
15# import ssjb
16sys.path.insert(0, PathSsjb)
17import ssjb
18import ssjb.ivy
19
20
21ArtifactStandalone = ssjb.ivy.Dep("cuchaz:enigma:0.6b")
22ArtifactLib = ssjb.ivy.Dep("cuchaz:enigma-lib:0.6b")
23
24# dependencies
25ExtraRepos = [
26 "http://maven.cuchazinteractive.com"
27]
28LibDeps = [
29 ssjb.ivy.Dep("com.google.guava:guava:17.0"),
30 ssjb.ivy.Dep("org.javassist:javassist:3.18.1-GA"),
31 ssjb.ivy.Dep("org.bitbucket.mstrobel:procyon-decompiler:0.5.28-enigma")
32]
33StandaloneDeps = LibDeps + [
34 ssjb.ivy.Dep("de.sciss:jsyntaxpane:1.0.0")
35]
36ProguardDep = ssjb.ivy.Dep("net.sf.proguard:proguard-base:5.1")
37TestDeps = [
38 ssjb.ivy.Dep("junit:junit:4.12"),
39 ssjb.ivy.Dep("org.hamcrest:hamcrest-all:1.3")
40]
41
42# functions
43
44def buildTestJar(name, glob):
45
46 pathJar = os.path.join(DirBuild, "%s.jar" % name)
47 pathObfJar = os.path.join(DirBuild, "%s.obf.jar" % name)
48
49 # build the deobf jar
50 with ssjb.file.TempDir("tmp") as dirTemp:
51 ssjb.file.copyTree(dirTemp, DirBin, ssjb.file.find(DirBin, "cuchaz/enigma/inputs/Keep.class"))
52 ssjb.file.copyTree(dirTemp, DirBin, ssjb.file.find(DirBin, glob))
53 ssjb.jar.makeJar(pathJar, dirTemp)
54
55 # build the obf jar
56 ssjb.callJavaJar(
57 os.path.join(DirLib, "proguard.jar"),
58 ["@proguard.conf", "-injars", pathJar, "-outjars", pathObfJar]
59 )
60
61
62def applyReadme(dirTemp):
63 ssjb.file.copy(dirTemp, "license.APL2.txt")
64 ssjb.file.copy(dirTemp, "license.GPL3.txt")
65 ssjb.file.copy(dirTemp, "readme.txt")
66
67
68def buildStandaloneJar(dirOut):
69 with ssjb.file.TempDir(os.path.join(dirOut, "tmp")) as dirTemp:
70 ssjb.file.copyTree(dirTemp, DirBin, ssjb.file.find(DirBin))
71 for path in ssjb.ivy.getJarPaths(StandaloneDeps, ExtraRepos):
72 ssjb.jar.unpackJar(dirTemp, path)
73 ssjb.file.delete(os.path.join(dirTemp, "LICENSE.txt"))
74 ssjb.file.delete(os.path.join(dirTemp, "META-INF/maven"))
75 applyReadme(dirTemp)
76 manifest = ssjb.jar.buildManifest(
77 ArtifactStandalone.artifactId,
78 ArtifactStandalone.version,
79 Author,
80 "cuchaz.enigma.Main"
81 )
82 pathJar = os.path.join(DirBuild, "%s.jar" % ArtifactStandalone.getName())
83 ssjb.jar.makeJar(pathJar, dirTemp, manifest=manifest)
84 ssjb.ivy.deployJarToLocalMavenRepo(PathLocalMavenRepo, pathJar, ArtifactStandalone)
85
86def buildLibJar(dirOut):
87 with ssjb.file.TempDir(os.path.join(dirOut, "tmp")) as dirTemp:
88 ssjb.file.copyTree(dirTemp, DirBin, ssjb.file.find(DirBin))
89 applyReadme(dirTemp)
90 pathJar = os.path.join(DirBuild, "%s.jar" % ArtifactLib.getName())
91 ssjb.jar.makeJar(pathJar, dirTemp)
92 ssjb.ivy.deployJarToLocalMavenRepo(PathLocalMavenRepo, pathJar, ArtifactLib, deps=LibDeps)
93
94
95# tasks
96
97def taskGetDeps():
98 ssjb.file.mkdir(DirLib)
99 ssjb.ivy.makeLibsJar(os.path.join(DirLib, "deps.jar"), StandaloneDeps, extraRepos=ExtraRepos)
100 ssjb.ivy.makeLibsJar(os.path.join(DirLib, "test-deps.jar"), TestDeps)
101 ssjb.ivy.makeJar(os.path.join(DirLib, "proguard.jar"), ProguardDep)
102
103def taskBuildTestJars():
104 buildTestJar("testLoneClass", "cuchaz/enigma/inputs/loneClass/*.class")
105 buildTestJar("testConstructors", "cuchaz/enigma/inputs/constructors/*.class")
106 buildTestJar("testInheritanceTree", "cuchaz/enigma/inputs/inheritanceTree/*.class")
107 buildTestJar("testInnerClasses", "cuchaz/enigma/inputs/innerClasses/*.class")
108
109def taskBuild():
110 ssjb.file.delete(DirBuild)
111 ssjb.file.mkdir(DirBuild)
112 buildStandaloneJar(DirBuild)
113 buildLibJar(DirBuild)
114
115ssjb.registerTask("getDeps", taskGetDeps)
116ssjb.registerTask("buildTestJars", taskBuildTestJars)
117ssjb.registerTask("build", taskBuild)
118ssjb.run()
119
diff --git a/conf/about.html b/conf/about.html
new file mode 100644
index 0000000..b75c1bf
--- /dev/null
+++ b/conf/about.html
@@ -0,0 +1,6 @@
1<html>
2 <h1>%s</h1>
3 <p>A tool for debofuscation of Java code</p>
4 <p>
5 <p>Version: %s</p>
6</html> \ No newline at end of file
diff --git a/license.APL2.txt b/license.APL2.txt
new file mode 100644
index 0000000..a453e43
--- /dev/null
+++ b/license.APL2.txt
@@ -0,0 +1,55 @@
1Apache License
2Version 2.0, January 2004
3http://www.apache.org/licenses/
4
5TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
71. Definitions.
8
9"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
10
11"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
12
13"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
14
15"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
16
17"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
18
19"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
20
21"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
22
23"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
24
25"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
26
27"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
28
292. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
30
313. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
32
334. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
34
35You must give any other recipients of the Work or Derivative Works a copy of this License; and
36
37
38You must cause any modified files to carry prominent notices stating that You changed the files; and
39
40
41You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
42
43
44If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
45You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
46
475. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
48
496. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
50
517. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
52
538. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
54
559. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. \ No newline at end of file
diff --git a/license.GPL3.txt b/license.GPL3.txt
new file mode 100644
index 0000000..20d40b6
--- /dev/null
+++ b/license.GPL3.txt
@@ -0,0 +1,674 @@
1 GNU GENERAL PUBLIC LICENSE
2 Version 3, 29 June 2007
3
4 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
5 Everyone is permitted to copy and distribute verbatim copies
6 of this license document, but changing it is not allowed.
7
8 Preamble
9
10 The GNU General Public License is a free, copyleft license for
11software and other kinds of works.
12
13 The licenses for most software and other practical works are designed
14to take away your freedom to share and change the works. By contrast,
15the GNU General Public License is intended to guarantee your freedom to
16share and change all versions of a program--to make sure it remains free
17software for all its users. We, the Free Software Foundation, use the
18GNU General Public License for most of our software; it applies also to
19any other work released this way by its authors. You can apply it to
20your programs, too.
21
22 When we speak of free software, we are referring to freedom, not
23price. Our General Public Licenses are designed to make sure that you
24have the freedom to distribute copies of free software (and charge for
25them if you wish), that you receive source code or can get it if you
26want it, that you can change the software or use pieces of it in new
27free programs, and that you know you can do these things.
28
29 To protect your rights, we need to prevent others from denying you
30these rights or asking you to surrender the rights. Therefore, you have
31certain responsibilities if you distribute copies of the software, or if
32you modify it: responsibilities to respect the freedom of others.
33
34 For example, if you distribute copies of such a program, whether
35gratis or for a fee, you must pass on to the recipients the same
36freedoms that you received. You must make sure that they, too, receive
37or can get the source code. And you must show them these terms so they
38know their rights.
39
40 Developers that use the GNU GPL protect your rights with two steps:
41(1) assert copyright on the software, and (2) offer you this License
42giving you legal permission to copy, distribute and/or modify it.
43
44 For the developers' and authors' protection, the GPL clearly explains
45that there is no warranty for this free software. For both users' and
46authors' sake, the GPL requires that modified versions be marked as
47changed, so that their problems will not be attributed erroneously to
48authors of previous versions.
49
50 Some devices are designed to deny users access to install or run
51modified versions of the software inside them, although the manufacturer
52can do so. This is fundamentally incompatible with the aim of
53protecting users' freedom to change the software. The systematic
54pattern of such abuse occurs in the area of products for individuals to
55use, which is precisely where it is most unacceptable. Therefore, we
56have designed this version of the GPL to prohibit the practice for those
57products. If such problems arise substantially in other domains, we
58stand ready to extend this provision to those domains in future versions
59of the GPL, as needed to protect the freedom of users.
60
61 Finally, every program is threatened constantly by software patents.
62States should not allow patents to restrict development and use of
63software on general-purpose computers, but in those that do, we wish to
64avoid the special danger that patents applied to a free program could
65make it effectively proprietary. To prevent this, the GPL assures that
66patents cannot be used to render the program non-free.
67
68 The precise terms and conditions for copying, distribution and
69modification follow.
70
71 TERMS AND CONDITIONS
72
73 0. Definitions.
74
75 "This License" refers to version 3 of the GNU General Public License.
76
77 "Copyright" also means copyright-like laws that apply to other kinds of
78works, such as semiconductor masks.
79
80 "The Program" refers to any copyrightable work licensed under this
81License. Each licensee is addressed as "you". "Licensees" and
82"recipients" may be individuals or organizations.
83
84 To "modify" a work means to copy from or adapt all or part of the work
85in a fashion requiring copyright permission, other than the making of an
86exact copy. The resulting work is called a "modified version" of the
87earlier work or a work "based on" the earlier work.
88
89 A "covered work" means either the unmodified Program or a work based
90on the Program.
91
92 To "propagate" a work means to do anything with it that, without
93permission, would make you directly or secondarily liable for
94infringement under applicable copyright law, except executing it on a
95computer or modifying a private copy. Propagation includes copying,
96distribution (with or without modification), making available to the
97public, and in some countries other activities as well.
98
99 To "convey" a work means any kind of propagation that enables other
100parties to make or receive copies. Mere interaction with a user through
101a computer network, with no transfer of a copy, is not conveying.
102
103 An interactive user interface displays "Appropriate Legal Notices"
104to the extent that it includes a convenient and prominently visible
105feature that (1) displays an appropriate copyright notice, and (2)
106tells the user that there is no warranty for the work (except to the
107extent that warranties are provided), that licensees may convey the
108work under this License, and how to view a copy of this License. If
109the interface presents a list of user commands or options, such as a
110menu, a prominent item in the list meets this criterion.
111
112 1. Source Code.
113
114 The "source code" for a work means the preferred form of the work
115for making modifications to it. "Object code" means any non-source
116form of a work.
117
118 A "Standard Interface" means an interface that either is an official
119standard defined by a recognized standards body, or, in the case of
120interfaces specified for a particular programming language, one that
121is widely used among developers working in that language.
122
123 The "System Libraries" of an executable work include anything, other
124than the work as a whole, that (a) is included in the normal form of
125packaging a Major Component, but which is not part of that Major
126Component, and (b) serves only to enable use of the work with that
127Major Component, or to implement a Standard Interface for which an
128implementation is available to the public in source code form. A
129"Major Component", in this context, means a major essential component
130(kernel, window system, and so on) of the specific operating system
131(if any) on which the executable work runs, or a compiler used to
132produce the work, or an object code interpreter used to run it.
133
134 The "Corresponding Source" for a work in object code form means all
135the source code needed to generate, install, and (for an executable
136work) run the object code and to modify the work, including scripts to
137control those activities. However, it does not include the work's
138System Libraries, or general-purpose tools or generally available free
139programs which are used unmodified in performing those activities but
140which are not part of the work. For example, Corresponding Source
141includes interface definition files associated with source files for
142the work, and the source code for shared libraries and dynamically
143linked subprograms that the work is specifically designed to require,
144such as by intimate data communication or control flow between those
145subprograms and other parts of the work.
146
147 The Corresponding Source need not include anything that users
148can regenerate automatically from other parts of the Corresponding
149Source.
150
151 The Corresponding Source for a work in source code form is that
152same work.
153
154 2. Basic Permissions.
155
156 All rights granted under this License are granted for the term of
157copyright on the Program, and are irrevocable provided the stated
158conditions are met. This License explicitly affirms your unlimited
159permission to run the unmodified Program. The output from running a
160covered work is covered by this License only if the output, given its
161content, constitutes a covered work. This License acknowledges your
162rights of fair use or other equivalent, as provided by copyright law.
163
164 You may make, run and propagate covered works that you do not
165convey, without conditions so long as your license otherwise remains
166in force. You may convey covered works to others for the sole purpose
167of having them make modifications exclusively for you, or provide you
168with facilities for running those works, provided that you comply with
169the terms of this License in conveying all material for which you do
170not control copyright. Those thus making or running the covered works
171for you must do so exclusively on your behalf, under your direction
172and control, on terms that prohibit them from making any copies of
173your copyrighted material outside their relationship with you.
174
175 Conveying under any other circumstances is permitted solely under
176the conditions stated below. Sublicensing is not allowed; section 10
177makes it unnecessary.
178
179 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180
181 No covered work shall be deemed part of an effective technological
182measure under any applicable law fulfilling obligations under article
18311 of the WIPO copyright treaty adopted on 20 December 1996, or
184similar laws prohibiting or restricting circumvention of such
185measures.
186
187 When you convey a covered work, you waive any legal power to forbid
188circumvention of technological measures to the extent such circumvention
189is effected by exercising rights under this License with respect to
190the covered work, and you disclaim any intention to limit operation or
191modification of the work as a means of enforcing, against the work's
192users, your or third parties' legal rights to forbid circumvention of
193technological measures.
194
195 4. Conveying Verbatim Copies.
196
197 You may convey verbatim copies of the Program's source code as you
198receive it, in any medium, provided that you conspicuously and
199appropriately publish on each copy an appropriate copyright notice;
200keep intact all notices stating that this License and any
201non-permissive terms added in accord with section 7 apply to the code;
202keep intact all notices of the absence of any warranty; and give all
203recipients a copy of this License along with the Program.
204
205 You may charge any price or no price for each copy that you convey,
206and you may offer support or warranty protection for a fee.
207
208 5. Conveying Modified Source Versions.
209
210 You may convey a work based on the Program, or the modifications to
211produce it from the Program, in the form of source code under the
212terms of section 4, provided that you also meet all of these conditions:
213
214 a) The work must carry prominent notices stating that you modified
215 it, and giving a relevant date.
216
217 b) The work must carry prominent notices stating that it is
218 released under this License and any conditions added under section
219 7. This requirement modifies the requirement in section 4 to
220 "keep intact all notices".
221
222 c) You must license the entire work, as a whole, under this
223 License to anyone who comes into possession of a copy. This
224 License will therefore apply, along with any applicable section 7
225 additional terms, to the whole of the work, and all its parts,
226 regardless of how they are packaged. This License gives no
227 permission to license the work in any other way, but it does not
228 invalidate such permission if you have separately received it.
229
230 d) If the work has interactive user interfaces, each must display
231 Appropriate Legal Notices; however, if the Program has interactive
232 interfaces that do not display Appropriate Legal Notices, your
233 work need not make them do so.
234
235 A compilation of a covered work with other separate and independent
236works, which are not by their nature extensions of the covered work,
237and which are not combined with it such as to form a larger program,
238in or on a volume of a storage or distribution medium, is called an
239"aggregate" if the compilation and its resulting copyright are not
240used to limit the access or legal rights of the compilation's users
241beyond what the individual works permit. Inclusion of a covered work
242in an aggregate does not cause this License to apply to the other
243parts of the aggregate.
244
245 6. Conveying Non-Source Forms.
246
247 You may convey a covered work in object code form under the terms
248of sections 4 and 5, provided that you also convey the
249machine-readable Corresponding Source under the terms of this License,
250in one of these ways:
251
252 a) Convey the object code in, or embodied in, a physical product
253 (including a physical distribution medium), accompanied by the
254 Corresponding Source fixed on a durable physical medium
255 customarily used for software interchange.
256
257 b) Convey the object code in, or embodied in, a physical product
258 (including a physical distribution medium), accompanied by a
259 written offer, valid for at least three years and valid for as
260 long as you offer spare parts or customer support for that product
261 model, to give anyone who possesses the object code either (1) a
262 copy of the Corresponding Source for all the software in the
263 product that is covered by this License, on a durable physical
264 medium customarily used for software interchange, for a price no
265 more than your reasonable cost of physically performing this
266 conveying of source, or (2) access to copy the
267 Corresponding Source from a network server at no charge.
268
269 c) Convey individual copies of the object code with a copy of the
270 written offer to provide the Corresponding Source. This
271 alternative is allowed only occasionally and noncommercially, and
272 only if you received the object code with such an offer, in accord
273 with subsection 6b.
274
275 d) Convey the object code by offering access from a designated
276 place (gratis or for a charge), and offer equivalent access to the
277 Corresponding Source in the same way through the same place at no
278 further charge. You need not require recipients to copy the
279 Corresponding Source along with the object code. If the place to
280 copy the object code is a network server, the Corresponding Source
281 may be on a different server (operated by you or a third party)
282 that supports equivalent copying facilities, provided you maintain
283 clear directions next to the object code saying where to find the
284 Corresponding Source. Regardless of what server hosts the
285 Corresponding Source, you remain obligated to ensure that it is
286 available for as long as needed to satisfy these requirements.
287
288 e) Convey the object code using peer-to-peer transmission, provided
289 you inform other peers where the object code and Corresponding
290 Source of the work are being offered to the general public at no
291 charge under subsection 6d.
292
293 A separable portion of the object code, whose source code is excluded
294from the Corresponding Source as a System Library, need not be
295included in conveying the object code work.
296
297 A "User Product" is either (1) a "consumer product", which means any
298tangible personal property which is normally used for personal, family,
299or household purposes, or (2) anything designed or sold for incorporation
300into a dwelling. In determining whether a product is a consumer product,
301doubtful cases shall be resolved in favor of coverage. For a particular
302product received by a particular user, "normally used" refers to a
303typical or common use of that class of product, regardless of the status
304of the particular user or of the way in which the particular user
305actually uses, or expects or is expected to use, the product. A product
306is a consumer product regardless of whether the product has substantial
307commercial, industrial or non-consumer uses, unless such uses represent
308the only significant mode of use of the product.
309
310 "Installation Information" for a User Product means any methods,
311procedures, authorization keys, or other information required to install
312and execute modified versions of a covered work in that User Product from
313a modified version of its Corresponding Source. The information must
314suffice to ensure that the continued functioning of the modified object
315code is in no case prevented or interfered with solely because
316modification has been made.
317
318 If you convey an object code work under this section in, or with, or
319specifically for use in, a User Product, and the conveying occurs as
320part of a transaction in which the right of possession and use of the
321User Product is transferred to the recipient in perpetuity or for a
322fixed term (regardless of how the transaction is characterized), the
323Corresponding Source conveyed under this section must be accompanied
324by the Installation Information. But this requirement does not apply
325if neither you nor any third party retains the ability to install
326modified object code on the User Product (for example, the work has
327been installed in ROM).
328
329 The requirement to provide Installation Information does not include a
330requirement to continue to provide support service, warranty, or updates
331for a work that has been modified or installed by the recipient, or for
332the User Product in which it has been modified or installed. Access to a
333network may be denied when the modification itself materially and
334adversely affects the operation of the network or violates the rules and
335protocols for communication across the network.
336
337 Corresponding Source conveyed, and Installation Information provided,
338in accord with this section must be in a format that is publicly
339documented (and with an implementation available to the public in
340source code form), and must require no special password or key for
341unpacking, reading or copying.
342
343 7. Additional Terms.
344
345 "Additional permissions" are terms that supplement the terms of this
346License by making exceptions from one or more of its conditions.
347Additional permissions that are applicable to the entire Program shall
348be treated as though they were included in this License, to the extent
349that they are valid under applicable law. If additional permissions
350apply only to part of the Program, that part may be used separately
351under those permissions, but the entire Program remains governed by
352this License without regard to the additional permissions.
353
354 When you convey a copy of a covered work, you may at your option
355remove any additional permissions from that copy, or from any part of
356it. (Additional permissions may be written to require their own
357removal in certain cases when you modify the work.) You may place
358additional permissions on material, added by you to a covered work,
359for which you have or can give appropriate copyright permission.
360
361 Notwithstanding any other provision of this License, for material you
362add to a covered work, you may (if authorized by the copyright holders of
363that material) supplement the terms of this License with terms:
364
365 a) Disclaiming warranty or limiting liability differently from the
366 terms of sections 15 and 16 of this License; or
367
368 b) Requiring preservation of specified reasonable legal notices or
369 author attributions in that material or in the Appropriate Legal
370 Notices displayed by works containing it; or
371
372 c) Prohibiting misrepresentation of the origin of that material, or
373 requiring that modified versions of such material be marked in
374 reasonable ways as different from the original version; or
375
376 d) Limiting the use for publicity purposes of names of licensors or
377 authors of the material; or
378
379 e) Declining to grant rights under trademark law for use of some
380 trade names, trademarks, or service marks; or
381
382 f) Requiring indemnification of licensors and authors of that
383 material by anyone who conveys the material (or modified versions of
384 it) with contractual assumptions of liability to the recipient, for
385 any liability that these contractual assumptions directly impose on
386 those licensors and authors.
387
388 All other non-permissive additional terms are considered "further
389restrictions" within the meaning of section 10. If the Program as you
390received it, or any part of it, contains a notice stating that it is
391governed by this License along with a term that is a further
392restriction, you may remove that term. If a license document contains
393a further restriction but permits relicensing or conveying under this
394License, you may add to a covered work material governed by the terms
395of that license document, provided that the further restriction does
396not survive such relicensing or conveying.
397
398 If you add terms to a covered work in accord with this section, you
399must place, in the relevant source files, a statement of the
400additional terms that apply to those files, or a notice indicating
401where to find the applicable terms.
402
403 Additional terms, permissive or non-permissive, may be stated in the
404form of a separately written license, or stated as exceptions;
405the above requirements apply either way.
406
407 8. Termination.
408
409 You may not propagate or modify a covered work except as expressly
410provided under this License. Any attempt otherwise to propagate or
411modify it is void, and will automatically terminate your rights under
412this License (including any patent licenses granted under the third
413paragraph of section 11).
414
415 However, if you cease all violation of this License, then your
416license from a particular copyright holder is reinstated (a)
417provisionally, unless and until the copyright holder explicitly and
418finally terminates your license, and (b) permanently, if the copyright
419holder fails to notify you of the violation by some reasonable means
420prior to 60 days after the cessation.
421
422 Moreover, your license from a particular copyright holder is
423reinstated permanently if the copyright holder notifies you of the
424violation by some reasonable means, this is the first time you have
425received notice of violation of this License (for any work) from that
426copyright holder, and you cure the violation prior to 30 days after
427your receipt of the notice.
428
429 Termination of your rights under this section does not terminate the
430licenses of parties who have received copies or rights from you under
431this License. If your rights have been terminated and not permanently
432reinstated, you do not qualify to receive new licenses for the same
433material under section 10.
434
435 9. Acceptance Not Required for Having Copies.
436
437 You are not required to accept this License in order to receive or
438run a copy of the Program. Ancillary propagation of a covered work
439occurring solely as a consequence of using peer-to-peer transmission
440to receive a copy likewise does not require acceptance. However,
441nothing other than this License grants you permission to propagate or
442modify any covered work. These actions infringe copyright if you do
443not accept this License. Therefore, by modifying or propagating a
444covered work, you indicate your acceptance of this License to do so.
445
446 10. Automatic Licensing of Downstream Recipients.
447
448 Each time you convey a covered work, the recipient automatically
449receives a license from the original licensors, to run, modify and
450propagate that work, subject to this License. You are not responsible
451for enforcing compliance by third parties with this License.
452
453 An "entity transaction" is a transaction transferring control of an
454organization, or substantially all assets of one, or subdividing an
455organization, or merging organizations. If propagation of a covered
456work results from an entity transaction, each party to that
457transaction who receives a copy of the work also receives whatever
458licenses to the work the party's predecessor in interest had or could
459give under the previous paragraph, plus a right to possession of the
460Corresponding Source of the work from the predecessor in interest, if
461the predecessor has it or can get it with reasonable efforts.
462
463 You may not impose any further restrictions on the exercise of the
464rights granted or affirmed under this License. For example, you may
465not impose a license fee, royalty, or other charge for exercise of
466rights granted under this License, and you may not initiate litigation
467(including a cross-claim or counterclaim in a lawsuit) alleging that
468any patent claim is infringed by making, using, selling, offering for
469sale, or importing the Program or any portion of it.
470
471 11. Patents.
472
473 A "contributor" is a copyright holder who authorizes use under this
474License of the Program or a work on which the Program is based. The
475work thus licensed is called the contributor's "contributor version".
476
477 A contributor's "essential patent claims" are all patent claims
478owned or controlled by the contributor, whether already acquired or
479hereafter acquired, that would be infringed by some manner, permitted
480by this License, of making, using, or selling its contributor version,
481but do not include claims that would be infringed only as a
482consequence of further modification of the contributor version. For
483purposes of this definition, "control" includes the right to grant
484patent sublicenses in a manner consistent with the requirements of
485this License.
486
487 Each contributor grants you a non-exclusive, worldwide, royalty-free
488patent license under the contributor's essential patent claims, to
489make, use, sell, offer for sale, import and otherwise run, modify and
490propagate the contents of its contributor version.
491
492 In the following three paragraphs, a "patent license" is any express
493agreement or commitment, however denominated, not to enforce a patent
494(such as an express permission to practice a patent or covenant not to
495sue for patent infringement). To "grant" such a patent license to a
496party means to make such an agreement or commitment not to enforce a
497patent against the party.
498
499 If you convey a covered work, knowingly relying on a patent license,
500and the Corresponding Source of the work is not available for anyone
501to copy, free of charge and under the terms of this License, through a
502publicly available network server or other readily accessible means,
503then you must either (1) cause the Corresponding Source to be so
504available, or (2) arrange to deprive yourself of the benefit of the
505patent license for this particular work, or (3) arrange, in a manner
506consistent with the requirements of this License, to extend the patent
507license to downstream recipients. "Knowingly relying" means you have
508actual knowledge that, but for the patent license, your conveying the
509covered work in a country, or your recipient's use of the covered work
510in a country, would infringe one or more identifiable patents in that
511country that you have reason to believe are valid.
512
513 If, pursuant to or in connection with a single transaction or
514arrangement, you convey, or propagate by procuring conveyance of, a
515covered work, and grant a patent license to some of the parties
516receiving the covered work authorizing them to use, propagate, modify
517or convey a specific copy of the covered work, then the patent license
518you grant is automatically extended to all recipients of the covered
519work and works based on it.
520
521 A patent license is "discriminatory" if it does not include within
522the scope of its coverage, prohibits the exercise of, or is
523conditioned on the non-exercise of one or more of the rights that are
524specifically granted under this License. You may not convey a covered
525work if you are a party to an arrangement with a third party that is
526in the business of distributing software, under which you make payment
527to the third party based on the extent of your activity of conveying
528the work, and under which the third party grants, to any of the
529parties who would receive the covered work from you, a discriminatory
530patent license (a) in connection with copies of the covered work
531conveyed by you (or copies made from those copies), or (b) primarily
532for and in connection with specific products or compilations that
533contain the covered work, unless you entered into that arrangement,
534or that patent license was granted, prior to 28 March 2007.
535
536 Nothing in this License shall be construed as excluding or limiting
537any implied license or other defenses to infringement that may
538otherwise be available to you under applicable patent law.
539
540 12. No Surrender of Others' Freedom.
541
542 If conditions are imposed on you (whether by court order, agreement or
543otherwise) that contradict the conditions of this License, they do not
544excuse you from the conditions of this License. If you cannot convey a
545covered work so as to satisfy simultaneously your obligations under this
546License and any other pertinent obligations, then as a consequence you may
547not convey it at all. For example, if you agree to terms that obligate you
548to collect a royalty for further conveying from those to whom you convey
549the Program, the only way you could satisfy both those terms and this
550License would be to refrain entirely from conveying the Program.
551
552 13. Use with the GNU Affero General Public License.
553
554 Notwithstanding any other provision of this License, you have
555permission to link or combine any covered work with a work licensed
556under version 3 of the GNU Affero General Public License into a single
557combined work, and to convey the resulting work. The terms of this
558License will continue to apply to the part which is the covered work,
559but the special requirements of the GNU Affero General Public License,
560section 13, concerning interaction through a network will apply to the
561combination as such.
562
563 14. Revised Versions of this License.
564
565 The Free Software Foundation may publish revised and/or new versions of
566the GNU General Public License from time to time. Such new versions will
567be similar in spirit to the present version, but may differ in detail to
568address new problems or concerns.
569
570 Each version is given a distinguishing version number. If the
571Program specifies that a certain numbered version of the GNU General
572Public License "or any later version" applies to it, you have the
573option of following the terms and conditions either of that numbered
574version or of any later version published by the Free Software
575Foundation. If the Program does not specify a version number of the
576GNU General Public License, you may choose any version ever published
577by the Free Software Foundation.
578
579 If the Program specifies that a proxy can decide which future
580versions of the GNU General Public License can be used, that proxy's
581public statement of acceptance of a version permanently authorizes you
582to choose that version for the Program.
583
584 Later license versions may give you additional or different
585permissions. However, no additional obligations are imposed on any
586author or copyright holder as a result of your choosing to follow a
587later version.
588
589 15. Disclaimer of Warranty.
590
591 THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599
600 16. Limitation of Liability.
601
602 IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610SUCH DAMAGES.
611
612 17. Interpretation of Sections 15 and 16.
613
614 If the disclaimer of warranty and limitation of liability provided
615above cannot be given local legal effect according to their terms,
616reviewing courts shall apply local law that most closely approximates
617an absolute waiver of all civil liability in connection with the
618Program, unless a warranty or assumption of liability accompanies a
619copy of the Program in return for a fee.
620
621 END OF TERMS AND CONDITIONS
622
623 How to Apply These Terms to Your New Programs
624
625 If you develop a new program, and you want it to be of the greatest
626possible use to the public, the best way to achieve this is to make it
627free software which everyone can redistribute and change under these terms.
628
629 To do so, attach the following notices to the program. It is safest
630to attach them to the start of each source file to most effectively
631state the exclusion of warranty; and each file should have at least
632the "copyright" line and a pointer to where the full notice is found.
633
634 <one line to give the program's name and a brief idea of what it does.>
635 Copyright (C) <year> <name of author>
636
637 This program is free software: you can redistribute it and/or modify
638 it under the terms of the GNU General Public License as published by
639 the Free Software Foundation, either version 3 of the License, or
640 (at your option) any later version.
641
642 This program is distributed in the hope that it will be useful,
643 but WITHOUT ANY WARRANTY; without even the implied warranty of
644 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 GNU General Public License for more details.
646
647 You should have received a copy of the GNU General Public License
648 along with this program. If not, see <http://www.gnu.org/licenses/>.
649
650Also add information on how to contact you by electronic and paper mail.
651
652 If the program does terminal interaction, make it output a short
653notice like this when it starts in an interactive mode:
654
655 <program> Copyright (C) <year> <name of author>
656 This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 This is free software, and you are welcome to redistribute it
658 under certain conditions; type `show c' for details.
659
660The hypothetical commands `show w' and `show c' should show the appropriate
661parts of the General Public License. Of course, your program's commands
662might be different; for a GUI interface, you would use an "about box".
663
664 You should also get your employer (if you work as a programmer) or school,
665if any, to sign a "copyright disclaimer" for the program, if necessary.
666For more information on this, and how to apply and follow the GNU GPL, see
667<http://www.gnu.org/licenses/>.
668
669 The GNU General Public License does not permit incorporating your program
670into proprietary programs. If your program is a subroutine library, you
671may consider it more useful to permit linking proprietary applications with
672the library. If this is what you want to do, use the GNU Lesser General
673Public License instead of this License. But first, please read
674<http://www.gnu.org/philosophy/why-not-lgpl.html>. \ No newline at end of file
diff --git a/proguard.conf b/proguard.conf
new file mode 100644
index 0000000..e1f04ae
--- /dev/null
+++ b/proguard.conf
@@ -0,0 +1,7 @@
1-libraryjars <java.home>/lib/rt.jar
2-overloadaggressively
3-repackageclasses
4-allowaccessmodification
5-dontoptimize
6-dontshrink
7-keep class cuchaz.enigma.inputs.Keep \ No newline at end of file
diff --git a/readme.txt b/readme.txt
new file mode 100644
index 0000000..3844f54
--- /dev/null
+++ b/readme.txt
@@ -0,0 +1,28 @@
1
2Enigma v0.6 beta
3A tool for deobfuscation of Java bytecode
4
5Copyright Jeff Martin, 2014
6
7
8LICENSE
9
10Enigma is distributed under the GNU General Public license version 3
11
12Enigma includes a modified version of Procyon which is distributed under the Apache license version 2. Procyon is copyrighted by Mike Strobel, 2013
13
14Enigma includes unmodified versions of the following libraries which are also released under the Apache license version 2.
15 Guava
16 Javassist
17 JSyntaxPane
18
19Copies of the GNU General Public license verion 3 and the Apache license v2 have been included in this distribution.
20
21
22USING ENIGMA
23
24Launch the GUI:
25 java -jar enigma.jar
26
27Use Enigma on the command line:
28 java -cp enigma.jar cuchaz.enigma.CommandMain
diff --git a/src/cuchaz/enigma/CommandMain.java b/src/cuchaz/enigma/CommandMain.java
new file mode 100644
index 0000000..1ec2ad2
--- /dev/null
+++ b/src/cuchaz/enigma/CommandMain.java
@@ -0,0 +1,136 @@
1package cuchaz.enigma;
2
3import java.io.File;
4import java.io.FileReader;
5import java.util.jar.JarFile;
6
7import cuchaz.enigma.Deobfuscator.ProgressListener;
8import cuchaz.enigma.mapping.Mappings;
9import cuchaz.enigma.mapping.MappingsReader;
10
11public class CommandMain {
12
13 public static class ConsoleProgressListener implements ProgressListener {
14
15 private static final int ReportTime = 5000; // 5s
16
17 private int m_totalWork;
18 private long m_startTime;
19 private long m_lastReportTime;
20
21 @Override
22 public void init(int totalWork, String title) {
23 m_totalWork = totalWork;
24 m_startTime = System.currentTimeMillis();
25 m_lastReportTime = m_startTime;
26 System.out.println(title);
27 }
28
29 @Override
30 public void onProgress(int numDone, String message) {
31
32 long now = System.currentTimeMillis();
33 boolean isLastUpdate = numDone == m_totalWork;
34 boolean shouldReport = isLastUpdate || now - m_lastReportTime > ReportTime;
35
36 if (shouldReport) {
37 int percent = numDone*100/m_totalWork;
38 System.out.println(String.format("\tProgress: %3d%%", percent));
39 m_lastReportTime = now;
40 }
41 if (isLastUpdate) {
42 double elapsedSeconds = (now - m_startTime)/1000;
43 System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds));
44 }
45 }
46 }
47
48 public static void main(String[] args)
49 throws Exception {
50
51 try {
52
53 // process the command
54 String command = getArg(args, 0, "command");
55 if (command.equalsIgnoreCase("deobfuscate")) {
56 deobfuscate(args);
57 } else if(command.equalsIgnoreCase("decompile")) {
58 decompile(args);
59 } else {
60 throw new IllegalArgumentException("Command not recognized: " + command);
61 }
62 } catch (IllegalArgumentException ex) {
63 System.out.println(ex.getMessage());
64 printHelp();
65 }
66 }
67
68 private static void printHelp() {
69 System.out.println(String.format("%s - %s", Constants.Name, Constants.Version));
70 System.out.println("Usage:");
71 System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain <command>");
72 System.out.println("\twhere <command> is one of:");
73 System.out.println("\t\tdeobfuscate <mappings file> <in jar> <out jar>");
74 System.out.println("\t\tdecompile <mappings file> <in jar> <out folder>");
75 }
76
77 private static void decompile(String[] args)
78 throws Exception {
79 File fileMappings = getReadableFile(getArg(args, 1, "mappings file"));
80 File fileJarIn = getReadableFile(getArg(args, 2, "in jar"));
81 File fileJarOut = getWritableFolder(getArg(args, 3, "out folder"));
82 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn));
83 deobfuscator.writeSources(fileJarOut, new ConsoleProgressListener());
84 }
85
86 private static void deobfuscate(String[] args)
87 throws Exception {
88 File fileMappings = getReadableFile(getArg(args, 1, "mappings file"));
89 File fileJarIn = getReadableFile(getArg(args, 2, "in jar"));
90 File fileJarOut = getWritableFile(getArg(args, 3, "out jar"));
91 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn));
92 deobfuscator.writeJar(fileJarOut, new ConsoleProgressListener());
93 }
94
95 private static Deobfuscator getDeobfuscator(File fileMappings, JarFile jar)
96 throws Exception {
97 System.out.println("Reading mappings...");
98 Mappings mappings = new MappingsReader().read(new FileReader(fileMappings));
99 System.out.println("Reading jar...");
100 Deobfuscator deobfuscator = new Deobfuscator(jar);
101 deobfuscator.setMappings(mappings);
102 return deobfuscator;
103 }
104
105 private static String getArg(String[] args, int i, String name) {
106 if (i >= args.length) {
107 throw new IllegalArgumentException(name + " is required");
108 }
109 return args[i];
110 }
111
112 private static File getWritableFile(String path) {
113 File file = new File(path).getAbsoluteFile();
114 File dir = file.getParentFile();
115 if (dir == null || !dir.exists()) {
116 throw new IllegalArgumentException("Cannot write to folder: " + file);
117 }
118 return file;
119 }
120
121 private static File getWritableFolder(String path) {
122 File dir = new File(path).getAbsoluteFile();
123 if (!dir.exists()) {
124 throw new IllegalArgumentException("Cannot write to folder: " + dir);
125 }
126 return dir;
127 }
128
129 private static File getReadableFile(String path) {
130 File file = new File(path).getAbsoluteFile();
131 if (!file.exists()) {
132 throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath());
133 }
134 return file;
135 }
136}
diff --git a/src/cuchaz/enigma/Constants.java b/src/cuchaz/enigma/Constants.java
new file mode 100644
index 0000000..a1ba2e9
--- /dev/null
+++ b/src/cuchaz/enigma/Constants.java
@@ -0,0 +1,20 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13public class Constants {
14 public static final String Name = "Enigma";
15 public static final String Version = "0.6 beta";
16 public static final String Url = "http://www.cuchazinteractive.com/enigma";
17 public static final int MiB = 1024 * 1024; // 1 mebibyte
18 public static final int KiB = 1024; // 1 kebibyte
19 public static final String NonePackage = "none";
20}
diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java
new file mode 100644
index 0000000..5f61686
--- /dev/null
+++ b/src/cuchaz/enigma/Deobfuscator.java
@@ -0,0 +1,539 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.\
3 *
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the GNU Public License v3.0
6 * which accompanies this distribution, and is available at
7 * http://www.gnu.org/licenses/gpl.html
8 *
9 * Contributors:
10 * Jeff Martin - initial API and implementation
11 ******************************************************************************/
12package cuchaz.enigma;
13
14import java.io.File;
15import java.io.FileOutputStream;
16import java.io.FileWriter;
17import java.io.IOException;
18import java.io.StringWriter;
19import java.util.List;
20import java.util.Map;
21import java.util.Set;
22import java.util.jar.JarEntry;
23import java.util.jar.JarFile;
24import java.util.jar.JarOutputStream;
25
26import javassist.CtClass;
27import javassist.bytecode.Descriptor;
28
29import com.google.common.collect.Lists;
30import com.google.common.collect.Maps;
31import com.google.common.collect.Sets;
32import com.strobel.assembler.metadata.MetadataSystem;
33import com.strobel.assembler.metadata.TypeDefinition;
34import com.strobel.decompiler.DecompilerContext;
35import com.strobel.decompiler.DecompilerSettings;
36import com.strobel.decompiler.PlainTextOutput;
37import com.strobel.decompiler.languages.java.JavaOutputVisitor;
38import com.strobel.decompiler.languages.java.ast.AstBuilder;
39import com.strobel.decompiler.languages.java.ast.CompilationUnit;
40import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor;
41
42import cuchaz.enigma.analysis.EntryReference;
43import cuchaz.enigma.analysis.JarClassIterator;
44import cuchaz.enigma.analysis.JarIndex;
45import cuchaz.enigma.analysis.SourceIndex;
46import cuchaz.enigma.analysis.SourceIndexVisitor;
47import cuchaz.enigma.analysis.Token;
48import cuchaz.enigma.mapping.ArgumentEntry;
49import cuchaz.enigma.mapping.BehaviorEntry;
50import cuchaz.enigma.mapping.BehaviorEntryFactory;
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.MappingsRenamer;
59import cuchaz.enigma.mapping.MethodEntry;
60import cuchaz.enigma.mapping.MethodMapping;
61import cuchaz.enigma.mapping.TranslationDirection;
62import cuchaz.enigma.mapping.Translator;
63
64public class Deobfuscator {
65
66 public interface ProgressListener {
67 void init(int totalWork, String title);
68 void onProgress(int numDone, String message);
69 }
70
71 private JarFile m_jar;
72 private DecompilerSettings m_settings;
73 private JarIndex m_jarIndex;
74 private Mappings m_mappings;
75 private MappingsRenamer m_renamer;
76 private Map<TranslationDirection,Translator> m_translatorCache;
77
78 public Deobfuscator(JarFile jar) throws IOException {
79 m_jar = jar;
80
81 // build the jar index
82 m_jarIndex = new JarIndex();
83 m_jarIndex.indexJar(m_jar, true);
84
85 // config the decompiler
86 m_settings = DecompilerSettings.javaDefaults();
87 m_settings.setMergeVariables(true);
88 m_settings.setForceExplicitImports(true);
89 m_settings.setForceExplicitTypeArguments(true);
90 // DEBUG
91 //m_settings.setShowSyntheticMembers(true);
92
93 // init defaults
94 m_translatorCache = Maps.newTreeMap();
95
96 // init mappings
97 setMappings(new Mappings());
98 }
99
100 public String getJarName() {
101 return m_jar.getName();
102 }
103
104 public JarIndex getJarIndex() {
105 return m_jarIndex;
106 }
107
108 public Mappings getMappings() {
109 return m_mappings;
110 }
111
112 public void setMappings(Mappings val) {
113 if (val == null) {
114 val = new Mappings();
115 }
116
117 // pass 1: look for any classes that got moved to inner classes
118 Map<String,String> renames = Maps.newHashMap();
119 for (ClassMapping classMapping : val.classes()) {
120 // make sure we strip the packages off of obfuscated inner classes
121 String innerClassName = new ClassEntry(classMapping.getObfName()).getSimpleName();
122 String outerClassName = m_jarIndex.getOuterClass(innerClassName);
123 if (outerClassName != null) {
124 // build the composite class name
125 String newName = outerClassName + "$" + innerClassName;
126
127 // add a rename
128 renames.put(classMapping.getObfName(), newName);
129
130 System.out.println(String.format("Converted class mapping %s to %s", classMapping.getObfName(), newName));
131 }
132 }
133 for (Map.Entry<String,String> entry : renames.entrySet()) {
134 val.renameObfClass(entry.getKey(), entry.getValue());
135 }
136
137 // pass 2: look for fields/methods that are actually declared in superclasses
138 MappingsRenamer renamer = new MappingsRenamer(m_jarIndex, val);
139 for (ClassMapping classMapping : Lists.newArrayList(val.classes())) {
140 ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfName());
141
142 // fields
143 for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) {
144 FieldEntry fieldEntry = new FieldEntry(obfClassEntry, fieldMapping.getObfName());
145 ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(fieldEntry);
146 if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(fieldEntry.getClassEntry())) {
147 boolean wasMoved = renamer.moveFieldToObfClass(classMapping, fieldMapping, resolvedObfClassEntry);
148 if (wasMoved) {
149 System.out.println(String.format("Moved field %s to class %s", fieldEntry, resolvedObfClassEntry));
150 } else {
151 System.err.println(String.format("WARNING: Would move field %s to class %s but the field was already there. Dropping instead.", fieldEntry, resolvedObfClassEntry));
152 }
153 }
154 }
155
156 // methods
157 for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) {
158 // skip constructors
159 if (methodMapping.isConstructor()) {
160 continue;
161 }
162
163 MethodEntry methodEntry = new MethodEntry(
164 obfClassEntry,
165 methodMapping.getObfName(),
166 methodMapping.getObfSignature()
167 );
168 ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(methodEntry);
169 if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(methodEntry.getClassEntry())) {
170 boolean wasMoved = renamer.moveMethodToObfClass(classMapping, methodMapping, resolvedObfClassEntry);
171 if (wasMoved) {
172 System.out.println(String.format("Moved method %s to class %s", methodEntry, resolvedObfClassEntry));
173 } else {
174 System.err.println(String.format("WARNING: Would move method %s to class %s but the method was already there. Dropping instead.", methodEntry, resolvedObfClassEntry));
175 }
176 }
177 }
178
179 // TODO: recurse to inner classes?
180 }
181
182 // drop mappings that don't match the jar
183 List<ClassEntry> unknownClasses = Lists.newArrayList();
184 for (ClassMapping classMapping : val.classes()) {
185 checkClassMapping(unknownClasses, classMapping);
186 }
187 if (!unknownClasses.isEmpty()) {
188 throw new Error("Unable to find classes in jar: " + unknownClasses);
189 }
190
191 m_mappings = val;
192 m_renamer = renamer;
193 m_translatorCache.clear();
194 }
195
196 private void checkClassMapping(List<ClassEntry> unknownClasses, ClassMapping classMapping) {
197 // check the class
198 ClassEntry classEntry = new ClassEntry(classMapping.getObfName());
199 String outerClassName = m_jarIndex.getOuterClass(classEntry.getSimpleName());
200 if (outerClassName != null) {
201 classEntry = new ClassEntry(outerClassName + "$" + classMapping.getObfName());
202 }
203 if (!m_jarIndex.getObfClassEntries().contains(classEntry)) {
204 unknownClasses.add(classEntry);
205 }
206
207 // check the fields
208 for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) {
209 FieldEntry fieldEntry = new FieldEntry(classEntry, fieldMapping.getObfName());
210 if (!m_jarIndex.containsObfField(fieldEntry)) {
211 System.err.println("WARNING: unable to find field " + fieldEntry + ". dropping mapping.");
212 classMapping.removeFieldMapping(fieldMapping);
213 }
214 }
215
216 // check methods
217 for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) {
218 BehaviorEntry obfBehaviorEntry = BehaviorEntryFactory.createObf(classEntry, methodMapping);
219 if (!m_jarIndex.containsObfBehavior(obfBehaviorEntry)) {
220 System.err.println("WARNING: unable to find behavior " + obfBehaviorEntry + ". dropping mapping.");
221 classMapping.removeMethodMapping(methodMapping);
222 }
223 }
224
225 // check inner classes
226 for (ClassMapping innerClassMapping : classMapping.innerClasses()) {
227 checkClassMapping(unknownClasses, innerClassMapping);
228 }
229 }
230
231 public Translator getTranslator(TranslationDirection direction) {
232 Translator translator = m_translatorCache.get(direction);
233 if (translator == null) {
234 translator = m_mappings.getTranslator(direction, m_jarIndex.getTranslationIndex());
235 m_translatorCache.put(direction, translator);
236 }
237 return translator;
238 }
239
240 public void getSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) {
241 for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) {
242 // skip inner classes
243 if (obfClassEntry.isInnerClass()) {
244 continue;
245 }
246
247 // separate the classes
248 ClassEntry deobfClassEntry = deobfuscateEntry(obfClassEntry);
249 if (!deobfClassEntry.equals(obfClassEntry)) {
250 // if the class has a mapping, clearly it's deobfuscated
251 deobfClasses.add(deobfClassEntry);
252 } else if (!obfClassEntry.getPackageName().equals(Constants.NonePackage)) {
253 // also call it deobufscated if it's not in the none package
254 deobfClasses.add(obfClassEntry);
255 } else {
256 // otherwise, assume it's still obfuscated
257 obfClasses.add(obfClassEntry);
258 }
259 }
260 }
261
262 public CompilationUnit getSourceTree(String obfClassName) {
263 // is this class deobfuscated?
264 // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out
265 // the decompiler only sees the deobfuscated class, so we need to load it by the deobfuscated name
266 String lookupClassName = obfClassName;
267 ClassMapping classMapping = m_mappings.getClassByObf(obfClassName);
268 if (classMapping != null && classMapping.getDeobfName() != null) {
269 lookupClassName = classMapping.getDeobfName();
270 }
271
272 // is this class even in the jar?
273 if (!m_jarIndex.containsObfClass(new ClassEntry(obfClassName))) {
274 return null;
275 }
276
277 // set the type loader
278 m_settings.setTypeLoader(new TranslatingTypeLoader(
279 m_jar,
280 m_jarIndex,
281 getTranslator(TranslationDirection.Obfuscating),
282 getTranslator(TranslationDirection.Deobfuscating)
283 ));
284
285 // decompile it!
286 TypeDefinition resolvedType = new MetadataSystem(m_settings.getTypeLoader()).lookupType(lookupClassName).resolve();
287 DecompilerContext context = new DecompilerContext();
288 context.setCurrentType(resolvedType);
289 context.setSettings(m_settings);
290 AstBuilder builder = new AstBuilder(context);
291 builder.addType(resolvedType);
292 builder.runTransformations(null);
293 return builder.getCompilationUnit();
294 }
295
296 public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source) {
297 // build the source index
298 SourceIndex index = new SourceIndex(source);
299 sourceTree.acceptVisitor(new SourceIndexVisitor(), index);
300
301 // DEBUG
302 // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null );
303
304 // resolve all the classes in the source references
305 for (Token token : index.referenceTokens()) {
306 EntryReference<Entry,Entry> deobfReference = index.getDeobfReference(token);
307
308 // get the obfuscated entry
309 Entry obfEntry = obfuscateEntry(deobfReference.entry);
310
311 // try to resolve the class
312 ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(obfEntry);
313 if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getClassEntry())) {
314 // change the class of the entry
315 obfEntry = obfEntry.cloneToNewClass(resolvedObfClassEntry);
316
317 // save the new deobfuscated reference
318 deobfReference.entry = deobfuscateEntry(obfEntry);
319 index.replaceDeobfReference(token, deobfReference);
320 }
321
322 // DEBUG
323 // System.out.println( token + " -> " + reference + " -> " + index.getReferenceToken( reference ) );
324 }
325
326 return index;
327 }
328
329 public String getSource(CompilationUnit sourceTree) {
330 // render the AST into source
331 StringWriter buf = new StringWriter();
332 sourceTree.acceptVisitor(new InsertParenthesesVisitor(), null);
333 sourceTree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(buf), m_settings), null);
334 return buf.toString();
335 }
336
337 public void writeSources(File dirOut, ProgressListener progress) throws IOException {
338 // get the classes to decompile
339 Set<ClassEntry> classEntries = Sets.newHashSet();
340 for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) {
341 // skip inner classes
342 if (obfClassEntry.isInnerClass()) {
343 continue;
344 }
345
346 classEntries.add(obfClassEntry);
347 }
348
349 if (progress != null) {
350 progress.init(classEntries.size(), "Decompiling classes...");
351 }
352
353 // DEOBFUSCATE ALL THE THINGS!! @_@
354 int i = 0;
355 for (ClassEntry obfClassEntry : classEntries) {
356 ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry));
357 if (progress != null) {
358 progress.onProgress(i++, deobfClassEntry.toString());
359 }
360
361 try {
362 // get the source
363 String source = getSource(getSourceTree(obfClassEntry.getName()));
364
365 // write the file
366 File file = new File(dirOut, deobfClassEntry.getName().replace('.', '/') + ".java");
367 file.getParentFile().mkdirs();
368 try (FileWriter out = new FileWriter(file)) {
369 out.write(source);
370 }
371 } catch (Throwable t) {
372 throw new Error("Unable to deobfuscate class " + deobfClassEntry.toString() + " (" + obfClassEntry.toString() + ")", t);
373 }
374 }
375 if (progress != null) {
376 progress.onProgress(i, "Done!");
377 }
378 }
379
380 public void writeJar(File out, ProgressListener progress) {
381 try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) {
382 if (progress != null) {
383 progress.init(JarClassIterator.getClassEntries(m_jar).size(), "Translating classes...");
384 }
385
386 // prep the loader
387 TranslatingTypeLoader loader = new TranslatingTypeLoader(
388 m_jar,
389 m_jarIndex,
390 getTranslator(TranslationDirection.Obfuscating),
391 getTranslator(TranslationDirection.Deobfuscating)
392 );
393
394 int i = 0;
395 for (CtClass c : JarClassIterator.classes(m_jar)) {
396 if (progress != null) {
397 progress.onProgress(i++, c.getName());
398 }
399
400 try {
401 c = loader.transformClass(c);
402 outJar.putNextEntry(new JarEntry(c.getName().replace('.', '/') + ".class"));
403 outJar.write(c.toBytecode());
404 outJar.closeEntry();
405 } catch (Throwable t) {
406 throw new Error("Unable to deobfuscate class " + c.getName(), t);
407 }
408 }
409 if (progress != null) {
410 progress.onProgress(i, "Done!");
411 }
412
413 outJar.close();
414 } catch (IOException ex) {
415 throw new Error("Unable to write to Jar file!");
416 }
417 }
418
419 public <T extends Entry> T obfuscateEntry(T deobfEntry) {
420 if (deobfEntry == null) {
421 return null;
422 }
423 return getTranslator(TranslationDirection.Obfuscating).translateEntry(deobfEntry);
424 }
425
426 public <T extends Entry> T deobfuscateEntry(T obfEntry) {
427 if (obfEntry == null) {
428 return null;
429 }
430 return getTranslator(TranslationDirection.Deobfuscating).translateEntry(obfEntry);
431 }
432
433 public <E extends Entry,C extends Entry> EntryReference<E,C> obfuscateReference(EntryReference<E,C> deobfReference) {
434 if (deobfReference == null) {
435 return null;
436 }
437 return new EntryReference<E,C>(
438 obfuscateEntry(deobfReference.entry),
439 obfuscateEntry(deobfReference.context),
440 deobfReference
441 );
442 }
443
444 public <E extends Entry,C extends Entry> EntryReference<E,C> deobfuscateReference(EntryReference<E,C> obfReference) {
445 if (obfReference == null) {
446 return null;
447 }
448 return new EntryReference<E,C>(
449 deobfuscateEntry(obfReference.entry),
450 deobfuscateEntry(obfReference.context),
451 obfReference
452 );
453 }
454
455 public boolean isObfuscatedIdentifier(Entry obfEntry) {
456 return m_jarIndex.containsObfEntry(obfEntry);
457 }
458
459 public boolean isRenameable(EntryReference<Entry,Entry> obfReference) {
460 return obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry());
461 }
462
463 // NOTE: these methods are a bit messy... oh well
464
465 public boolean hasDeobfuscatedName(Entry obfEntry) {
466 Translator translator = getTranslator(TranslationDirection.Deobfuscating);
467 if (obfEntry instanceof ClassEntry) {
468 return translator.translate((ClassEntry)obfEntry) != null;
469 } else if (obfEntry instanceof FieldEntry) {
470 return translator.translate((FieldEntry)obfEntry) != null;
471 } else if (obfEntry instanceof MethodEntry) {
472 return translator.translate((MethodEntry)obfEntry) != null;
473 } else if (obfEntry instanceof ConstructorEntry) {
474 // constructors have no names
475 return false;
476 } else if (obfEntry instanceof ArgumentEntry) {
477 return translator.translate((ArgumentEntry)obfEntry) != null;
478 } else {
479 throw new Error("Unknown entry type: " + obfEntry.getClass().getName());
480 }
481 }
482
483 public void rename(Entry obfEntry, String newName) {
484 if (obfEntry instanceof ClassEntry) {
485 m_renamer.setClassName((ClassEntry)obfEntry, Descriptor.toJvmName(newName));
486 } else if (obfEntry instanceof FieldEntry) {
487 m_renamer.setFieldName((FieldEntry)obfEntry, newName);
488 } else if (obfEntry instanceof MethodEntry) {
489 m_renamer.setMethodTreeName((MethodEntry)obfEntry, newName);
490 } else if (obfEntry instanceof ConstructorEntry) {
491 throw new IllegalArgumentException("Cannot rename constructors");
492 } else if (obfEntry instanceof ArgumentEntry) {
493 m_renamer.setArgumentName((ArgumentEntry)obfEntry, newName);
494 } else {
495 throw new Error("Unknown entry type: " + obfEntry.getClass().getName());
496 }
497
498 // clear caches
499 m_translatorCache.clear();
500 }
501
502 public void removeMapping(Entry obfEntry) {
503 if (obfEntry instanceof ClassEntry) {
504 m_renamer.removeClassMapping((ClassEntry)obfEntry);
505 } else if (obfEntry instanceof FieldEntry) {
506 m_renamer.removeFieldMapping((FieldEntry)obfEntry);
507 } else if (obfEntry instanceof MethodEntry) {
508 m_renamer.removeMethodTreeMapping((MethodEntry)obfEntry);
509 } else if (obfEntry instanceof ConstructorEntry) {
510 throw new IllegalArgumentException("Cannot rename constructors");
511 } else if (obfEntry instanceof ArgumentEntry) {
512 m_renamer.removeArgumentMapping((ArgumentEntry)obfEntry);
513 } else {
514 throw new Error("Unknown entry type: " + obfEntry);
515 }
516
517 // clear caches
518 m_translatorCache.clear();
519 }
520
521 public void markAsDeobfuscated(Entry obfEntry) {
522 if (obfEntry instanceof ClassEntry) {
523 m_renamer.markClassAsDeobfuscated((ClassEntry)obfEntry);
524 } else if (obfEntry instanceof FieldEntry) {
525 m_renamer.markFieldAsDeobfuscated((FieldEntry)obfEntry);
526 } else if (obfEntry instanceof MethodEntry) {
527 m_renamer.markMethodTreeAsDeobfuscated((MethodEntry)obfEntry);
528 } else if (obfEntry instanceof ConstructorEntry) {
529 throw new IllegalArgumentException("Cannot rename constructors");
530 } else if (obfEntry instanceof ArgumentEntry) {
531 m_renamer.markArgumentAsDeobfuscated((ArgumentEntry)obfEntry);
532 } else {
533 throw new Error("Unknown entry type: " + obfEntry);
534 }
535
536 // clear caches
537 m_translatorCache.clear();
538 }
539}
diff --git a/src/cuchaz/enigma/Main.java b/src/cuchaz/enigma/Main.java
new file mode 100644
index 0000000..acae94b
--- /dev/null
+++ b/src/cuchaz/enigma/Main.java
@@ -0,0 +1,51 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java
new file mode 100644
index 0000000..9287999
--- /dev/null
+++ b/src/cuchaz/enigma/TranslatingTypeLoader.java
@@ -0,0 +1,211 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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.Map;
17import java.util.jar.JarEntry;
18import java.util.jar.JarFile;
19
20import javassist.ByteArrayClassPath;
21import javassist.CannotCompileException;
22import javassist.ClassPool;
23import javassist.CtClass;
24import javassist.NotFoundException;
25import javassist.bytecode.Descriptor;
26
27import com.google.common.collect.Maps;
28import com.strobel.assembler.metadata.Buffer;
29import com.strobel.assembler.metadata.ClasspathTypeLoader;
30import com.strobel.assembler.metadata.ITypeLoader;
31
32import cuchaz.enigma.analysis.JarIndex;
33import cuchaz.enigma.bytecode.ClassRenamer;
34import cuchaz.enigma.bytecode.ClassTranslator;
35import cuchaz.enigma.bytecode.InnerClassWriter;
36import cuchaz.enigma.bytecode.MethodParameterWriter;
37import cuchaz.enigma.mapping.ClassEntry;
38import cuchaz.enigma.mapping.Translator;
39
40public class TranslatingTypeLoader implements ITypeLoader {
41
42 private JarFile m_jar;
43 private JarIndex m_jarIndex;
44 private Translator m_obfuscatingTranslator;
45 private Translator m_deobfuscatingTranslator;
46 private Map<String,byte[]> m_cache;
47 private ClasspathTypeLoader m_defaultTypeLoader;
48
49 public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex) {
50 this(jar, jarIndex, new Translator(), new Translator());
51 }
52
53 public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator) {
54 m_jar = jar;
55 m_jarIndex = jarIndex;
56 m_obfuscatingTranslator = obfuscatingTranslator;
57 m_deobfuscatingTranslator = deobfuscatingTranslator;
58 m_cache = Maps.newHashMap();
59 m_defaultTypeLoader = new ClasspathTypeLoader();
60 }
61
62 public void clearCache() {
63 m_cache.clear();
64 }
65
66 @Override
67 public boolean tryLoadType(String deobfClassName, Buffer out) {
68 // check the cache
69 byte[] data;
70 if (m_cache.containsKey(deobfClassName)) {
71 data = m_cache.get(deobfClassName);
72 } else {
73 data = loadType(deobfClassName);
74 m_cache.put(deobfClassName, data);
75 }
76
77 if (data == null) {
78 // chain to default type loader
79 return m_defaultTypeLoader.tryLoadType(deobfClassName, out);
80 }
81
82 // send the class to the decompiler
83 out.reset(data.length);
84 System.arraycopy(data, 0, out.array(), out.position(), data.length);
85 out.position(0);
86 return true;
87 }
88
89 public CtClass loadClass(String deobfClassName) {
90 byte[] data = loadType(deobfClassName);
91 if (data == null) {
92 return null;
93 }
94
95 // return a javassist handle for the class
96 String javaClassFileName = Descriptor.toJavaName(deobfClassName);
97 ClassPool classPool = new ClassPool();
98 classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, data));
99 try {
100 return classPool.get(javaClassFileName);
101 } catch (NotFoundException ex) {
102 throw new Error(ex);
103 }
104 }
105
106 private byte[] loadType(String deobfClassName) {
107 ClassEntry deobfClassEntry = new ClassEntry(deobfClassName);
108 ClassEntry obfClassEntry = m_obfuscatingTranslator.translateEntry(deobfClassEntry);
109
110 // is this an inner class referenced directly?
111 String obfOuterClassName = m_jarIndex.getOuterClass(obfClassEntry.getSimpleName());
112 if (obfOuterClassName != null) {
113 // this class doesn't really exist. Reference it by outer$inner instead
114 System.err.println(String.format("WARNING: class %s referenced by bare inner name instead of via outer class %s", deobfClassName, obfOuterClassName));
115 return null;
116 }
117
118 /* DEBUG
119 if( !Arrays.asList( "java", "org", "io" ).contains( deobfClassName.split( "/" )[0] ) ) {
120 System.out.println( String.format( "Looking for %s (%s)", deobfClassEntry.getName(), obfClassEntry.getName() ) );
121 }
122 */
123
124 // get the jar entry
125 String classFileName;
126 if (obfClassEntry.isInnerClass()) {
127 // use just the inner class name for inner classes
128 classFileName = obfClassEntry.getInnerClassName();
129 } else if (obfClassEntry.getPackageName().equals(Constants.NonePackage)) {
130 // use the outer class simple name for classes in the none package
131 classFileName = obfClassEntry.getSimpleName();
132 } else {
133 // otherwise, just use the class name (ie for classes in packages)
134 classFileName = obfClassEntry.getName();
135 }
136
137 JarEntry entry = m_jar.getJarEntry(classFileName + ".class");
138 if (entry == null) {
139 return null;
140 }
141
142 try {
143 // read the class file into a buffer
144 ByteArrayOutputStream data = new ByteArrayOutputStream();
145 byte[] buf = new byte[1024 * 1024]; // 1 KiB
146 InputStream in = m_jar.getInputStream(entry);
147 while (true) {
148 int bytesRead = in.read(buf);
149 if (bytesRead <= 0) {
150 break;
151 }
152 data.write(buf, 0, bytesRead);
153 }
154 data.close();
155 in.close();
156 buf = data.toByteArray();
157
158 // load the javassist handle to the raw class
159 String javaClassFileName = Descriptor.toJavaName(classFileName);
160 ClassPool classPool = new ClassPool();
161 classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, buf));
162 CtClass c = classPool.get(javaClassFileName);
163
164 c = transformClass(c);
165
166 // sanity checking
167 assertClassName(c, deobfClassEntry);
168
169 // DEBUG
170 Util.writeClass( c );
171
172 // we have a transformed class!
173 return c.toBytecode();
174 } catch (IOException | NotFoundException | CannotCompileException ex) {
175 throw new Error(ex);
176 }
177 }
178
179 public CtClass transformClass(CtClass c) throws IOException, NotFoundException, CannotCompileException {
180 // we moved a lot of classes out of the default package into the none package
181 // make sure all the class references are consistent
182 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
183
184 // reconstruct inner classes
185 new InnerClassWriter(m_jarIndex).write(c);
186
187 // re-get the javassist handle since we changed class names
188 ClassEntry obfClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
189 String javaClassReconstructedName = Descriptor.toJavaName(obfClassEntry.getName());
190 ClassPool classPool = new ClassPool();
191 classPool.insertClassPath(new ByteArrayClassPath(javaClassReconstructedName, c.toBytecode()));
192 c = classPool.get(javaClassReconstructedName);
193
194 // check that the file is correct after inner class reconstruction (ie cause Javassist to fail fast if something is wrong)
195 assertClassName(c, obfClassEntry);
196
197 // do all kinds of deobfuscating transformations on the class
198 new MethodParameterWriter(m_deobfuscatingTranslator).writeMethodArguments(c);
199 new ClassTranslator(m_deobfuscatingTranslator).translate(c);
200
201 return c;
202 }
203
204 private void assertClassName(CtClass c, ClassEntry obfClassEntry) {
205 String name1 = Descriptor.toJvmName(c.getName());
206 assert (name1.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name1);
207
208 String name2 = Descriptor.toJvmName(c.getClassFile().getName());
209 assert (name2.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name2);
210 }
211}
diff --git a/src/cuchaz/enigma/Util.java b/src/cuchaz/enigma/Util.java
new file mode 100644
index 0000000..7f04bda
--- /dev/null
+++ b/src/cuchaz/enigma/Util.java
@@ -0,0 +1,104 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..8d3409a
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/Access.java
@@ -0,0 +1,43 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..9adac5e
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java
@@ -0,0 +1,93 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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/ClassImplementationsTreeNode.java b/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
new file mode 100644
index 0000000..49aac5f
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
@@ -0,0 +1,80 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..3eaa391
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java
@@ -0,0 +1,85 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..bb611df
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/EntryReference.java
@@ -0,0 +1,126 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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.getInnerClassName();
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
new file mode 100644
index 0000000..b54489c
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/EntryRenamer.java
@@ -0,0 +1,171 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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.ConstructorEntry;
25import cuchaz.enigma.mapping.Entry;
26import cuchaz.enigma.mapping.FieldEntry;
27import cuchaz.enigma.mapping.MethodEntry;
28
29public class EntryRenamer {
30
31 public static <T> void renameClassesInSet(Map<String,String> renames, Set<T> set) {
32 List<T> entries = Lists.newArrayList();
33 for (T val : set) {
34 entries.add(renameClassesInThing(renames, val));
35 }
36 set.clear();
37 set.addAll(entries);
38 }
39
40 public static <Key,Val> void renameClassesInMap(Map<String,String> renames, Map<Key,Val> map) {
41 // for each key/value pair...
42 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
43 for (Map.Entry<Key,Val> entry : map.entrySet()) {
44 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
45 renameClassesInThing(renames, entry.getKey()),
46 renameClassesInThing(renames, entry.getValue())
47 ));
48 }
49 map.clear();
50 for (Map.Entry<Key,Val> entry : entriesToAdd) {
51 map.put(entry.getKey(), entry.getValue());
52 }
53 }
54
55 public static <Key,Val> void renameClassesInMultimap(Map<String,String> renames, Multimap<Key,Val> map) {
56 // for each key/value pair...
57 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
58 for (Map.Entry<Key,Val> entry : map.entries()) {
59 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
60 renameClassesInThing(renames, entry.getKey()),
61 renameClassesInThing(renames, entry.getValue())
62 ));
63 }
64 map.clear();
65 for (Map.Entry<Key,Val> entry : entriesToAdd) {
66 map.put(entry.getKey(), entry.getValue());
67 }
68 }
69
70 public static <Key,Val> void renameMethodsInMultimap(Map<MethodEntry,MethodEntry> renames, Multimap<Key,Val> map) {
71 // for each key/value pair...
72 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
73 for (Map.Entry<Key,Val> entry : map.entries()) {
74 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
75 renameMethodsInThing(renames, entry.getKey()),
76 renameMethodsInThing(renames, entry.getValue())
77 ));
78 }
79 map.clear();
80 for (Map.Entry<Key,Val> entry : entriesToAdd) {
81 map.put(entry.getKey(), entry.getValue());
82 }
83 }
84
85 public static <Key,Val> void renameMethodsInMap(Map<MethodEntry,MethodEntry> renames, Map<Key,Val> map) {
86 // for each key/value pair...
87 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
88 for (Map.Entry<Key,Val> entry : map.entrySet()) {
89 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
90 renameMethodsInThing(renames, entry.getKey()),
91 renameMethodsInThing(renames, entry.getValue())
92 ));
93 }
94 map.clear();
95 for (Map.Entry<Key,Val> entry : entriesToAdd) {
96 map.put(entry.getKey(), entry.getValue());
97 }
98 }
99
100 @SuppressWarnings("unchecked")
101 public static <T> T renameMethodsInThing(Map<MethodEntry,MethodEntry> renames, T thing) {
102 if (thing instanceof MethodEntry) {
103 MethodEntry methodEntry = (MethodEntry)thing;
104 MethodEntry newMethodEntry = renames.get(methodEntry);
105 if (newMethodEntry != null) {
106 return (T)new MethodEntry(
107 methodEntry.getClassEntry(),
108 newMethodEntry.getName(),
109 methodEntry.getSignature()
110 );
111 }
112 return thing;
113 } else if (thing instanceof ArgumentEntry) {
114 ArgumentEntry argumentEntry = (ArgumentEntry)thing;
115 return (T)new ArgumentEntry(
116 renameMethodsInThing(renames, argumentEntry.getBehaviorEntry()),
117 argumentEntry.getIndex(),
118 argumentEntry.getName()
119 );
120 } else if (thing instanceof EntryReference) {
121 EntryReference<Entry,Entry> reference = (EntryReference<Entry,Entry>)thing;
122 reference.entry = renameMethodsInThing(renames, reference.entry);
123 reference.context = renameMethodsInThing(renames, reference.context);
124 return thing;
125 }
126 return thing;
127 }
128
129 @SuppressWarnings("unchecked")
130 public static <T> T renameClassesInThing(Map<String,String> renames, T thing) {
131 if (thing instanceof String) {
132 String stringEntry = (String)thing;
133 if (renames.containsKey(stringEntry)) {
134 return (T)renames.get(stringEntry);
135 }
136 } else if (thing instanceof ClassEntry) {
137 ClassEntry classEntry = (ClassEntry)thing;
138 return (T)new ClassEntry(renameClassesInThing(renames, classEntry.getClassName()));
139 } else if (thing instanceof FieldEntry) {
140 FieldEntry fieldEntry = (FieldEntry)thing;
141 return (T)new FieldEntry(renameClassesInThing(renames, fieldEntry.getClassEntry()), fieldEntry.getName());
142 } else if (thing instanceof ConstructorEntry) {
143 ConstructorEntry constructorEntry = (ConstructorEntry)thing;
144 return (T)new ConstructorEntry(
145 renameClassesInThing(renames, constructorEntry.getClassEntry()),
146 constructorEntry.getSignature()
147 );
148 } else if (thing instanceof MethodEntry) {
149 MethodEntry methodEntry = (MethodEntry)thing;
150 return (T)new MethodEntry(
151 renameClassesInThing(renames, methodEntry.getClassEntry()),
152 methodEntry.getName(),
153 methodEntry.getSignature()
154 );
155 } else if (thing instanceof ArgumentEntry) {
156 ArgumentEntry argumentEntry = (ArgumentEntry)thing;
157 return (T)new ArgumentEntry(
158 renameClassesInThing(renames, argumentEntry.getBehaviorEntry()),
159 argumentEntry.getIndex(),
160 argumentEntry.getName()
161 );
162 } else if (thing instanceof EntryReference) {
163 EntryReference<Entry,Entry> reference = (EntryReference<Entry,Entry>)thing;
164 reference.entry = renameClassesInThing(renames, reference.entry);
165 reference.context = renameClassesInThing(renames, reference.context);
166 return thing;
167 }
168
169 return thing;
170 }
171}
diff --git a/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
new file mode 100644
index 0000000..2173eea
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
@@ -0,0 +1,81 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..72a9912
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/JarClassIterator.java
@@ -0,0 +1,137 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..c96d3bc
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/JarIndex.java
@@ -0,0 +1,828 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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.HashSet;
16import java.util.List;
17import java.util.Map;
18import java.util.Set;
19import java.util.jar.JarFile;
20
21import javassist.CannotCompileException;
22import javassist.CtBehavior;
23import javassist.CtClass;
24import javassist.CtConstructor;
25import javassist.CtField;
26import javassist.CtMethod;
27import javassist.NotFoundException;
28import javassist.bytecode.AccessFlag;
29import javassist.bytecode.Descriptor;
30import javassist.bytecode.FieldInfo;
31import javassist.expr.ConstructorCall;
32import javassist.expr.ExprEditor;
33import javassist.expr.FieldAccess;
34import javassist.expr.MethodCall;
35import javassist.expr.NewExpr;
36
37import com.google.common.collect.HashMultimap;
38import com.google.common.collect.Lists;
39import com.google.common.collect.Maps;
40import com.google.common.collect.Multimap;
41import com.google.common.collect.Sets;
42
43import cuchaz.enigma.Constants;
44import cuchaz.enigma.bytecode.ClassRenamer;
45import cuchaz.enigma.mapping.ArgumentEntry;
46import cuchaz.enigma.mapping.BehaviorEntry;
47import cuchaz.enigma.mapping.BehaviorEntryFactory;
48import cuchaz.enigma.mapping.ClassEntry;
49import cuchaz.enigma.mapping.ConstructorEntry;
50import cuchaz.enigma.mapping.Entry;
51import cuchaz.enigma.mapping.FieldEntry;
52import cuchaz.enigma.mapping.MethodEntry;
53import cuchaz.enigma.mapping.SignatureUpdater;
54import cuchaz.enigma.mapping.Translator;
55
56public class JarIndex {
57
58 private Set<ClassEntry> m_obfClassEntries;
59 private TranslationIndex m_translationIndex;
60 private Multimap<String,String> m_interfaces;
61 private Map<Entry,Access> m_access;
62 private Map<FieldEntry,ClassEntry> m_fieldClasses;
63 private Multimap<String,MethodEntry> m_methodImplementations;
64 private Multimap<BehaviorEntry,EntryReference<BehaviorEntry,BehaviorEntry>> m_behaviorReferences;
65 private Multimap<FieldEntry,EntryReference<FieldEntry,BehaviorEntry>> m_fieldReferences;
66 private Multimap<String,String> m_innerClasses;
67 private Map<String,String> m_outerClasses;
68 private Map<String,BehaviorEntry> m_anonymousClasses;
69 private Map<MethodEntry,MethodEntry> m_bridgeMethods;
70
71 public JarIndex() {
72 m_obfClassEntries = Sets.newHashSet();
73 m_translationIndex = new TranslationIndex();
74 m_interfaces = HashMultimap.create();
75 m_access = Maps.newHashMap();
76 m_fieldClasses = Maps.newHashMap();
77 m_methodImplementations = HashMultimap.create();
78 m_behaviorReferences = HashMultimap.create();
79 m_fieldReferences = HashMultimap.create();
80 m_innerClasses = HashMultimap.create();
81 m_outerClasses = Maps.newHashMap();
82 m_anonymousClasses = Maps.newHashMap();
83 m_bridgeMethods = Maps.newHashMap();
84 }
85
86 public void indexJar(JarFile jar, boolean buildInnerClasses) {
87 // step 1: read the class names
88 for (ClassEntry classEntry : JarClassIterator.getClassEntries(jar)) {
89 if (classEntry.isInDefaultPackage()) {
90 // move out of default package
91 classEntry = new ClassEntry(Constants.NonePackage + "/" + classEntry.getName());
92 }
93 m_obfClassEntries.add(classEntry);
94 }
95
96 // step 2: index field/method/constructor access
97 for (CtClass c : JarClassIterator.classes(jar)) {
98 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
99 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
100 for (CtField field : c.getDeclaredFields()) {
101 FieldEntry fieldEntry = new FieldEntry(classEntry, field.getName());
102 m_access.put(fieldEntry, Access.get(field));
103 }
104 for (CtMethod method : c.getDeclaredMethods()) {
105 MethodEntry methodEntry = new MethodEntry(classEntry, method.getName(), method.getSignature());
106 m_access.put(methodEntry, Access.get(method));
107 }
108 for (CtConstructor constructor : c.getDeclaredConstructors()) {
109 ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, constructor.getSignature());
110 m_access.put(constructorEntry, Access.get(constructor));
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 m_interfaces.put(className, interfaceName);
126 }
127 for (CtField field : c.getDeclaredFields()) {
128 indexField(field);
129 }
130 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
131 indexBehavior(behavior);
132 }
133 }
134
135 // step 4: index field, method, constructor references
136 for (CtClass c : JarClassIterator.classes(jar)) {
137 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
138 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
139 indexBehaviorReferences(behavior);
140 }
141 }
142
143 if (buildInnerClasses) {
144 // step 5: index inner classes and anonymous classes
145 for (CtClass c : JarClassIterator.classes(jar)) {
146 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
147 String outerClassName = findOuterClass(c);
148 if (outerClassName != null) {
149 String innerClassName = c.getSimpleName();
150 m_innerClasses.put(outerClassName, innerClassName);
151 boolean innerWasAdded = m_outerClasses.put(innerClassName, outerClassName) == null;
152 assert (innerWasAdded);
153
154 BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassName);
155 if (enclosingBehavior != null) {
156 m_anonymousClasses.put(innerClassName, enclosingBehavior);
157
158 // DEBUG
159 // System.out.println( "ANONYMOUS: " + outerClassName + "$" + innerClassName );
160 } else {
161 // DEBUG
162 // System.out.println( "INNER: " + outerClassName + "$" + innerClassName );
163 }
164 }
165 }
166
167 // step 6: update other indices with inner class info
168 Map<String,String> renames = Maps.newHashMap();
169 for (Map.Entry<String,String> entry : m_outerClasses.entrySet()) {
170 renames.put(Constants.NonePackage + "/" + entry.getKey(), entry.getValue() + "$" + entry.getKey());
171 }
172 EntryRenamer.renameClassesInSet(renames, m_obfClassEntries);
173 m_translationIndex.renameClasses(renames);
174 EntryRenamer.renameClassesInMultimap(renames, m_interfaces);
175 EntryRenamer.renameClassesInMultimap(renames, m_methodImplementations);
176 EntryRenamer.renameClassesInMultimap(renames, m_behaviorReferences);
177 EntryRenamer.renameClassesInMultimap(renames, m_fieldReferences);
178 EntryRenamer.renameClassesInMap(renames, m_bridgeMethods);
179 EntryRenamer.renameClassesInMap(renames, m_access);
180 }
181
182 // step 6: update other indices with bridge method info
183 EntryRenamer.renameMethodsInMultimap(m_bridgeMethods, m_methodImplementations);
184 EntryRenamer.renameMethodsInMultimap(m_bridgeMethods, m_behaviorReferences);
185 EntryRenamer.renameMethodsInMultimap(m_bridgeMethods, m_fieldReferences);
186 EntryRenamer.renameMethodsInMap(m_bridgeMethods, m_access);
187 }
188
189 private void indexField(CtField field) {
190 // get the field entry
191 String className = Descriptor.toJvmName(field.getDeclaringClass().getName());
192 FieldEntry fieldEntry = new FieldEntry(new ClassEntry(className), field.getName());
193
194 // is the field a class type?
195 if (field.getSignature().startsWith("L")) {
196 ClassEntry fieldTypeEntry = new ClassEntry(field.getSignature().substring(1, field.getSignature().length() - 1));
197 m_fieldClasses.put(fieldEntry, fieldTypeEntry);
198 }
199 }
200
201 private void indexBehavior(CtBehavior behavior) {
202 // get the behavior entry
203 final BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(behavior);
204 if (behaviorEntry instanceof MethodEntry) {
205 MethodEntry methodEntry = (MethodEntry)behaviorEntry;
206
207 // index implementation
208 m_methodImplementations.put(behaviorEntry.getClassName(), methodEntry);
209
210 // look for bridge methods
211 CtMethod bridgedMethod = getBridgedMethod((CtMethod)behavior);
212 if (bridgedMethod != null) {
213 MethodEntry bridgedMethodEntry = new MethodEntry(
214 behaviorEntry.getClassEntry(),
215 bridgedMethod.getName(),
216 bridgedMethod.getSignature()
217 );
218 m_bridgeMethods.put(bridgedMethodEntry, methodEntry);
219 }
220 }
221 // looks like we don't care about constructors here
222 }
223
224 private void indexBehaviorReferences(CtBehavior behavior) {
225 // index method calls
226 final BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(behavior);
227 try {
228 behavior.instrument(new ExprEditor() {
229 @Override
230 public void edit(MethodCall call) {
231 MethodEntry calledMethodEntry = new MethodEntry(
232 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
233 call.getMethodName(),
234 call.getSignature()
235 );
236 ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledMethodEntry);
237 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) {
238 calledMethodEntry = new MethodEntry(
239 resolvedClassEntry,
240 call.getMethodName(),
241 call.getSignature()
242 );
243 }
244 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
245 calledMethodEntry,
246 call.getMethodName(),
247 behaviorEntry
248 );
249 m_behaviorReferences.put(calledMethodEntry, reference);
250 }
251
252 @Override
253 public void edit(FieldAccess call) {
254 FieldEntry calledFieldEntry = new FieldEntry(
255 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
256 call.getFieldName()
257 );
258 ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledFieldEntry);
259 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) {
260 calledFieldEntry = new FieldEntry(resolvedClassEntry, call.getFieldName());
261 }
262 EntryReference<FieldEntry,BehaviorEntry> reference = new EntryReference<FieldEntry,BehaviorEntry>(
263 calledFieldEntry,
264 call.getFieldName(),
265 behaviorEntry
266 );
267 m_fieldReferences.put(calledFieldEntry, reference);
268 }
269
270 @Override
271 public void edit(ConstructorCall call) {
272 ConstructorEntry calledConstructorEntry = new ConstructorEntry(
273 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
274 call.getSignature()
275 );
276 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
277 calledConstructorEntry,
278 call.getMethodName(),
279 behaviorEntry
280 );
281 m_behaviorReferences.put(calledConstructorEntry, reference);
282 }
283
284 @Override
285 public void edit(NewExpr call) {
286 ConstructorEntry calledConstructorEntry = new ConstructorEntry(
287 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
288 call.getSignature()
289 );
290 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
291 calledConstructorEntry,
292 call.getClassName(),
293 behaviorEntry
294 );
295 m_behaviorReferences.put(calledConstructorEntry, reference);
296 }
297 });
298 } catch (CannotCompileException ex) {
299 throw new Error(ex);
300 }
301 }
302
303 private CtMethod getBridgedMethod(CtMethod method) {
304
305 // bridge methods just call another method, cast it to the return type, and return the result
306 // let's see if we can detect this scenario
307
308 // skip non-synthetic methods
309 if ( (method.getModifiers() & AccessFlag.SYNTHETIC) == 0) {
310 return null;
311 }
312
313 // get all the called methods
314 final List<MethodCall> methodCalls = Lists.newArrayList();
315 try {
316 method.instrument(new ExprEditor() {
317 @Override
318 public void edit(MethodCall call) {
319 methodCalls.add(call);
320 }
321 });
322 } catch (CannotCompileException ex) {
323 // this is stupid... we're not even compiling anything
324 throw new Error(ex);
325 }
326
327 // is there just one?
328 if (methodCalls.size() != 1) {
329 return null;
330 }
331 MethodCall call = methodCalls.get(0);
332
333 try {
334 // we have a bridge method!
335 return call.getMethod();
336 } catch (NotFoundException ex) {
337 // can't find the type? not a bridge method
338 return null;
339 }
340 }
341
342 private String findOuterClass(CtClass c) {
343
344 // inner classes:
345 // have constructors that can (illegally) set synthetic fields
346 // the outer class is the only class that calls constructors
347
348 // use the synthetic fields to find the synthetic constructors
349 for (CtConstructor constructor : c.getDeclaredConstructors()) {
350 Set<String> syntheticFieldTypes = Sets.newHashSet();
351 if (!isIllegalConstructor(syntheticFieldTypes, constructor)) {
352 continue;
353 }
354
355 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
356 ConstructorEntry constructorEntry = new ConstructorEntry(
357 classEntry,
358 constructor.getMethodInfo().getDescriptor()
359 );
360
361 // gather the classes from the illegally-set synthetic fields
362 Set<ClassEntry> illegallySetClasses = Sets.newHashSet();
363 for (String type : syntheticFieldTypes) {
364 if (type.startsWith("L")) {
365 ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1));
366 if (isSaneOuterClass(outerClassEntry, classEntry)) {
367 illegallySetClasses.add(outerClassEntry);
368 }
369 }
370 }
371
372 // who calls this constructor?
373 Set<ClassEntry> callerClasses = Sets.newHashSet();
374 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : getBehaviorReferences(constructorEntry)) {
375
376 // make sure it's not a call to super
377 if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) {
378
379 // is the entry a superclass of the context?
380 ClassEntry calledClassEntry = reference.entry.getClassEntry();
381 ClassEntry superclassEntry = m_translationIndex.getSuperclass(reference.context.getClassEntry());
382 if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) {
383 // it's a super call, skip
384 continue;
385 }
386 }
387
388 if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) {
389 callerClasses.add(reference.context.getClassEntry());
390 }
391 }
392
393 // do we have an answer yet?
394 if (callerClasses.isEmpty()) {
395 if (illegallySetClasses.size() == 1) {
396 return illegallySetClasses.iterator().next().getName();
397 } else {
398 System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry));
399 }
400 } else {
401 if (callerClasses.size() == 1) {
402 return callerClasses.iterator().next().getName();
403 } else {
404 // multiple callers, do the illegally set classes narrow it down?
405 Set<ClassEntry> intersection = Sets.newHashSet(callerClasses);
406 intersection.retainAll(illegallySetClasses);
407 if (intersection.size() == 1) {
408 return intersection.iterator().next().getName();
409 } else {
410 System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses));
411 }
412 }
413 }
414 }
415
416 return null;
417 }
418
419 private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) {
420
421 // clearly this would be silly
422 if (outerClassEntry.equals(innerClassEntry)) {
423 return false;
424 }
425
426 // is the outer class in the jar?
427 if (!m_obfClassEntries.contains(outerClassEntry)) {
428 return false;
429 }
430
431 return true;
432 }
433
434 @SuppressWarnings("unchecked")
435 private boolean isIllegalConstructor(Set<String> syntheticFieldTypes, CtConstructor constructor) {
436
437 // illegal constructors only set synthetic member fields, then call super()
438 String className = constructor.getDeclaringClass().getName();
439
440 // collect all the field accesses, constructor calls, and method calls
441 final List<FieldAccess> illegalFieldWrites = Lists.newArrayList();
442 final List<ConstructorCall> constructorCalls = Lists.newArrayList();
443 try {
444 constructor.instrument(new ExprEditor() {
445 @Override
446 public void edit(FieldAccess fieldAccess) {
447 if (fieldAccess.isWriter() && constructorCalls.isEmpty()) {
448 illegalFieldWrites.add(fieldAccess);
449 }
450 }
451
452 @Override
453 public void edit(ConstructorCall constructorCall) {
454 constructorCalls.add(constructorCall);
455 }
456 });
457 } catch (CannotCompileException ex) {
458 // we're not compiling anything... this is stupid
459 throw new Error(ex);
460 }
461
462 // are there any illegal field writes?
463 if (illegalFieldWrites.isEmpty()) {
464 return false;
465 }
466
467 // are all the writes to synthetic fields?
468 for (FieldAccess fieldWrite : illegalFieldWrites) {
469
470 // all illegal writes have to be to the local class
471 if (!fieldWrite.getClassName().equals(className)) {
472 System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName()));
473 return false;
474 }
475
476 // find the field
477 FieldInfo fieldInfo = null;
478 for (FieldInfo info : (List<FieldInfo>)constructor.getDeclaringClass().getClassFile().getFields()) {
479 if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) {
480 fieldInfo = info;
481 break;
482 }
483 }
484 if (fieldInfo == null) {
485 // field is in a superclass or something, can't be a local synthetic member
486 return false;
487 }
488
489 // is this field synthetic?
490 boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0;
491 if (isSynthetic) {
492 syntheticFieldTypes.add(fieldInfo.getDescriptor());
493 } else {
494 System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName()));
495 return false;
496 }
497 }
498
499 // we passed all the tests!
500 return true;
501 }
502
503 private BehaviorEntry isAnonymousClass(CtClass c, String outerClassName) {
504
505 ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
506
507 // anonymous classes:
508 // can't be abstract
509 // have only one constructor
510 // it's called exactly once by the outer class
511 // the type the instance is assigned to can't be this type
512
513 // is abstract?
514 if (Modifier.isAbstract(c.getModifiers())) {
515 return null;
516 }
517
518 // is there exactly one constructor?
519 if (c.getDeclaredConstructors().length != 1) {
520 return null;
521 }
522 CtConstructor constructor = c.getDeclaredConstructors()[0];
523
524 // is this constructor called exactly once?
525 ConstructorEntry constructorEntry = new ConstructorEntry(
526 innerClassEntry,
527 constructor.getMethodInfo().getDescriptor()
528 );
529 Collection<EntryReference<BehaviorEntry,BehaviorEntry>> references = getBehaviorReferences(constructorEntry);
530 if (references.size() != 1) {
531 return null;
532 }
533
534 // does the caller use this type?
535 BehaviorEntry caller = references.iterator().next().context;
536 for (FieldEntry fieldEntry : getReferencedFields(caller)) {
537 ClassEntry fieldClass = getFieldClass(fieldEntry);
538 if (fieldClass != null && fieldClass.equals(innerClassEntry)) {
539 // caller references this type, so it can't be anonymous
540 return null;
541 }
542 }
543 for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) {
544 // get the class types from the signature
545 for (String className : SignatureUpdater.getClasses(behaviorEntry.getSignature())) {
546 if (className.equals(innerClassEntry.getName())) {
547 // caller references this type, so it can't be anonymous
548 return null;
549 }
550 }
551 }
552
553 return caller;
554 }
555
556 public Set<ClassEntry> getObfClassEntries() {
557 return m_obfClassEntries;
558 }
559
560 public TranslationIndex getTranslationIndex() {
561 return m_translationIndex;
562 }
563
564 public Access getAccess(Entry entry) {
565 return m_access.get(entry);
566 }
567
568 public ClassEntry getFieldClass(FieldEntry fieldEntry) {
569 return m_fieldClasses.get(fieldEntry);
570 }
571
572 public ClassInheritanceTreeNode getClassInheritance(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
573
574 // get the root node
575 List<String> ancestry = Lists.newArrayList();
576 ancestry.add(obfClassEntry.getName());
577 for (ClassEntry classEntry : m_translationIndex.getAncestry(obfClassEntry)) {
578 ancestry.add(classEntry.getName());
579 }
580 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(
581 deobfuscatingTranslator,
582 ancestry.get(ancestry.size() - 1)
583 );
584
585 // expand all children recursively
586 rootNode.load(m_translationIndex, true);
587
588 return rootNode;
589 }
590
591 public ClassImplementationsTreeNode getClassImplementations(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
592
593 // is this even an interface?
594 if (isInterface(obfClassEntry.getClassName())) {
595 ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(deobfuscatingTranslator, obfClassEntry);
596 node.load(this);
597 return node;
598 }
599 return null;
600 }
601
602 public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
603
604 // travel to the ancestor implementation
605 ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry();
606 for (ClassEntry ancestorClassEntry : m_translationIndex.getAncestry(obfMethodEntry.getClassEntry())) {
607 MethodEntry ancestorMethodEntry = new MethodEntry(
608 new ClassEntry(ancestorClassEntry),
609 obfMethodEntry.getName(),
610 obfMethodEntry.getSignature()
611 );
612 if (containsObfBehavior(ancestorMethodEntry)) {
613 baseImplementationClassEntry = ancestorClassEntry;
614 }
615 }
616
617 // make a root node at the base
618 MethodEntry methodEntry = new MethodEntry(
619 baseImplementationClassEntry,
620 obfMethodEntry.getName(),
621 obfMethodEntry.getSignature()
622 );
623 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode(
624 deobfuscatingTranslator,
625 methodEntry,
626 containsObfBehavior(methodEntry)
627 );
628
629 // expand the full tree
630 rootNode.load(this, true);
631
632 return rootNode;
633 }
634
635 public MethodImplementationsTreeNode getMethodImplementations(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
636
637 MethodEntry interfaceMethodEntry;
638
639 // is this method on an interface?
640 if (isInterface(obfMethodEntry.getClassName())) {
641 interfaceMethodEntry = obfMethodEntry;
642 } else {
643 // get the interface class
644 List<MethodEntry> methodInterfaces = Lists.newArrayList();
645 for (String interfaceName : getInterfaces(obfMethodEntry.getClassName())) {
646 // is this method defined in this interface?
647 MethodEntry methodInterface = new MethodEntry(
648 new ClassEntry(interfaceName),
649 obfMethodEntry.getName(),
650 obfMethodEntry.getSignature()
651 );
652 if (containsObfBehavior(methodInterface)) {
653 methodInterfaces.add(methodInterface);
654 }
655 }
656 if (methodInterfaces.isEmpty()) {
657 return null;
658 }
659 if (methodInterfaces.size() > 1) {
660 throw new Error("Too many interfaces define this method! This is not yet supported by Enigma!");
661 }
662 interfaceMethodEntry = methodInterfaces.get(0);
663 }
664
665 MethodImplementationsTreeNode rootNode = new MethodImplementationsTreeNode(deobfuscatingTranslator, interfaceMethodEntry);
666 rootNode.load(this);
667 return rootNode;
668 }
669
670 public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) {
671 Set<MethodEntry> methodEntries = Sets.newHashSet();
672 getRelatedMethodImplementations(methodEntries, getMethodInheritance(null, obfMethodEntry));
673 return methodEntries;
674 }
675
676 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) {
677 MethodEntry methodEntry = node.getMethodEntry();
678 if (containsObfBehavior(methodEntry)) {
679 // collect the entry
680 methodEntries.add(methodEntry);
681 }
682
683 // look at interface methods too
684 MethodImplementationsTreeNode implementations = getMethodImplementations(null, methodEntry);
685 if (implementations != null) {
686 getRelatedMethodImplementations(methodEntries, implementations);
687 }
688
689 // recurse
690 for (int i = 0; i < node.getChildCount(); i++) {
691 getRelatedMethodImplementations(methodEntries, (MethodInheritanceTreeNode)node.getChildAt(i));
692 }
693 }
694
695 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) {
696 MethodEntry methodEntry = node.getMethodEntry();
697 if (containsObfBehavior(methodEntry)) {
698 // collect the entry
699 methodEntries.add(methodEntry);
700 }
701
702 // recurse
703 for (int i = 0; i < node.getChildCount(); i++) {
704 getRelatedMethodImplementations(methodEntries, (MethodImplementationsTreeNode)node.getChildAt(i));
705 }
706 }
707
708 public Collection<EntryReference<FieldEntry,BehaviorEntry>> getFieldReferences(FieldEntry fieldEntry) {
709 return m_fieldReferences.get(fieldEntry);
710 }
711
712 public Collection<FieldEntry> getReferencedFields(BehaviorEntry behaviorEntry) {
713 // linear search is fast enough for now
714 Set<FieldEntry> fieldEntries = Sets.newHashSet();
715 for (EntryReference<FieldEntry,BehaviorEntry> reference : m_fieldReferences.values()) {
716 if (reference.context == behaviorEntry) {
717 fieldEntries.add(reference.entry);
718 }
719 }
720 return fieldEntries;
721 }
722
723 public Collection<EntryReference<BehaviorEntry,BehaviorEntry>> getBehaviorReferences(BehaviorEntry behaviorEntry) {
724 return m_behaviorReferences.get(behaviorEntry);
725 }
726
727 public Collection<BehaviorEntry> getReferencedBehaviors(BehaviorEntry behaviorEntry) {
728 // linear search is fast enough for now
729 Set<BehaviorEntry> behaviorEntries = Sets.newHashSet();
730 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : m_behaviorReferences.values()) {
731 if (reference.context == behaviorEntry) {
732 behaviorEntries.add(reference.entry);
733 }
734 }
735 return behaviorEntries;
736 }
737
738 public Collection<String> getInnerClasses(String obfOuterClassName) {
739 return m_innerClasses.get(obfOuterClassName);
740 }
741
742 public String getOuterClass(String obfInnerClassName) {
743 // make sure we use the right name
744 if (new ClassEntry(obfInnerClassName).getPackageName() != null) {
745 throw new IllegalArgumentException("Don't reference obfuscated inner classes using packages: " + obfInnerClassName);
746 }
747 return m_outerClasses.get(obfInnerClassName);
748 }
749
750 public boolean isAnonymousClass(String obfInnerClassName) {
751 return m_anonymousClasses.containsKey(obfInnerClassName);
752 }
753
754 public BehaviorEntry getAnonymousClassCaller(String obfInnerClassName) {
755 return m_anonymousClasses.get(obfInnerClassName);
756 }
757
758 public Set<String> getInterfaces(String className) {
759 Set<String> interfaceNames = new HashSet<String>();
760 interfaceNames.addAll(m_interfaces.get(className));
761 for (ClassEntry ancestor : m_translationIndex.getAncestry(new ClassEntry(className))) {
762 interfaceNames.addAll(m_interfaces.get(ancestor.getName()));
763 }
764 return interfaceNames;
765 }
766
767 public Set<String> getImplementingClasses(String targetInterfaceName) {
768 // linear search is fast enough for now
769 Set<String> classNames = Sets.newHashSet();
770 for (Map.Entry<String,String> entry : m_interfaces.entries()) {
771 String className = entry.getKey();
772 String interfaceName = entry.getValue();
773 if (interfaceName.equals(targetInterfaceName)) {
774 classNames.add(className);
775 m_translationIndex.getSubclassNamesRecursively(classNames, new ClassEntry(className));
776 }
777 }
778 return classNames;
779 }
780
781 public boolean isInterface(String className) {
782 return m_interfaces.containsValue(className);
783 }
784
785 public MethodEntry getBridgeMethod(MethodEntry methodEntry) {
786 return m_bridgeMethods.get(methodEntry);
787 }
788
789 public boolean containsObfClass(ClassEntry obfClassEntry) {
790 return m_obfClassEntries.contains(obfClassEntry);
791 }
792
793 public boolean containsObfField(FieldEntry obfFieldEntry) {
794 return m_access.containsKey(obfFieldEntry);
795 }
796
797 public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) {
798 return m_access.containsKey(obfBehaviorEntry);
799 }
800
801 public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) {
802 // check the behavior
803 if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) {
804 return false;
805 }
806
807 // check the argument
808 if (obfArgumentEntry.getIndex() >= Descriptor.numOfParameters(obfArgumentEntry.getBehaviorEntry().getSignature())) {
809 return false;
810 }
811
812 return true;
813 }
814
815 public boolean containsObfEntry(Entry obfEntry) {
816 if (obfEntry instanceof ClassEntry) {
817 return containsObfClass((ClassEntry)obfEntry);
818 } else if (obfEntry instanceof FieldEntry) {
819 return containsObfField((FieldEntry)obfEntry);
820 } else if (obfEntry instanceof BehaviorEntry) {
821 return containsObfBehavior((BehaviorEntry)obfEntry);
822 } else if (obfEntry instanceof ArgumentEntry) {
823 return containsObfArgument((ArgumentEntry)obfEntry);
824 } else {
825 throw new Error("Entry type not supported: " + obfEntry.getClass().getName());
826 }
827 }
828}
diff --git a/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java
new file mode 100644
index 0000000..1009226
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java
@@ -0,0 +1,100 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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 // get all method implementations
67 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
68 for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) {
69 MethodEntry methodEntry = new MethodEntry(
70 new ClassEntry(implementingClassName),
71 m_entry.getName(),
72 m_entry.getSignature()
73 );
74 if (index.containsObfBehavior(methodEntry)) {
75 nodes.add(new MethodImplementationsTreeNode(m_deobfuscatingTranslator, methodEntry));
76 }
77 }
78
79 // add them to this node
80 for (MethodImplementationsTreeNode node : nodes) {
81 this.add(node);
82 }
83 }
84
85 public static MethodImplementationsTreeNode findNode(MethodImplementationsTreeNode node, MethodEntry entry) {
86 // is this the node?
87 if (node.getMethodEntry().equals(entry)) {
88 return node;
89 }
90
91 // recurse
92 for (int i = 0; i < node.getChildCount(); i++) {
93 MethodImplementationsTreeNode foundNode = findNode((MethodImplementationsTreeNode)node.getChildAt(i), entry);
94 if (foundNode != null) {
95 return foundNode;
96 }
97 }
98 return null;
99 }
100}
diff --git a/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java
new file mode 100644
index 0000000..8718220
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java
@@ -0,0 +1,114 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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/ReferenceTreeNode.java b/src/cuchaz/enigma/analysis/ReferenceTreeNode.java
new file mode 100644
index 0000000..2b08616
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/ReferenceTreeNode.java
@@ -0,0 +1,18 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import cuchaz.enigma.mapping.Entry;
14
15public interface ReferenceTreeNode<E extends Entry,C extends Entry> {
16 E getEntry();
17 EntryReference<E,C> getReference();
18}
diff --git a/src/cuchaz/enigma/analysis/SourceIndex.java b/src/cuchaz/enigma/analysis/SourceIndex.java
new file mode 100644
index 0000000..b43ab61
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/SourceIndex.java
@@ -0,0 +1,173 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
36 public SourceIndex(String source) {
37 m_source = source;
38 m_tokenToReference = Maps.newTreeMap();
39 m_referenceToTokens = HashMultimap.create();
40 m_declarationToToken = Maps.newHashMap();
41 m_lineOffsets = Lists.newArrayList();
42
43 // count the lines
44 m_lineOffsets.add(0);
45 for (int i = 0; i < source.length(); i++) {
46 if (source.charAt(i) == '\n') {
47 m_lineOffsets.add(i + 1);
48 }
49 }
50 }
51
52 public String getSource() {
53 return m_source;
54 }
55
56 public Token getToken(AstNode node) {
57
58 // get the text of the node
59 String name = "";
60 if (node instanceof Identifier) {
61 name = ((Identifier)node).getName();
62 }
63
64 // get a token for this node's region
65 Region region = node.getRegion();
66 if (region.getBeginLine() == 0 || region.getEndLine() == 0) {
67 // DEBUG
68 System.err.println(String.format("WARNING: %s \"%s\" has invalid region: %s", node.getNodeType(), name, region));
69 return null;
70 }
71 Token token = new Token(
72 toPos(region.getBeginLine(), region.getBeginColumn()),
73 toPos(region.getEndLine(), region.getEndColumn()),
74 m_source
75 );
76 if (token.start == 0) {
77 // DEBUG
78 System.err.println(String.format("WARNING: %s \"%s\" has invalid start: %s", node.getNodeType(), name, region));
79 return null;
80 }
81
82 // DEBUG
83 // System.out.println( String.format( "%s \"%s\" region: %s", node.getNodeType(), name, region ) );
84
85 // for tokens representing inner classes, make sure we only get the simple name
86 int pos = name.lastIndexOf('$');
87 if (pos >= 0) {
88 token.end -= pos + 1;
89 }
90
91 return token;
92 }
93
94 public void addReference(AstNode node, Entry deobfEntry, Entry deobfContext) {
95 Token token = getToken(node);
96 if (token != null) {
97 EntryReference<Entry,Entry> deobfReference = new EntryReference<Entry,Entry>(deobfEntry, token.text, deobfContext);
98 m_tokenToReference.put(token, deobfReference);
99 m_referenceToTokens.put(deobfReference, token);
100 }
101 }
102
103 public void addDeclaration(AstNode node, Entry deobfEntry) {
104 Token token = getToken(node);
105 if (token != null) {
106 EntryReference<Entry,Entry> reference = new EntryReference<Entry,Entry>(deobfEntry, token.text);
107 m_tokenToReference.put(token, reference);
108 m_referenceToTokens.put(reference, token);
109 m_declarationToToken.put(deobfEntry, token);
110 }
111 }
112
113 public Token getReferenceToken(int pos) {
114 Token token = m_tokenToReference.floorKey(new Token(pos, pos, null));
115 if (token != null && token.contains(pos)) {
116 return token;
117 }
118 return null;
119 }
120
121 public Collection<Token> getReferenceTokens(EntryReference<Entry,Entry> deobfReference) {
122 return m_referenceToTokens.get(deobfReference);
123 }
124
125 public EntryReference<Entry,Entry> getDeobfReference(Token token) {
126 if (token == null) {
127 return null;
128 }
129 return m_tokenToReference.get(token);
130 }
131
132 public void replaceDeobfReference(Token token, EntryReference<Entry,Entry> newDeobfReference) {
133 EntryReference<Entry,Entry> oldDeobfReference = m_tokenToReference.get(token);
134 m_tokenToReference.put(token, newDeobfReference);
135 Collection<Token> tokens = m_referenceToTokens.get(oldDeobfReference);
136 m_referenceToTokens.removeAll(oldDeobfReference);
137 m_referenceToTokens.putAll(newDeobfReference, tokens);
138 }
139
140 public Iterable<Token> referenceTokens() {
141 return m_tokenToReference.keySet();
142 }
143
144 public Iterable<Token> declarationTokens() {
145 return m_declarationToToken.values();
146 }
147
148 public Token getDeclarationToken(Entry deobfEntry) {
149 return m_declarationToToken.get(deobfEntry);
150 }
151
152 public int getLineNumber(int pos) {
153 // line number is 1-based
154 int line = 0;
155 for (Integer offset : m_lineOffsets) {
156 if (offset > pos) {
157 break;
158 }
159 line++;
160 }
161 return line;
162 }
163
164 public int getColumnNumber(int pos) {
165 // column number is 1-based
166 return pos - m_lineOffsets.get(getLineNumber(pos) - 1) + 1;
167 }
168
169 private int toPos(int line, int col) {
170 // line and col are 1-based
171 return m_lineOffsets.get(line - 1) + col - 1;
172 }
173}
diff --git a/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java
new file mode 100644
index 0000000..43c1749
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java
@@ -0,0 +1,163 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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.ConstructorDeclaration;
21import com.strobel.decompiler.languages.java.ast.IdentifierExpression;
22import com.strobel.decompiler.languages.java.ast.InvocationExpression;
23import com.strobel.decompiler.languages.java.ast.Keys;
24import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
25import com.strobel.decompiler.languages.java.ast.MethodDeclaration;
26import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression;
27import com.strobel.decompiler.languages.java.ast.ParameterDeclaration;
28import com.strobel.decompiler.languages.java.ast.SimpleType;
29import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression;
30import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression;
31
32import cuchaz.enigma.mapping.ArgumentEntry;
33import cuchaz.enigma.mapping.BehaviorEntry;
34import cuchaz.enigma.mapping.ClassEntry;
35import cuchaz.enigma.mapping.ConstructorEntry;
36import cuchaz.enigma.mapping.FieldEntry;
37import cuchaz.enigma.mapping.MethodEntry;
38
39public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
40
41 private BehaviorEntry m_behaviorEntry;
42
43 public SourceIndexBehaviorVisitor(BehaviorEntry behaviorEntry) {
44 m_behaviorEntry = behaviorEntry;
45 }
46
47 @Override
48 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
49 return recurse(node, index);
50 }
51
52 @Override
53 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
54 return recurse(node, index);
55 }
56
57 @Override
58 public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) {
59 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
60
61 // get the behavior entry
62 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
63 BehaviorEntry behaviorEntry = null;
64 if (ref instanceof MethodReference) {
65 MethodReference methodRef = (MethodReference)ref;
66 if (methodRef.isConstructor()) {
67 behaviorEntry = new ConstructorEntry(classEntry, ref.getSignature());
68 } else if (methodRef.isTypeInitializer()) {
69 behaviorEntry = new ConstructorEntry(classEntry);
70 } else {
71 behaviorEntry = new MethodEntry(classEntry, ref.getName(), ref.getSignature());
72 }
73 }
74 if (behaviorEntry != null) {
75 // get the node for the token
76 AstNode tokenNode = null;
77 if (node.getTarget() instanceof MemberReferenceExpression) {
78 tokenNode = ((MemberReferenceExpression)node.getTarget()).getMemberNameToken();
79 } else if (node.getTarget() instanceof SuperReferenceExpression) {
80 tokenNode = node.getTarget();
81 } else if (node.getTarget() instanceof ThisReferenceExpression) {
82 tokenNode = node.getTarget();
83 }
84 if (tokenNode != null) {
85 index.addReference(tokenNode, behaviorEntry, m_behaviorEntry);
86 }
87 }
88
89 return recurse(node, index);
90 }
91
92 @Override
93 public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) {
94 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
95 if (ref != null) {
96 // make sure this is actually a field
97 if (ref.getSignature().indexOf('(') >= 0) {
98 throw new Error("Expected a field here! got " + ref);
99 }
100
101 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
102 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName());
103 index.addReference(node.getMemberNameToken(), fieldEntry, m_behaviorEntry);
104 }
105
106 return recurse(node, index);
107 }
108
109 @Override
110 public Void visitSimpleType(SimpleType node, SourceIndex index) {
111 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
112 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
113 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
114 index.addReference(node.getIdentifierToken(), classEntry, m_behaviorEntry);
115 }
116
117 return recurse(node, index);
118 }
119
120 @Override
121 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
122 ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION);
123 ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName());
124 MethodDefinition methodDef = (MethodDefinition)def.getMethod();
125 BehaviorEntry behaviorEntry;
126 if (methodDef.isConstructor()) {
127 behaviorEntry = new ConstructorEntry(classEntry, methodDef.getSignature());
128 } else {
129 behaviorEntry = new MethodEntry(classEntry, methodDef.getName(), methodDef.getSignature());
130 }
131 ArgumentEntry argumentEntry = new ArgumentEntry(behaviorEntry, def.getPosition(), node.getName());
132 index.addDeclaration(node.getNameToken(), argumentEntry);
133
134 return recurse(node, index);
135 }
136
137 @Override
138 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
139 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
140 if (ref != null) {
141 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
142 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName());
143 index.addReference(node.getIdentifierToken(), fieldEntry, m_behaviorEntry);
144 }
145
146 return recurse(node, index);
147 }
148
149 @Override
150 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
151 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
152 if (ref != null) {
153 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
154 ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, ref.getSignature());
155 if (node.getType() instanceof SimpleType) {
156 SimpleType simpleTypeNode = (SimpleType)node.getType();
157 index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, m_behaviorEntry);
158 }
159 }
160
161 return recurse(node, index);
162 }
163}
diff --git a/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
new file mode 100644
index 0000000..7b902a9
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
@@ -0,0 +1,114 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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.BehaviorEntryFactory;
30import cuchaz.enigma.mapping.ClassEntry;
31import cuchaz.enigma.mapping.ConstructorEntry;
32import cuchaz.enigma.mapping.FieldEntry;
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 ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName());
71 BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(classEntry, def.getName(), def.getSignature());
72 AstNode tokenNode = node.getNameToken();
73 if (behaviorEntry instanceof ConstructorEntry) {
74 ConstructorEntry constructorEntry = (ConstructorEntry)behaviorEntry;
75 if (constructorEntry.isStatic()) {
76 tokenNode = node.getModifiers().firstOrNullObject();
77 }
78 }
79 index.addDeclaration(tokenNode, behaviorEntry);
80 return node.acceptVisitor(new SourceIndexBehaviorVisitor(behaviorEntry), index);
81 }
82
83 @Override
84 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
85 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
86 ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName());
87 ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, def.getSignature());
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 ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName());
96 FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName());
97 assert (node.getVariables().size() == 1);
98 VariableInitializer variable = node.getVariables().firstOrNullObject();
99 index.addDeclaration(variable.getNameToken(), fieldEntry);
100
101 return recurse(node, index);
102 }
103
104 @Override
105 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
106 // treat enum declarations as field declarations
107 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
108 ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName());
109 FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName());
110 index.addDeclaration(node.getNameToken(), fieldEntry);
111
112 return recurse(node, index);
113 }
114}
diff --git a/src/cuchaz/enigma/analysis/SourceIndexVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexVisitor.java
new file mode 100644
index 0000000..0d5bdc0
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/SourceIndexVisitor.java
@@ -0,0 +1,452 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..481d2f4
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/Token.java
@@ -0,0 +1,56 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..7597c3a
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/TranslationIndex.java
@@ -0,0 +1,227 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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.HashMap;
20import java.util.List;
21import java.util.Map;
22import java.util.Set;
23import java.util.zip.GZIPInputStream;
24import java.util.zip.GZIPOutputStream;
25
26import javassist.CtBehavior;
27import javassist.CtClass;
28import javassist.CtField;
29
30import com.google.common.collect.HashMultimap;
31import com.google.common.collect.Lists;
32import com.google.common.collect.Maps;
33import com.google.common.collect.Multimap;
34
35import cuchaz.enigma.mapping.ArgumentEntry;
36import cuchaz.enigma.mapping.BehaviorEntry;
37import cuchaz.enigma.mapping.ClassEntry;
38import cuchaz.enigma.mapping.Entry;
39import cuchaz.enigma.mapping.FieldEntry;
40import cuchaz.enigma.mapping.JavassistUtil;
41import cuchaz.enigma.mapping.Translator;
42
43public class TranslationIndex implements Serializable {
44
45 private static final long serialVersionUID = 738687982126844179L;
46
47 private Map<ClassEntry,ClassEntry> m_superclasses;
48 private Multimap<ClassEntry,FieldEntry> m_fieldEntries;
49 private Multimap<ClassEntry,BehaviorEntry> m_behaviorEntries;
50
51 public TranslationIndex() {
52 m_superclasses = Maps.newHashMap();
53 m_fieldEntries = HashMultimap.create();
54 m_behaviorEntries = HashMultimap.create();
55 }
56
57 public TranslationIndex(TranslationIndex other, Translator translator) {
58
59 // translate the superclasses
60 m_superclasses = Maps.newHashMap();
61 for (Map.Entry<ClassEntry,ClassEntry> mapEntry : other.m_superclasses.entrySet()) {
62 m_superclasses.put(
63 translator.translateEntry(mapEntry.getKey()),
64 translator.translateEntry(mapEntry.getValue())
65 );
66 }
67
68 // translate the fields
69 m_fieldEntries = HashMultimap.create();
70 for (Map.Entry<ClassEntry,FieldEntry> mapEntry : other.m_fieldEntries.entries()) {
71 m_fieldEntries.put(
72 translator.translateEntry(mapEntry.getKey()),
73 translator.translateEntry(mapEntry.getValue())
74 );
75 }
76
77 m_behaviorEntries = HashMultimap.create();
78 for (Map.Entry<ClassEntry,BehaviorEntry> mapEntry : other.m_behaviorEntries.entries()) {
79 m_behaviorEntries.put(
80 translator.translateEntry(mapEntry.getKey()),
81 translator.translateEntry(mapEntry.getValue())
82 );
83 }
84 }
85
86 public void indexClass(CtClass c) {
87
88 ClassEntry classEntry = JavassistUtil.getClassEntry(c);
89
90 // add the superclass
91 ClassEntry superclassEntry = JavassistUtil.getSuperclassEntry(c);
92 if (!isJre(classEntry) && superclassEntry != null && !isJre(superclassEntry)) {
93 m_superclasses.put(classEntry, superclassEntry);
94 }
95
96 // add fields
97 for (CtField field : c.getDeclaredFields()) {
98 FieldEntry fieldEntry = JavassistUtil.getFieldEntry(field);
99 m_fieldEntries.put(fieldEntry.getClassEntry(), fieldEntry);
100 }
101
102 // add behaviors
103 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
104 BehaviorEntry behaviorEntry = JavassistUtil.getBehaviorEntry(behavior);
105 m_behaviorEntries.put(behaviorEntry.getClassEntry(), behaviorEntry);
106 }
107 }
108
109 public void renameClasses(Map<String,String> renames) {
110 EntryRenamer.renameClassesInMap(renames, m_superclasses);
111 EntryRenamer.renameClassesInMultimap(renames, m_fieldEntries);
112 EntryRenamer.renameClassesInMultimap(renames, m_behaviorEntries);
113 }
114
115 public ClassEntry getSuperclass(ClassEntry classEntry) {
116 return m_superclasses.get(classEntry);
117 }
118
119 public List<ClassEntry> getAncestry(ClassEntry classEntry) {
120 List<ClassEntry> ancestors = Lists.newArrayList();
121 while (classEntry != null) {
122 classEntry = getSuperclass(classEntry);
123 if (classEntry != null) {
124 ancestors.add(classEntry);
125 }
126 }
127 return ancestors;
128 }
129
130 public List<ClassEntry> getSubclass(ClassEntry classEntry) {
131 // linear search is fast enough for now
132 List<ClassEntry> subclasses = Lists.newArrayList();
133 for (Map.Entry<ClassEntry,ClassEntry> entry : m_superclasses.entrySet()) {
134 ClassEntry subclass = entry.getKey();
135 ClassEntry superclass = entry.getValue();
136 if (classEntry.equals(superclass)) {
137 subclasses.add(subclass);
138 }
139 }
140 return subclasses;
141 }
142
143 public void getSubclassesRecursively(Set<ClassEntry> out, ClassEntry classEntry) {
144 for (ClassEntry subclassEntry : getSubclass(classEntry)) {
145 out.add(subclassEntry);
146 getSubclassesRecursively(out, subclassEntry);
147 }
148 }
149
150 public void getSubclassNamesRecursively(Set<String> out, ClassEntry classEntry) {
151 for (ClassEntry subclassEntry : getSubclass(classEntry)) {
152 out.add(subclassEntry.getName());
153 getSubclassNamesRecursively(out, subclassEntry);
154 }
155 }
156
157 public boolean entryExists(Entry entry) {
158 if (entry instanceof FieldEntry) {
159 return fieldExists((FieldEntry)entry);
160 } else if (entry instanceof BehaviorEntry) {
161 return behaviorExists((BehaviorEntry)entry);
162 } else if (entry instanceof ArgumentEntry) {
163 return behaviorExists(((ArgumentEntry)entry).getBehaviorEntry());
164 }
165 throw new IllegalArgumentException("Cannot check existence for " + entry.getClass());
166 }
167
168 public boolean fieldExists(FieldEntry fieldEntry) {
169 return m_fieldEntries.containsEntry(fieldEntry.getClassEntry(), fieldEntry);
170 }
171
172 public boolean behaviorExists(BehaviorEntry behaviorEntry) {
173 return m_behaviorEntries.containsEntry(behaviorEntry.getClassEntry(), behaviorEntry);
174 }
175
176 public ClassEntry resolveEntryClass(Entry entry) {
177
178 if (entry instanceof ClassEntry) {
179 return (ClassEntry)entry;
180 }
181
182 // this entry could refer to a method on a class where the method is not actually implemented
183 // travel up the inheritance tree to find the closest implementation
184 while (!entryExists(entry)) {
185
186 // is there a parent class?
187 ClassEntry superclassEntry = getSuperclass(entry.getClassEntry());
188 if (superclassEntry == null) {
189 // this is probably a method from a class in a library
190 // we can't trace the implementation up any higher unless we index the library
191 return null;
192 }
193
194 // move up to the parent class
195 entry = entry.cloneToNewClass(superclassEntry);
196 }
197 return entry.getClassEntry();
198 }
199
200 private boolean isJre(ClassEntry classEntry) {
201 String packageName = classEntry.getPackageName();
202 return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax"));
203 }
204
205 public void write(OutputStream out)
206 throws IOException {
207 GZIPOutputStream gzipout = new GZIPOutputStream(out);
208 ObjectOutputStream oout = new ObjectOutputStream(gzipout);
209 oout.writeObject(m_superclasses);
210 oout.writeObject(m_fieldEntries);
211 oout.writeObject(m_behaviorEntries);
212 gzipout.finish();
213 }
214
215 @SuppressWarnings("unchecked")
216 public void read(InputStream in)
217 throws IOException {
218 try {
219 ObjectInputStream oin = new ObjectInputStream(new GZIPInputStream(in));
220 m_superclasses = (HashMap<ClassEntry,ClassEntry>)oin.readObject();
221 m_fieldEntries = (HashMultimap<ClassEntry,FieldEntry>)oin.readObject();
222 m_behaviorEntries = (HashMultimap<ClassEntry,BehaviorEntry>)oin.readObject();
223 } catch (ClassNotFoundException ex) {
224 throw new Error(ex);
225 }
226 }
227}
diff --git a/src/cuchaz/enigma/analysis/TreeDumpVisitor.java b/src/cuchaz/enigma/analysis/TreeDumpVisitor.java
new file mode 100644
index 0000000..23f8089
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/TreeDumpVisitor.java
@@ -0,0 +1,512 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..b6efbd4
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/CheckCastIterator.java
@@ -0,0 +1,126 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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;
24
25public class CheckCastIterator implements Iterator<CheckCast> {
26
27 public static class CheckCast {
28
29 public String className;
30 public MethodEntry prevMethodEntry;
31
32 public CheckCast(String className, MethodEntry prevMethodEntry) {
33 this.className = className;
34 this.prevMethodEntry = prevMethodEntry;
35 }
36 }
37
38 private ConstPool m_constants;
39 private CodeAttribute m_attribute;
40 private CodeIterator m_iter;
41 private CheckCast m_next;
42
43 public CheckCastIterator(CodeAttribute codeAttribute) throws BadBytecode {
44 m_constants = codeAttribute.getConstPool();
45 m_attribute = codeAttribute;
46 m_iter = m_attribute.iterator();
47
48 m_next = getNext();
49 }
50
51 @Override
52 public boolean hasNext() {
53 return m_next != null;
54 }
55
56 @Override
57 public CheckCast next() {
58 CheckCast out = m_next;
59 try {
60 m_next = getNext();
61 } catch (BadBytecode ex) {
62 throw new Error(ex);
63 }
64 return out;
65 }
66
67 @Override
68 public void remove() {
69 throw new UnsupportedOperationException();
70 }
71
72 private CheckCast getNext() throws BadBytecode {
73 int prevPos = 0;
74 while (m_iter.hasNext()) {
75 int pos = m_iter.next();
76 int opcode = m_iter.byteAt(pos);
77 switch (opcode) {
78 case Opcode.CHECKCAST:
79
80 // get the type of this op code (next two bytes are a classinfo index)
81 MethodEntry prevMethodEntry = getMethodEntry(prevPos);
82 if (prevMethodEntry != null) {
83 return new CheckCast(m_constants.getClassInfo(m_iter.s16bitAt(pos + 1)), prevMethodEntry);
84 }
85 break;
86 }
87 prevPos = pos;
88 }
89 return null;
90 }
91
92 private MethodEntry getMethodEntry(int pos) {
93 switch (m_iter.byteAt(pos)) {
94 case Opcode.INVOKEVIRTUAL:
95 case Opcode.INVOKESTATIC:
96 case Opcode.INVOKEDYNAMIC:
97 case Opcode.INVOKESPECIAL: {
98 int index = m_iter.s16bitAt(pos + 1);
99 return new MethodEntry(
100 new ClassEntry(Descriptor.toJvmName(m_constants.getMethodrefClassName(index))),
101 m_constants.getMethodrefName(index),
102 m_constants.getMethodrefType(index)
103 );
104 }
105
106 case Opcode.INVOKEINTERFACE: {
107 int index = m_iter.s16bitAt(pos + 1);
108 return new MethodEntry(
109 new ClassEntry(Descriptor.toJvmName(m_constants.getInterfaceMethodrefClassName(index))),
110 m_constants.getInterfaceMethodrefName(index),
111 m_constants.getInterfaceMethodrefType(index)
112 );
113 }
114 }
115 return null;
116 }
117
118 public Iterable<CheckCast> casts() {
119 return new Iterable<CheckCast>() {
120 @Override
121 public Iterator<CheckCast> iterator() {
122 return CheckCastIterator.this;
123 }
124 };
125 }
126}
diff --git a/src/cuchaz/enigma/bytecode/ClassRenamer.java b/src/cuchaz/enigma/bytecode/ClassRenamer.java
new file mode 100644
index 0000000..a5fea92
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/ClassRenamer.java
@@ -0,0 +1,110 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.Map;
14import java.util.Set;
15
16import javassist.ClassMap;
17import javassist.CtClass;
18import javassist.bytecode.ConstPool;
19import javassist.bytecode.Descriptor;
20import javassist.bytecode.InnerClassesAttribute;
21
22import com.google.common.collect.Maps;
23import com.google.common.collect.Sets;
24
25import cuchaz.enigma.mapping.ClassEntry;
26
27public class ClassRenamer {
28
29 public static void renameClasses(CtClass c, Map<ClassEntry,ClassEntry> map) {
30
31 // build the map used by javassist
32 ClassMap nameMap = new ClassMap();
33 for (Map.Entry<ClassEntry,ClassEntry> entry : map.entrySet()) {
34 nameMap.put(entry.getKey().getName(), entry.getValue().getName());
35 }
36
37 c.replaceClassName(nameMap);
38
39 // replace simple names in the InnerClasses attribute too
40 ConstPool constants = c.getClassFile().getConstPool();
41 InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
42 if (attr != null) {
43 for (int i = 0; i < attr.tableLength(); i++) {
44 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(attr.innerClass(i)));
45 if (attr.innerNameIndex(i) != 0) {
46 attr.setInnerNameIndex(i, constants.addUtf8Info(classEntry.getInnerClassName()));
47 }
48
49 /* DEBUG
50 System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i)));
51 */
52 }
53 }
54 }
55
56 public static Set<ClassEntry> getAllClassEntries(final CtClass c) {
57
58 // get the classes that javassist knows about
59 final Set<ClassEntry> entries = Sets.newHashSet();
60 ClassMap map = new ClassMap() {
61 @Override
62 public Object get(Object obj) {
63 if (obj instanceof String) {
64 String str = (String)obj;
65
66 // javassist throws a lot of weird things at this map
67 // I either have to implement my on class scanner, or just try to filter out the weirdness
68 // I'm opting to filter out the weirdness for now
69
70 // skip anything with generic arguments
71 if (str.indexOf('<') >= 0 || str.indexOf('>') >= 0 || str.indexOf(';') >= 0) {
72 return null;
73 }
74
75 // convert path/to/class.inner to path/to/class$inner
76 str = str.replace('.', '$');
77
78 // remember everything else
79 entries.add(new ClassEntry(str));
80 }
81 return null;
82 }
83
84 private static final long serialVersionUID = -202160293602070641L;
85 };
86 c.replaceClassName(map);
87
88 return entries;
89 }
90
91 public static void moveAllClassesOutOfDefaultPackage(CtClass c, String newPackageName) {
92 Map<ClassEntry,ClassEntry> map = Maps.newHashMap();
93 for (ClassEntry classEntry : ClassRenamer.getAllClassEntries(c)) {
94 if (classEntry.isInDefaultPackage()) {
95 map.put(classEntry, new ClassEntry(newPackageName + "/" + classEntry.getName()));
96 }
97 }
98 ClassRenamer.renameClasses(c, map);
99 }
100
101 public static void moveAllClassesIntoDefaultPackage(CtClass c, String oldPackageName) {
102 Map<ClassEntry,ClassEntry> map = Maps.newHashMap();
103 for (ClassEntry classEntry : ClassRenamer.getAllClassEntries(c)) {
104 if (classEntry.getPackageName().equals(oldPackageName)) {
105 map.put(classEntry, new ClassEntry(classEntry.getSimpleName()));
106 }
107 }
108 ClassRenamer.renameClasses(c, map);
109 }
110}
diff --git a/src/cuchaz/enigma/bytecode/ClassTranslator.java b/src/cuchaz/enigma/bytecode/ClassTranslator.java
new file mode 100644
index 0000000..fb2fb27
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/ClassTranslator.java
@@ -0,0 +1,141 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.Map;
14
15import javassist.CtBehavior;
16import javassist.CtClass;
17import javassist.CtField;
18import javassist.CtMethod;
19import javassist.bytecode.ConstPool;
20import javassist.bytecode.Descriptor;
21import javassist.bytecode.SourceFileAttribute;
22
23import com.google.common.collect.Maps;
24
25import cuchaz.enigma.mapping.BehaviorEntry;
26import cuchaz.enigma.mapping.BehaviorEntryFactory;
27import cuchaz.enigma.mapping.ClassEntry;
28import cuchaz.enigma.mapping.FieldEntry;
29import cuchaz.enigma.mapping.MethodEntry;
30import cuchaz.enigma.mapping.Translator;
31
32public class ClassTranslator {
33
34 private Translator m_translator;
35
36 public ClassTranslator(Translator translator) {
37 m_translator = translator;
38 }
39
40 public void translate(CtClass c) {
41
42 // NOTE: the order of these translations is very important
43
44 // translate all the field and method references in the code by editing the constant pool
45 ConstPool constants = c.getClassFile().getConstPool();
46 ConstPoolEditor editor = new ConstPoolEditor(constants);
47 for (int i = 1; i < constants.getSize(); i++) {
48 switch (constants.getTag(i)) {
49
50 case ConstPool.CONST_Fieldref: {
51
52 // translate the name
53 FieldEntry entry = new FieldEntry(
54 new ClassEntry(Descriptor.toJvmName(constants.getFieldrefClassName(i))),
55 constants.getFieldrefName(i)
56 );
57 FieldEntry translatedEntry = m_translator.translateEntry(entry);
58
59 // translate the type
60 String type = constants.getFieldrefType(i);
61 String translatedType = m_translator.translateSignature(type);
62
63 if (!entry.equals(translatedEntry) || !type.equals(translatedType)) {
64 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedType);
65 }
66 }
67 break;
68
69 case ConstPool.CONST_Methodref:
70 case ConstPool.CONST_InterfaceMethodref: {
71
72 // translate the name and type
73 BehaviorEntry entry = BehaviorEntryFactory.create(
74 Descriptor.toJvmName(editor.getMemberrefClassname(i)),
75 editor.getMemberrefName(i),
76 editor.getMemberrefType(i)
77 );
78 BehaviorEntry translatedEntry = m_translator.translateEntry(entry);
79
80 if (!entry.getName().equals(translatedEntry.getName()) || !entry.getSignature().equals(translatedEntry.getSignature())) {
81 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature());
82 }
83 }
84 break;
85 }
86 }
87
88 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
89
90 // translate all the fields
91 for (CtField field : c.getDeclaredFields()) {
92
93 // translate the name
94 FieldEntry entry = new FieldEntry(classEntry, field.getName());
95 String translatedName = m_translator.translate(entry);
96 if (translatedName != null) {
97 field.setName(translatedName);
98 }
99
100 // translate the type
101 String translatedType = m_translator.translateSignature(field.getFieldInfo().getDescriptor());
102 field.getFieldInfo().setDescriptor(translatedType);
103 }
104
105 // translate all the methods and constructors
106 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
107 if (behavior instanceof CtMethod) {
108 CtMethod method = (CtMethod)behavior;
109
110 // translate the name
111 MethodEntry entry = new MethodEntry(classEntry, method.getName(), method.getSignature());
112 String translatedName = m_translator.translate(entry);
113 if (translatedName != null) {
114 method.setName(translatedName);
115 }
116 }
117
118 // translate the type
119 String translatedSignature = m_translator.translateSignature(behavior.getMethodInfo().getDescriptor());
120 behavior.getMethodInfo().setDescriptor(translatedSignature);
121 }
122
123 // translate all the class names referenced in the code
124 // the above code only changed method/field/reference names and types, but not the class names themselves
125 Map<ClassEntry,ClassEntry> map = Maps.newHashMap();
126 for (ClassEntry obfClassEntry : ClassRenamer.getAllClassEntries(c)) {
127 ClassEntry deobfClassEntry = m_translator.translateEntry(obfClassEntry);
128 if (!obfClassEntry.equals(deobfClassEntry)) {
129 map.put(obfClassEntry, deobfClassEntry);
130 }
131 }
132 ClassRenamer.renameClasses(c, map);
133
134 // translate the source file attribute too
135 ClassEntry deobfClassEntry = map.get(classEntry);
136 if (deobfClassEntry != null) {
137 String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOuterClassName()) + ".java";
138 c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile));
139 }
140 }
141}
diff --git a/src/cuchaz/enigma/bytecode/ConstPoolEditor.java b/src/cuchaz/enigma/bytecode/ConstPoolEditor.java
new file mode 100644
index 0000000..2dec3b7
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/ConstPoolEditor.java
@@ -0,0 +1,263 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..deaf623
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/InfoType.java
@@ -0,0 +1,317 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..f52c31a
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/InnerClassWriter.java
@@ -0,0 +1,102 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.Collection;
14
15import javassist.CtClass;
16import javassist.bytecode.AccessFlag;
17import javassist.bytecode.ConstPool;
18import javassist.bytecode.Descriptor;
19import javassist.bytecode.EnclosingMethodAttribute;
20import javassist.bytecode.InnerClassesAttribute;
21import cuchaz.enigma.Constants;
22import cuchaz.enigma.analysis.JarIndex;
23import cuchaz.enigma.mapping.BehaviorEntry;
24import cuchaz.enigma.mapping.ClassEntry;
25
26public class InnerClassWriter {
27
28 private JarIndex m_jarIndex;
29
30 public InnerClassWriter(JarIndex jarIndex) {
31 m_jarIndex = jarIndex;
32 }
33
34 public void write(CtClass c) {
35
36 // is this an inner or outer class?
37 String obfInnerClassName = new ClassEntry(Descriptor.toJvmName(c.getName())).getSimpleName();
38 String obfOuterClassName = m_jarIndex.getOuterClass(obfInnerClassName);
39 if (obfOuterClassName == null) {
40 // this is an outer class
41 obfOuterClassName = Descriptor.toJvmName(c.getName());
42 } else {
43 // this is an inner class, rename it to outer$inner
44 ClassEntry obfClassEntry = new ClassEntry(obfOuterClassName + "$" + obfInnerClassName);
45 c.setName(obfClassEntry.getName());
46
47 BehaviorEntry caller = m_jarIndex.getAnonymousClassCaller(obfInnerClassName);
48 if (caller != null) {
49 // write the enclosing method attribute
50 if (caller.getName().equals("<clinit>")) {
51 c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName()));
52 } else {
53 c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature()));
54 }
55 }
56 }
57
58 // write the inner classes if needed
59 Collection<String> obfInnerClassNames = m_jarIndex.getInnerClasses(obfOuterClassName);
60 if (obfInnerClassNames != null && !obfInnerClassNames.isEmpty()) {
61 writeInnerClasses(c, obfOuterClassName, obfInnerClassNames);
62 }
63 }
64
65 private void writeInnerClasses(CtClass c, String obfOuterClassName, Collection<String> obfInnerClassNames) {
66 InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool());
67 c.getClassFile().addAttribute(attr);
68 for (String obfInnerClassName : obfInnerClassNames) {
69 // get the new inner class name
70 ClassEntry obfClassEntry = new ClassEntry(obfOuterClassName + "$" + obfInnerClassName);
71
72 // here's what the JVM spec says about the InnerClasses attribute
73 // append( inner, outer of inner if inner is member of outer 0 ow, name after $ if inner not anonymous 0 ow, flags );
74
75 // update the attribute with this inner class
76 ConstPool constPool = c.getClassFile().getConstPool();
77 int innerClassIndex = constPool.addClassInfo(obfClassEntry.getName());
78 int outerClassIndex = 0;
79 int innerClassSimpleNameIndex = 0;
80 if (!m_jarIndex.isAnonymousClass(obfInnerClassName)) {
81 outerClassIndex = constPool.addClassInfo(obfClassEntry.getOuterClassName());
82 innerClassSimpleNameIndex = constPool.addUtf8Info(obfClassEntry.getInnerClassName());
83 }
84
85 attr.append(innerClassIndex, outerClassIndex, innerClassSimpleNameIndex, c.getClassFile().getAccessFlags() & ~AccessFlag.SUPER);
86
87 /* DEBUG
88 System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)",
89 obfClassEntry,
90 attr.outerClass(attr.tableLength() - 1),
91 attr.innerClass(attr.tableLength() - 1),
92 attr.innerName(attr.tableLength() - 1),
93 Constants.NonePackage + "/" + obfInnerClassName,
94 obfClassEntry.getName()
95 ));
96 */
97
98 // make sure the outer class references only the new inner class names
99 c.replaceClassName(Constants.NonePackage + "/" + obfInnerClassName, obfClassEntry.getName());
100 }
101 }
102}
diff --git a/src/cuchaz/enigma/bytecode/MethodParameterWriter.java b/src/cuchaz/enigma/bytecode/MethodParameterWriter.java
new file mode 100644
index 0000000..5a11cd8
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/MethodParameterWriter.java
@@ -0,0 +1,66 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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.CtConstructor;
19import javassist.CtMethod;
20import javassist.bytecode.Descriptor;
21import cuchaz.enigma.mapping.ArgumentEntry;
22import cuchaz.enigma.mapping.BehaviorEntry;
23import cuchaz.enigma.mapping.ClassEntry;
24import cuchaz.enigma.mapping.ConstructorEntry;
25import cuchaz.enigma.mapping.MethodEntry;
26import cuchaz.enigma.mapping.Translator;
27
28public class MethodParameterWriter {
29
30 private Translator m_translator;
31
32 public MethodParameterWriter(Translator translator) {
33 m_translator = translator;
34 }
35
36 public void writeMethodArguments(CtClass c) {
37
38 // Procyon will read method arguments from the "MethodParameters" attribute, so write those
39 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
40 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
41 int numParams = Descriptor.numOfParameters(behavior.getMethodInfo().getDescriptor());
42 if (numParams <= 0) {
43 continue;
44 }
45
46 // get the behavior entry
47 BehaviorEntry behaviorEntry;
48 if (behavior instanceof CtMethod) {
49 behaviorEntry = new MethodEntry(classEntry, behavior.getMethodInfo().getName(), behavior.getSignature());
50 } else if (behavior instanceof CtConstructor) {
51 behaviorEntry = new ConstructorEntry(classEntry, behavior.getSignature());
52 } else {
53 throw new Error("Unsupported behavior type: " + behavior.getClass().getName());
54 }
55
56 // get the list of parameter 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/cuchaz/enigma/bytecode/MethodParametersAttribute.java b/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java
new file mode 100644
index 0000000..bf95956
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java
@@ -0,0 +1,85 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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 // add the names to the class const pool
31 ConstPool constPool = info.getConstPool();
32 List<Integer> parameterNameIndices = new ArrayList<Integer>();
33 for (String name : names) {
34 if (name != null) {
35 parameterNameIndices.add(constPool.addUtf8Info(name));
36 } else {
37 parameterNameIndices.add(0);
38 }
39 }
40
41 // add the attribute to the method
42 info.addAttribute(new MethodParametersAttribute(constPool, parameterNameIndices));
43 }
44
45 private static byte[] writeStruct(List<Integer> parameterNameIndices) {
46 // JVM 8 Spec says the struct looks like this:
47 // http://cr.openjdk.java.net/~mr/se/8/java-se-8-fr-spec-01/java-se-8-jvms-fr-diffs.pdf
48 // uint8 num_params
49 // for each param:
50 // uint16 name_index -> points to UTF8 entry in constant pool, or 0 for no entry
51 // uint16 access_flags -> don't care, just set to 0
52
53 ByteArrayOutputStream buf = new ByteArrayOutputStream();
54 DataOutputStream out = new DataOutputStream(buf);
55
56 // NOTE: java hates unsigned integers, so we have to be careful here
57 // the writeShort(), writeByte() methods will read 16,8 low-order bits from the int argument
58 // as long as the int argument is in range of the unsigned short/byte type, it will be written as an unsigned short/byte
59 // if the int is out of range, the byte stream won't look the way we want and weird things will happen
60 final int SIZEOF_UINT8 = 1;
61 final int SIZEOF_UINT16 = 2;
62 final int MAX_UINT8 = (1 << 8) - 1;
63 final int MAX_UINT16 = (1 << 16) - 1;
64
65 try {
66 assert (parameterNameIndices.size() >= 0 && parameterNameIndices.size() <= MAX_UINT8);
67 out.writeByte(parameterNameIndices.size());
68
69 for (Integer index : parameterNameIndices) {
70 assert (index >= 0 && index <= MAX_UINT16);
71 out.writeShort(index);
72
73 // just write 0 for the access flags
74 out.writeShort(0);
75 }
76
77 out.close();
78 byte[] data = buf.toByteArray();
79 assert (data.length == SIZEOF_UINT8 + parameterNameIndices.size() * (SIZEOF_UINT16 + SIZEOF_UINT16));
80 return data;
81 } catch (IOException ex) {
82 throw new Error(ex);
83 }
84 }
85}
diff --git a/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
new file mode 100644
index 0000000..d76f056
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..d00c102
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java
@@ -0,0 +1,156 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..0d780ea
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..9fe945f
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..4c95b22
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..e151117
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..6e82f3e
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..6665ffe
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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/bytecode/accessors/Utf8InfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java
new file mode 100644
index 0000000..2abf60b
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java
@@ -0,0 +1,28 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
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/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java
new file mode 100644
index 0000000..7340403
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassIdentity.java
@@ -0,0 +1,411 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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;
19
20import javassist.CannotCompileException;
21import javassist.CtBehavior;
22import javassist.CtClass;
23import javassist.CtConstructor;
24import javassist.CtField;
25import javassist.CtMethod;
26import javassist.bytecode.BadBytecode;
27import javassist.bytecode.CodeIterator;
28import javassist.bytecode.ConstPool;
29import javassist.bytecode.Descriptor;
30import javassist.bytecode.Opcode;
31import javassist.expr.ConstructorCall;
32import javassist.expr.ExprEditor;
33import javassist.expr.FieldAccess;
34import javassist.expr.MethodCall;
35import javassist.expr.NewExpr;
36
37import com.google.common.collect.HashMultiset;
38import com.google.common.collect.Lists;
39import com.google.common.collect.Maps;
40import com.google.common.collect.Multiset;
41
42import cuchaz.enigma.Constants;
43import cuchaz.enigma.Util;
44import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
45import cuchaz.enigma.analysis.EntryReference;
46import cuchaz.enigma.analysis.JarIndex;
47import cuchaz.enigma.bytecode.ConstPoolEditor;
48import cuchaz.enigma.bytecode.InfoType;
49import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
50import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
51import cuchaz.enigma.mapping.BehaviorEntry;
52import cuchaz.enigma.mapping.ClassEntry;
53import cuchaz.enigma.mapping.ConstructorEntry;
54import cuchaz.enigma.mapping.Entry;
55import cuchaz.enigma.mapping.FieldEntry;
56import cuchaz.enigma.mapping.MethodEntry;
57import cuchaz.enigma.mapping.SignatureUpdater;
58import cuchaz.enigma.mapping.SignatureUpdater.ClassNameUpdater;
59
60public class ClassIdentity {
61
62 private ClassEntry m_classEntry;
63 private SidedClassNamer m_namer;
64 private Multiset<String> m_fields;
65 private Multiset<String> m_methods;
66 private Multiset<String> m_constructors;
67 private String m_staticInitializer;
68 private String m_extends;
69 private Multiset<String> m_implements;
70 private Multiset<String> m_implementations;
71 private Multiset<String> m_references;
72
73 public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) {
74 m_namer = namer;
75
76 // stuff from the bytecode
77
78 m_classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
79 m_fields = HashMultiset.create();
80 for (CtField field : c.getDeclaredFields()) {
81 m_fields.add(scrubSignature(field.getSignature()));
82 }
83 m_methods = HashMultiset.create();
84 for (CtMethod method : c.getDeclaredMethods()) {
85 m_methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method));
86 }
87 m_constructors = HashMultiset.create();
88 for (CtConstructor constructor : c.getDeclaredConstructors()) {
89 m_constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor));
90 }
91 m_staticInitializer = "";
92 if (c.getClassInitializer() != null) {
93 m_staticInitializer = getBehaviorSignature(c.getClassInitializer());
94 }
95 m_extends = "";
96 if (c.getClassFile().getSuperclass() != null) {
97 m_extends = scrubClassName(c.getClassFile().getSuperclass());
98 }
99 m_implements = HashMultiset.create();
100 for (String interfaceName : c.getClassFile().getInterfaces()) {
101 m_implements.add(scrubClassName(interfaceName));
102 }
103
104 // stuff from the jar index
105
106 m_implementations = HashMultiset.create();
107 ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, m_classEntry);
108 if (implementationsNode != null) {
109 @SuppressWarnings("unchecked")
110 Enumeration<ClassImplementationsTreeNode> implementations = implementationsNode.children();
111 while (implementations.hasMoreElements()) {
112 ClassImplementationsTreeNode node = implementations.nextElement();
113 m_implementations.add(scrubClassName(node.getClassEntry().getName()));
114 }
115 }
116
117 m_references = HashMultiset.create();
118 if (useReferences) {
119 for (CtField field : c.getDeclaredFields()) {
120 FieldEntry fieldEntry = new FieldEntry(m_classEntry, field.getName());
121 for (EntryReference<FieldEntry,BehaviorEntry> reference : index.getFieldReferences(fieldEntry)) {
122 addReference(reference);
123 }
124 }
125 for (CtMethod method : c.getDeclaredMethods()) {
126 MethodEntry methodEntry = new MethodEntry(m_classEntry, method.getName(), method.getSignature());
127 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(methodEntry)) {
128 addReference(reference);
129 }
130 }
131 for (CtConstructor constructor : c.getDeclaredConstructors()) {
132 ConstructorEntry constructorEntry = new ConstructorEntry(m_classEntry, constructor.getSignature());
133 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(constructorEntry)) {
134 addReference(reference);
135 }
136 }
137 }
138 }
139
140 private void addReference(EntryReference<? extends Entry,BehaviorEntry> reference) {
141 if (reference.context.getSignature() != null) {
142 m_references.add(String.format("%s_%s", scrubClassName(reference.context.getClassName()), scrubSignature(reference.context.getSignature())));
143 } else {
144 m_references.add(String.format("%s_<clinit>", scrubClassName(reference.context.getClassName())));
145 }
146 }
147
148 public ClassEntry getClassEntry() {
149 return m_classEntry;
150 }
151
152 @Override
153 public String toString() {
154 StringBuilder buf = new StringBuilder();
155 buf.append("class: ");
156 buf.append(m_classEntry.getName());
157 buf.append(" ");
158 buf.append(hashCode());
159 buf.append("\n");
160 for (String field : m_fields) {
161 buf.append("\tfield ");
162 buf.append(field);
163 buf.append("\n");
164 }
165 for (String method : m_methods) {
166 buf.append("\tmethod ");
167 buf.append(method);
168 buf.append("\n");
169 }
170 for (String constructor : m_constructors) {
171 buf.append("\tconstructor ");
172 buf.append(constructor);
173 buf.append("\n");
174 }
175 if (m_staticInitializer.length() > 0) {
176 buf.append("\tinitializer ");
177 buf.append(m_staticInitializer);
178 buf.append("\n");
179 }
180 if (m_extends.length() > 0) {
181 buf.append("\textends ");
182 buf.append(m_extends);
183 buf.append("\n");
184 }
185 for (String interfaceName : m_implements) {
186 buf.append("\timplements ");
187 buf.append(interfaceName);
188 buf.append("\n");
189 }
190 for (String implementation : m_implementations) {
191 buf.append("\timplemented by ");
192 buf.append(implementation);
193 buf.append("\n");
194 }
195 for (String reference : m_references) {
196 buf.append("\treference ");
197 buf.append(reference);
198 buf.append("\n");
199 }
200 return buf.toString();
201 }
202
203 private String scrubClassName(String className) {
204 return scrubSignature("L" + Descriptor.toJvmName(className) + ";");
205 }
206
207 private String scrubSignature(String signature) {
208 return SignatureUpdater.update(signature, new ClassNameUpdater() {
209 private Map<String,String> m_classNames = Maps.newHashMap();
210
211 @Override
212 public String update(String className) {
213 // classes not in the none package can be passed through
214 ClassEntry classEntry = new ClassEntry(className);
215 if (!classEntry.getPackageName().equals(Constants.NonePackage)) {
216 return className;
217 }
218
219 // is this class ourself?
220 if (className.equals(m_classEntry.getName())) {
221 return "CSelf";
222 }
223
224 // try the namer
225 if (m_namer != null) {
226 String newName = m_namer.getName(className);
227 if (newName != null) {
228 return newName;
229 }
230 }
231
232 // otherwise, use local naming
233 if (!m_classNames.containsKey(className)) {
234 m_classNames.put(className, getNewClassName());
235 }
236 return m_classNames.get(className);
237 }
238
239 private String getNewClassName() {
240 return String.format("C%03d", m_classNames.size());
241 }
242 });
243 }
244
245 private boolean isClassMatchedUniquely(String className) {
246 return m_namer != null && m_namer.getName(Descriptor.toJvmName(className)) != null;
247 }
248
249 private String getBehaviorSignature(CtBehavior behavior) {
250 try {
251 // does this method have an implementation?
252 if (behavior.getMethodInfo().getCodeAttribute() == null) {
253 return "(none)";
254 }
255
256 // compute the hash from the opcodes
257 ConstPool constants = behavior.getMethodInfo().getConstPool();
258 final MessageDigest digest = MessageDigest.getInstance("MD5");
259 CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator();
260 while (iter.hasNext()) {
261 int pos = iter.next();
262
263 // update the hash with the opcode
264 int opcode = iter.byteAt(pos);
265 digest.update((byte)opcode);
266
267 switch (opcode) {
268 case Opcode.LDC: {
269 int constIndex = iter.byteAt(pos + 1);
270 updateHashWithConstant(digest, constants, constIndex);
271 }
272 break;
273
274 case Opcode.LDC_W:
275 case Opcode.LDC2_W: {
276 int constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2);
277 updateHashWithConstant(digest, constants, constIndex);
278 }
279 break;
280 }
281 }
282
283 // update hash with method and field accesses
284 behavior.instrument(new ExprEditor() {
285 @Override
286 public void edit(MethodCall call) {
287 updateHashWithString(digest, scrubClassName(call.getClassName()));
288 updateHashWithString(digest, scrubSignature(call.getSignature()));
289 if (isClassMatchedUniquely(call.getClassName())) {
290 updateHashWithString(digest, call.getMethodName());
291 }
292 }
293
294 @Override
295 public void edit(FieldAccess access) {
296 updateHashWithString(digest, scrubClassName(access.getClassName()));
297 updateHashWithString(digest, scrubSignature(access.getSignature()));
298 if (isClassMatchedUniquely(access.getClassName())) {
299 updateHashWithString(digest, access.getFieldName());
300 }
301 }
302
303 @Override
304 public void edit(ConstructorCall call) {
305 updateHashWithString(digest, scrubClassName(call.getClassName()));
306 updateHashWithString(digest, scrubSignature(call.getSignature()));
307 }
308
309 @Override
310 public void edit(NewExpr expr) {
311 updateHashWithString(digest, scrubClassName(expr.getClassName()));
312 }
313 });
314
315 // convert the hash to a hex string
316 return toHex(digest.digest());
317 } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) {
318 throw new Error(ex);
319 }
320 }
321
322 private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) {
323 ConstPoolEditor editor = new ConstPoolEditor(constants);
324 ConstInfoAccessor item = editor.getItem(index);
325 if (item.getType() == InfoType.StringInfo) {
326 updateHashWithString(digest, constants.getStringInfo(index));
327 }
328 // TODO: other constants
329 }
330
331 private void updateHashWithString(MessageDigest digest, String val) {
332 try {
333 digest.update(val.getBytes("UTF8"));
334 } catch (UnsupportedEncodingException ex) {
335 throw new Error(ex);
336 }
337 }
338
339 private String toHex(byte[] bytes) {
340 // function taken from:
341 // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java
342 final char[] hexArray = "0123456789ABCDEF".toCharArray();
343 char[] hexChars = new char[bytes.length * 2];
344 for (int j = 0; j < bytes.length; j++) {
345 int v = bytes[j] & 0xFF;
346 hexChars[j * 2] = hexArray[v >>> 4];
347 hexChars[j * 2 + 1] = hexArray[v & 0x0F];
348 }
349 return new String(hexChars);
350 }
351
352 @Override
353 public boolean equals(Object other) {
354 if (other instanceof ClassIdentity) {
355 return equals((ClassIdentity)other);
356 }
357 return false;
358 }
359
360 public boolean equals(ClassIdentity other) {
361 return m_fields.equals(other.m_fields)
362 && m_methods.equals(other.m_methods)
363 && m_constructors.equals(other.m_constructors)
364 && m_staticInitializer.equals(other.m_staticInitializer)
365 && m_extends.equals(other.m_extends)
366 && m_implements.equals(other.m_implements)
367 && m_implementations.equals(other.m_implementations)
368 && m_references.equals(other.m_references);
369 }
370
371 @Override
372 public int hashCode() {
373 List<Object> objs = Lists.newArrayList();
374 objs.addAll(m_fields);
375 objs.addAll(m_methods);
376 objs.addAll(m_constructors);
377 objs.add(m_staticInitializer);
378 objs.add(m_extends);
379 objs.addAll(m_implements);
380 objs.addAll(m_implementations);
381 objs.addAll(m_references);
382 return Util.combineHashesOrdered(objs);
383 }
384
385 public int getMatchScore(ClassIdentity other) {
386 return getNumMatches(m_fields, other.m_fields)
387 + getNumMatches(m_methods, other.m_methods)
388 + getNumMatches(m_constructors, other.m_constructors);
389 }
390
391 public int getMaxMatchScore() {
392 return m_fields.size() + m_methods.size() + m_constructors.size();
393 }
394
395 public boolean matches(CtClass c) {
396 // just compare declaration counts
397 return m_fields.size() == c.getDeclaredFields().length
398 && m_methods.size() == c.getDeclaredMethods().length
399 && m_constructors.size() == c.getDeclaredConstructors().length;
400 }
401
402 private int getNumMatches(Multiset<String> a, Multiset<String> b) {
403 int numMatches = 0;
404 for (String val : a) {
405 if (b.contains(val)) {
406 numMatches++;
407 }
408 }
409 return numMatches;
410 }
411}
diff --git a/src/cuchaz/enigma/convert/ClassMatcher.java b/src/cuchaz/enigma/convert/ClassMatcher.java
new file mode 100644
index 0000000..fc39ed0
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassMatcher.java
@@ -0,0 +1,415 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.io.File;
14import java.io.FileReader;
15import java.io.FileWriter;
16import java.io.IOException;
17import java.util.ArrayList;
18import java.util.Arrays;
19import java.util.Collection;
20import java.util.Collections;
21import java.util.Comparator;
22import java.util.Iterator;
23import java.util.LinkedHashMap;
24import java.util.List;
25import java.util.Map;
26import java.util.Set;
27import java.util.jar.JarFile;
28
29import javassist.CtBehavior;
30import javassist.CtClass;
31
32import com.google.common.collect.ArrayListMultimap;
33import com.google.common.collect.BiMap;
34import com.google.common.collect.HashBiMap;
35import com.google.common.collect.Lists;
36import com.google.common.collect.Maps;
37import com.google.common.collect.Multimap;
38import com.google.common.collect.Sets;
39
40import cuchaz.enigma.TranslatingTypeLoader;
41import cuchaz.enigma.analysis.JarIndex;
42import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
43import cuchaz.enigma.mapping.ClassEntry;
44import cuchaz.enigma.mapping.ClassMapping;
45import cuchaz.enigma.mapping.MappingParseException;
46import cuchaz.enigma.mapping.Mappings;
47import cuchaz.enigma.mapping.MappingsReader;
48import cuchaz.enigma.mapping.MappingsWriter;
49import cuchaz.enigma.mapping.MethodEntry;
50import cuchaz.enigma.mapping.MethodMapping;
51
52public class ClassMatcher {
53
54 public static void main(String[] args) throws IOException, MappingParseException {
55 // TEMP
56 JarFile sourceJar = new JarFile(new File("input/1.8-pre3.jar"));
57 JarFile destJar = new JarFile(new File("input/1.8.jar"));
58 File inMappingsFile = new File("../Enigma Mappings/1.8-pre3.mappings");
59 File outMappingsFile = new File("../Enigma Mappings/1.8.mappings");
60
61 // define a matching to use when the automated system cannot find a match
62 Map<String,String> fallbackMatching = Maps.newHashMap();
63 fallbackMatching.put("none/ayb", "none/ayf");
64 fallbackMatching.put("none/ayd", "none/ayd");
65 fallbackMatching.put("none/bgk", "unknown/bgk");
66
67 // do the conversion
68 Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile));
69 convertMappings(sourceJar, destJar, mappings, fallbackMatching);
70
71 // write out the converted mappings
72 FileWriter writer = new FileWriter(outMappingsFile);
73 new MappingsWriter().write(writer, mappings);
74 writer.close();
75 System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
76 }
77
78 private static void convertMappings(JarFile sourceJar, JarFile destJar, Mappings mappings, Map<String,String> fallbackMatching) {
79 // index jars
80 System.out.println("Indexing source jar...");
81 JarIndex sourceIndex = new JarIndex();
82 sourceIndex.indexJar(sourceJar, false);
83 System.out.println("Indexing dest jar...");
84 JarIndex destIndex = new JarIndex();
85 destIndex.indexJar(destJar, false);
86 TranslatingTypeLoader sourceLoader = new TranslatingTypeLoader(sourceJar, sourceIndex);
87 TranslatingTypeLoader destLoader = new TranslatingTypeLoader(destJar, destIndex);
88
89 // compute the matching
90 ClassMatching matching = computeMatching(sourceIndex, sourceLoader, destIndex, destLoader);
91 Map<String,Map.Entry<ClassIdentity,List<ClassIdentity>>> matchingIndex = matching.getIndex();
92
93 // get all the obf class names used in the mappings
94 Set<String> usedClassNames = mappings.getAllObfClassNames();
95 Set<String> allClassNames = Sets.newHashSet();
96 for (ClassEntry classEntry : sourceIndex.getObfClassEntries()) {
97 allClassNames.add(classEntry.getName());
98 }
99 usedClassNames.retainAll(allClassNames);
100 System.out.println("Used " + usedClassNames.size() + " classes in the mappings");
101
102 // probabilistically match the non-uniquely-matched source classes
103 for (Map.Entry<ClassIdentity,List<ClassIdentity>> entry : matchingIndex.values()) {
104 ClassIdentity sourceClass = entry.getKey();
105 List<ClassIdentity> destClasses = entry.getValue();
106
107 // skip classes that are uniquely matched
108 if (destClasses.size() == 1) {
109 continue;
110 }
111
112 // skip classes that aren't used in the mappings
113 if (!usedClassNames.contains(sourceClass.getClassEntry().getName())) {
114 continue;
115 }
116
117 System.out.println("No exact match for source class " + sourceClass.getClassEntry());
118
119 // find the closest classes
120 Multimap<Integer,ClassIdentity> scoredMatches = ArrayListMultimap.create();
121 for (ClassIdentity c : destClasses) {
122 scoredMatches.put(sourceClass.getMatchScore(c), c);
123 }
124 List<Integer> scores = new ArrayList<Integer>(scoredMatches.keySet());
125 Collections.sort(scores, Collections.reverseOrder());
126 printScoredMatches(sourceClass.getMaxMatchScore(), scores, scoredMatches);
127
128 // does the best match have a non-zero score and the same name?
129 int bestScore = scores.get(0);
130 Collection<ClassIdentity> bestMatches = scoredMatches.get(bestScore);
131 if (bestScore > 0 && bestMatches.size() == 1) {
132 ClassIdentity bestMatch = bestMatches.iterator().next();
133 if (bestMatch.getClassEntry().equals(sourceClass.getClassEntry())) {
134 // use it
135 System.out.println("\tAutomatically choosing likely match: " + bestMatch.getClassEntry().getName());
136 destClasses.clear();
137 destClasses.add(bestMatch);
138 }
139 }
140 }
141
142 // group the matching into unique and non-unique matches
143 BiMap<String,String> matchedClassNames = HashBiMap.create();
144 Set<String> unmatchedSourceClassNames = Sets.newHashSet();
145 for (String className : usedClassNames) {
146 // is there a match for this class?
147 Map.Entry<ClassIdentity,List<ClassIdentity>> entry = matchingIndex.get(className);
148 ClassIdentity sourceClass = entry.getKey();
149 List<ClassIdentity> matches = entry.getValue();
150
151 if (matches.size() == 1) {
152 // unique match! We're good to go!
153 matchedClassNames.put(sourceClass.getClassEntry().getName(), matches.get(0).getClassEntry().getName());
154 } else {
155 // no match, check the fallback matching
156 String fallbackMatch = fallbackMatching.get(className);
157 if (fallbackMatch != null) {
158 matchedClassNames.put(sourceClass.getClassEntry().getName(), fallbackMatch);
159 } else {
160 unmatchedSourceClassNames.add(className);
161 }
162 }
163 }
164
165 // report unmatched classes
166 if (!unmatchedSourceClassNames.isEmpty()) {
167 System.err.println("ERROR: there were unmatched classes!");
168 for (String className : unmatchedSourceClassNames) {
169 System.err.println("\t" + className);
170 }
171 return;
172 }
173
174 // get the class name changes from the matched class names
175 Map<String,String> classChanges = Maps.newHashMap();
176 for (Map.Entry<String,String> entry : matchedClassNames.entrySet()) {
177 if (!entry.getKey().equals(entry.getValue())) {
178 classChanges.put(entry.getKey(), entry.getValue());
179 System.out.println(String.format("Class change: %s -> %s", entry.getKey(), entry.getValue()));
180 /* DEBUG
181 System.out.println(String.format("\n%s\n%s",
182 new ClassIdentity(sourceLoader.loadClass(entry.getKey()), null, sourceIndex, false, false),
183 new ClassIdentity( destLoader.loadClass(entry.getValue()), null, destIndex, false, false)
184 ));
185 */
186 }
187 }
188
189 // sort the changes so classes are renamed in the correct order
190 // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b
191 LinkedHashMap<String,String> orderedClassChanges = Maps.newLinkedHashMap();
192 int numChangesLeft = classChanges.size();
193 while (!classChanges.isEmpty()) {
194 Iterator<Map.Entry<String,String>> iter = classChanges.entrySet().iterator();
195 while (iter.hasNext()) {
196 Map.Entry<String,String> entry = iter.next();
197 if (classChanges.get(entry.getValue()) == null) {
198 orderedClassChanges.put(entry.getKey(), entry.getValue());
199 iter.remove();
200 }
201 }
202
203 // did we remove any changes?
204 if (numChangesLeft - classChanges.size() > 0) {
205 // keep going
206 numChangesLeft = classChanges.size();
207 } else {
208 // can't sort anymore. There must be a loop
209 break;
210 }
211 }
212 if (classChanges.size() > 0) {
213 throw new Error(String.format("Unable to sort %d/%d class changes!", classChanges.size(), matchedClassNames.size()));
214 }
215
216 // convert the mappings in the correct class order
217 for (Map.Entry<String,String> entry : orderedClassChanges.entrySet()) {
218 mappings.renameObfClass(entry.getKey(), entry.getValue());
219 }
220
221 // check the method matches
222 System.out.println("Checking methods...");
223 for (ClassMapping classMapping : mappings.classes()) {
224 ClassEntry classEntry = new ClassEntry(classMapping.getObfName());
225 for (MethodMapping methodMapping : classMapping.methods()) {
226
227 // skip constructors
228 if (methodMapping.getObfName().equals("<init>")) {
229 continue;
230 }
231
232 MethodEntry methodEntry = new MethodEntry(
233 classEntry,
234 methodMapping.getObfName(),
235 methodMapping.getObfSignature()
236 );
237 if (!destIndex.containsObfBehavior(methodEntry)) {
238 System.err.println("WARNING: method doesn't match: " + methodEntry);
239
240 // show the available methods
241 System.err.println("\tAvailable dest methods:");
242 CtClass c = destLoader.loadClass(classMapping.getObfName());
243 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
244 MethodEntry declaredMethodEntry = new MethodEntry(
245 new ClassEntry(classMapping.getObfName()),
246 behavior.getName(),
247 behavior.getSignature()
248 );
249 System.err.println("\t\t" + declaredMethodEntry);
250 }
251
252 System.err.println("\tAvailable source methods:");
253 c = sourceLoader.loadClass(matchedClassNames.inverse().get(classMapping.getObfName()));
254 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
255 MethodEntry declaredMethodEntry = new MethodEntry(
256 new ClassEntry(classMapping.getObfName()),
257 behavior.getName(),
258 behavior.getSignature()
259 );
260 System.err.println("\t\t" + declaredMethodEntry);
261 }
262 }
263 }
264 }
265
266 System.out.println("Done!");
267 }
268
269 public static ClassMatching computeMatching(JarIndex sourceIndex, TranslatingTypeLoader sourceLoader, JarIndex destIndex, TranslatingTypeLoader destLoader) {
270
271 System.out.println("Matching classes...");
272
273 ClassMatching matching = null;
274 for (boolean useReferences : Arrays.asList(false, true)) {
275 int numMatches = 0;
276 do {
277 SidedClassNamer sourceNamer = null;
278 SidedClassNamer destNamer = null;
279 if (matching != null) {
280 // build a class namer
281 ClassNamer namer = new ClassNamer(matching.getUniqueMatches());
282 sourceNamer = namer.getSourceNamer();
283 destNamer = namer.getDestNamer();
284
285 // note the number of matches
286 numMatches = matching.getUniqueMatches().size();
287 }
288
289 // get the entries left to match
290 Set<ClassEntry> sourceClassEntries = Sets.newHashSet();
291 Set<ClassEntry> destClassEntries = Sets.newHashSet();
292 if (matching == null) {
293 sourceClassEntries.addAll(sourceIndex.getObfClassEntries());
294 destClassEntries.addAll(destIndex.getObfClassEntries());
295 matching = new ClassMatching();
296 } else {
297 for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : matching.getAmbiguousMatches().entrySet()) {
298 for (ClassIdentity c : entry.getKey()) {
299 sourceClassEntries.add(c.getClassEntry());
300 matching.removeSource(c);
301 }
302 for (ClassIdentity c : entry.getValue()) {
303 destClassEntries.add(c.getClassEntry());
304 matching.removeDest(c);
305 }
306 }
307 for (ClassIdentity c : matching.getUnmatchedSourceClasses()) {
308 sourceClassEntries.add(c.getClassEntry());
309 matching.removeSource(c);
310 }
311 for (ClassIdentity c : matching.getUnmatchedDestClasses()) {
312 destClassEntries.add(c.getClassEntry());
313 matching.removeDest(c);
314 }
315 }
316
317 // compute a matching for the classes
318 for (ClassEntry classEntry : sourceClassEntries) {
319 CtClass c = sourceLoader.loadClass(classEntry.getName());
320 ClassIdentity sourceClass = new ClassIdentity(c, sourceNamer, sourceIndex, useReferences);
321 matching.addSource(sourceClass);
322 }
323 for (ClassEntry classEntry : destClassEntries) {
324 CtClass c = destLoader.loadClass(classEntry.getName());
325 ClassIdentity destClass = new ClassIdentity(c, destNamer, destIndex, useReferences);
326 matching.matchDestClass(destClass);
327 }
328
329 // TEMP
330 System.out.println(matching);
331 } while (matching.getUniqueMatches().size() - numMatches > 0);
332 }
333
334 // check the class matches
335 System.out.println("Checking class matches...");
336 ClassNamer namer = new ClassNamer(matching.getUniqueMatches());
337 SidedClassNamer sourceNamer = namer.getSourceNamer();
338 SidedClassNamer destNamer = namer.getDestNamer();
339 for (Map.Entry<ClassIdentity,ClassIdentity> entry : matching.getUniqueMatches().entrySet()) {
340
341 // check source
342 ClassIdentity sourceClass = entry.getKey();
343 CtClass sourceC = sourceLoader.loadClass(sourceClass.getClassEntry().getName());
344 assert (sourceC != null) : "Unable to load source class " + sourceClass.getClassEntry();
345 assert (sourceClass.matches(sourceC)) : "Source " + sourceClass + " doesn't match " + new ClassIdentity(sourceC, sourceNamer, sourceIndex, false);
346
347 // check dest
348 ClassIdentity destClass = entry.getValue();
349 CtClass destC = destLoader.loadClass(destClass.getClassEntry().getName());
350 assert (destC != null) : "Unable to load dest class " + destClass.getClassEntry();
351 assert (destClass.matches(destC)) : "Dest " + destClass + " doesn't match " + new ClassIdentity(destC, destNamer, destIndex, false);
352 }
353
354 // warn about the ambiguous matchings
355 List<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>> ambiguousMatches = new ArrayList<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>>(matching.getAmbiguousMatches().entrySet());
356 Collections.sort(ambiguousMatches, new Comparator<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>>() {
357 @Override
358 public int compare(Map.Entry<List<ClassIdentity>,List<ClassIdentity>> a, Map.Entry<List<ClassIdentity>,List<ClassIdentity>> b) {
359 String aName = a.getKey().get(0).getClassEntry().getName();
360 String bName = b.getKey().get(0).getClassEntry().getName();
361 return aName.compareTo(bName);
362 }
363 });
364 for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : ambiguousMatches) {
365 System.out.println("Ambiguous matching:");
366 System.out.println("\tSource: " + getClassNames(entry.getKey()));
367 System.out.println("\tDest: " + getClassNames(entry.getValue()));
368 }
369
370 /* DEBUG
371 Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry = ambiguousMatches.get( 7 );
372 for (ClassIdentity c : entry.getKey()) {
373 System.out.println(c);
374 }
375 for(ClassIdentity c : entry.getKey()) {
376 System.out.println(decompile(sourceLoader, c.getClassEntry()));
377 }
378 */
379
380 return matching;
381 }
382
383 private static void printScoredMatches(int maxScore, List<Integer> scores, Multimap<Integer,ClassIdentity> scoredMatches) {
384 int numScoredMatchesShown = 0;
385 for (int score : scores) {
386 for (ClassIdentity scoredMatch : scoredMatches.get(score)) {
387 System.out.println(String.format("\tScore: %3d %3.0f%% %s", score, 100.0 * score / maxScore, scoredMatch.getClassEntry().getName()));
388 if (numScoredMatchesShown++ > 10) {
389 return;
390 }
391 }
392 }
393 }
394
395 private static List<String> getClassNames(Collection<ClassIdentity> classes) {
396 List<String> out = Lists.newArrayList();
397 for (ClassIdentity c : classes) {
398 out.add(c.getClassEntry().getName());
399 }
400 Collections.sort(out);
401 return out;
402 }
403
404 /* DEBUG
405 private static String decompile(TranslatingTypeLoader loader, ClassEntry classEntry) {
406 PlainTextOutput output = new PlainTextOutput();
407 DecompilerSettings settings = DecompilerSettings.javaDefaults();
408 settings.setForceExplicitImports(true);
409 settings.setShowSyntheticMembers(true);
410 settings.setTypeLoader(loader);
411 Decompiler.decompile(classEntry.getName(), output, settings);
412 return output.toString();
413 }
414 */
415}
diff --git a/src/cuchaz/enigma/convert/ClassMatching.java b/src/cuchaz/enigma/convert/ClassMatching.java
new file mode 100644
index 0000000..53b6f7f
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassMatching.java
@@ -0,0 +1,173 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.AbstractMap;
14import java.util.ArrayList;
15import java.util.Arrays;
16import java.util.Collection;
17import java.util.List;
18import java.util.Map;
19
20import com.google.common.collect.ArrayListMultimap;
21import com.google.common.collect.BiMap;
22import com.google.common.collect.HashBiMap;
23import com.google.common.collect.Lists;
24import com.google.common.collect.Maps;
25import com.google.common.collect.Multimap;
26
27public class ClassMatching {
28
29 private Multimap<ClassIdentity,ClassIdentity> m_sourceClasses;
30 private Multimap<ClassIdentity,ClassIdentity> m_matchedDestClasses;
31 private List<ClassIdentity> m_unmatchedDestClasses;
32
33 public ClassMatching() {
34 m_sourceClasses = ArrayListMultimap.create();
35 m_matchedDestClasses = ArrayListMultimap.create();
36 m_unmatchedDestClasses = Lists.newArrayList();
37 }
38
39 public void addSource(ClassIdentity c) {
40 m_sourceClasses.put(c, c);
41 }
42
43 public void matchDestClass(ClassIdentity destClass) {
44 Collection<ClassIdentity> matchedSourceClasses = m_sourceClasses.get(destClass);
45 if (matchedSourceClasses.isEmpty()) {
46 // no match
47 m_unmatchedDestClasses.add(destClass);
48 } else {
49 // found a match
50 m_matchedDestClasses.put(destClass, destClass);
51
52 // DEBUG
53 ClassIdentity sourceClass = matchedSourceClasses.iterator().next();
54 assert (sourceClass.hashCode() == destClass.hashCode());
55 assert (sourceClass.equals(destClass));
56 }
57 }
58
59 public void removeSource(ClassIdentity sourceClass) {
60 m_sourceClasses.remove(sourceClass, sourceClass);
61 }
62
63 public void removeDest(ClassIdentity destClass) {
64 m_matchedDestClasses.remove(destClass, destClass);
65 m_unmatchedDestClasses.remove(destClass);
66 }
67
68 public List<ClassIdentity> getSourceClasses() {
69 return new ArrayList<ClassIdentity>(m_sourceClasses.values());
70 }
71
72 public List<ClassIdentity> getDestClasses() {
73 List<ClassIdentity> classes = Lists.newArrayList();
74 classes.addAll(m_matchedDestClasses.values());
75 classes.addAll(m_unmatchedDestClasses);
76 return classes;
77 }
78
79 public BiMap<ClassIdentity,ClassIdentity> getUniqueMatches() {
80 BiMap<ClassIdentity,ClassIdentity> uniqueMatches = HashBiMap.create();
81 for (ClassIdentity sourceClass : m_sourceClasses.keySet()) {
82 Collection<ClassIdentity> matchedSourceClasses = m_sourceClasses.get(sourceClass);
83 Collection<ClassIdentity> matchedDestClasses = m_matchedDestClasses.get(sourceClass);
84 if (matchedSourceClasses.size() == 1 && matchedDestClasses.size() == 1) {
85 ClassIdentity matchedSourceClass = matchedSourceClasses.iterator().next();
86 ClassIdentity matchedDestClass = matchedDestClasses.iterator().next();
87 uniqueMatches.put(matchedSourceClass, matchedDestClass);
88 }
89 }
90 return uniqueMatches;
91 }
92
93 public BiMap<List<ClassIdentity>,List<ClassIdentity>> getAmbiguousMatches() {
94 BiMap<List<ClassIdentity>,List<ClassIdentity>> ambiguousMatches = HashBiMap.create();
95 for (ClassIdentity sourceClass : m_sourceClasses.keySet()) {
96 Collection<ClassIdentity> matchedSourceClasses = m_sourceClasses.get(sourceClass);
97 Collection<ClassIdentity> matchedDestClasses = m_matchedDestClasses.get(sourceClass);
98 if (matchedSourceClasses.size() > 1 && matchedDestClasses.size() > 1) {
99 ambiguousMatches.put(
100 new ArrayList<ClassIdentity>(matchedSourceClasses),
101 new ArrayList<ClassIdentity>(matchedDestClasses)
102 );
103 }
104 }
105 return ambiguousMatches;
106 }
107
108 public int getNumAmbiguousSourceMatches() {
109 int num = 0;
110 for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : getAmbiguousMatches().entrySet()) {
111 num += entry.getKey().size();
112 }
113 return num;
114 }
115
116 public int getNumAmbiguousDestMatches() {
117 int num = 0;
118 for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : getAmbiguousMatches().entrySet()) {
119 num += entry.getValue().size();
120 }
121 return num;
122 }
123
124 public List<ClassIdentity> getUnmatchedSourceClasses() {
125 List<ClassIdentity> classes = Lists.newArrayList();
126 for (ClassIdentity sourceClass : getSourceClasses()) {
127 if (m_matchedDestClasses.get(sourceClass).isEmpty()) {
128 classes.add(sourceClass);
129 }
130 }
131 return classes;
132 }
133
134 public List<ClassIdentity> getUnmatchedDestClasses() {
135 return new ArrayList<ClassIdentity>(m_unmatchedDestClasses);
136 }
137
138 public Map<String,Map.Entry<ClassIdentity,List<ClassIdentity>>> getIndex() {
139 Map<String,Map.Entry<ClassIdentity,List<ClassIdentity>>> conversion = Maps.newHashMap();
140 for (Map.Entry<ClassIdentity,ClassIdentity> entry : getUniqueMatches().entrySet()) {
141 conversion.put(
142 entry.getKey().getClassEntry().getName(),
143 new AbstractMap.SimpleEntry<ClassIdentity,List<ClassIdentity>>(entry.getKey(), Arrays.asList(entry.getValue()))
144 );
145 }
146 for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : getAmbiguousMatches().entrySet()) {
147 for (ClassIdentity sourceClass : entry.getKey()) {
148 conversion.put(
149 sourceClass.getClassEntry().getName(),
150 new AbstractMap.SimpleEntry<ClassIdentity,List<ClassIdentity>>(sourceClass, entry.getValue())
151 );
152 }
153 }
154 for (ClassIdentity sourceClass : getUnmatchedSourceClasses()) {
155 conversion.put(
156 sourceClass.getClassEntry().getName(),
157 new AbstractMap.SimpleEntry<ClassIdentity,List<ClassIdentity>>(sourceClass, getUnmatchedDestClasses())
158 );
159 }
160 return conversion;
161 }
162
163 @Override
164 public String toString() {
165 StringBuilder buf = new StringBuilder();
166 buf.append(String.format("%12s%8s%8s\n", "", "Source", "Dest"));
167 buf.append(String.format("%12s%8d%8d\n", "Classes", getSourceClasses().size(), getDestClasses().size()));
168 buf.append(String.format("%12s%8d%8d\n", "Unique", getUniqueMatches().size(), getUniqueMatches().size()));
169 buf.append(String.format("%12s%8d%8d\n", "Ambiguous", getNumAmbiguousSourceMatches(), getNumAmbiguousDestMatches()));
170 buf.append(String.format("%12s%8d%8d\n", "Unmatched", getUnmatchedSourceClasses().size(), getUnmatchedDestClasses().size()));
171 return buf.toString();
172 }
173}
diff --git a/src/cuchaz/enigma/convert/ClassNamer.java b/src/cuchaz/enigma/convert/ClassNamer.java
new file mode 100644
index 0000000..1b6e81c
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassNamer.java
@@ -0,0 +1,64 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
18public class ClassNamer {
19
20 public interface SidedClassNamer {
21 String getName(String name);
22 }
23
24 private Map<String,String> m_sourceNames;
25 private Map<String,String> m_destNames;
26
27 public ClassNamer(BiMap<ClassIdentity,ClassIdentity> mappings) {
28 // convert the identity mappings to name maps
29 m_sourceNames = Maps.newHashMap();
30 m_destNames = Maps.newHashMap();
31 int i = 0;
32 for (Map.Entry<ClassIdentity,ClassIdentity> entry : mappings.entrySet()) {
33 String name = String.format("M%04d", i++);
34 m_sourceNames.put(entry.getKey().getClassEntry().getName(), name);
35 m_destNames.put(entry.getValue().getClassEntry().getName(), name);
36 }
37 }
38
39 public String getSourceName(String name) {
40 return m_sourceNames.get(name);
41 }
42
43 public String getDestName(String name) {
44 return m_destNames.get(name);
45 }
46
47 public SidedClassNamer getSourceNamer() {
48 return new SidedClassNamer() {
49 @Override
50 public String getName(String name) {
51 return getSourceName(name);
52 }
53 };
54 }
55
56 public SidedClassNamer getDestNamer() {
57 return new SidedClassNamer() {
58 @Override
59 public String getName(String name) {
60 return getDestName(name);
61 }
62 };
63 }
64}
diff --git a/src/cuchaz/enigma/gui/AboutDialog.java b/src/cuchaz/enigma/gui/AboutDialog.java
new file mode 100644
index 0000000..2476b56
--- /dev/null
+++ b/src/cuchaz/enigma/gui/AboutDialog.java
@@ -0,0 +1,86 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..db7c85b
--- /dev/null
+++ b/src/cuchaz/enigma/gui/BoxHighlightPainter.java
@@ -0,0 +1,64 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
new file mode 100644
index 0000000..acee483
--- /dev/null
+++ b/src/cuchaz/enigma/gui/BrowserCaret.java
@@ -0,0 +1,45 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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/ClassListCellRenderer.java b/src/cuchaz/enigma/gui/ClassListCellRenderer.java
new file mode 100644
index 0000000..d0f01e6
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassListCellRenderer.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Component;
14
15import javassist.bytecode.Descriptor;
16
17import javax.swing.DefaultListCellRenderer;
18import javax.swing.JLabel;
19import javax.swing.JList;
20import javax.swing.ListCellRenderer;
21
22public class ClassListCellRenderer implements ListCellRenderer<String> {
23
24 private DefaultListCellRenderer m_defaultRenderer;
25
26 public ClassListCellRenderer() {
27 m_defaultRenderer = new DefaultListCellRenderer();
28 }
29
30 @Override
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);
33 label.setText(Descriptor.toJavaName(className));
34 return label;
35 }
36}
diff --git a/src/cuchaz/enigma/gui/ClassSelector.java b/src/cuchaz/enigma/gui/ClassSelector.java
new file mode 100644
index 0000000..654bfbe
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassSelector.java
@@ -0,0 +1,164 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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.List;
19import java.util.Map;
20
21import javax.swing.JTree;
22import javax.swing.tree.DefaultMutableTreeNode;
23import javax.swing.tree.DefaultTreeModel;
24import javax.swing.tree.TreePath;
25
26import com.google.common.collect.ArrayListMultimap;
27import com.google.common.collect.Lists;
28import com.google.common.collect.Maps;
29import com.google.common.collect.Multimap;
30
31import cuchaz.enigma.mapping.ClassEntry;
32
33public class ClassSelector extends JTree {
34
35 private static final long serialVersionUID = -7632046902384775977L;
36
37 public interface ClassSelectionListener {
38 void onSelectClass(ClassEntry classEntry);
39 }
40
41 public static Comparator<ClassEntry> ObfuscatedClassEntryComparator;
42 public static Comparator<ClassEntry> DeobfuscatedClassEntryComparator;
43
44 static {
45 ObfuscatedClassEntryComparator = new Comparator<ClassEntry>() {
46 @Override
47 public int compare(ClassEntry a, ClassEntry b) {
48 if (a.getName().length() != b.getName().length()) {
49 return a.getName().length() - b.getName().length();
50 }
51 return a.getName().compareTo(b.getName());
52 }
53 };
54
55 DeobfuscatedClassEntryComparator = new Comparator<ClassEntry>() {
56 @Override
57 public int compare(ClassEntry a, ClassEntry b) {
58 return a.getName().compareTo(b.getName());
59 }
60 };
61 }
62
63 private ClassSelectionListener m_listener;
64 private Comparator<ClassEntry> m_comparator;
65
66 public ClassSelector(Comparator<ClassEntry> comparator) {
67 m_comparator = comparator;
68
69 // configure the tree control
70 setRootVisible(false);
71 setShowsRootHandles(false);
72 setModel(null);
73
74 // hook events
75 addMouseListener(new MouseAdapter() {
76 @Override
77 public void mouseClicked(MouseEvent event) {
78 if (m_listener != null && event.getClickCount() == 2) {
79 // get the selected node
80 TreePath path = getSelectionPath();
81 if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) {
82 ClassSelectorClassNode node = (ClassSelectorClassNode)path.getLastPathComponent();
83 m_listener.onSelectClass(node.getClassEntry());
84 }
85 }
86 }
87 });
88
89 // init defaults
90 m_listener = null;
91 }
92
93 public void setListener(ClassSelectionListener val) {
94 m_listener = val;
95 }
96
97 public void setClasses(Collection<ClassEntry> classEntries) {
98 if (classEntries == null) {
99 setModel(null);
100 return;
101 }
102
103 // build the package names
104 Map<String,ClassSelectorPackageNode> packages = Maps.newHashMap();
105 for (ClassEntry classEntry : classEntries) {
106 packages.put(classEntry.getPackageName(), null);
107 }
108
109 // sort the packages
110 List<String> sortedPackageNames = Lists.newArrayList(packages.keySet());
111 Collections.sort(sortedPackageNames, new Comparator<String>() {
112 @Override
113 public int compare(String a, String b) {
114 // I can never keep this rule straight when writing these damn things...
115 // a < b => -1, a == b => 0, a > b => +1
116
117 String[] aparts = a.split("/");
118 String[] bparts = b.split("/");
119 for (int i = 0; true; i++) {
120 if (i >= aparts.length) {
121 return -1;
122 } else if (i >= bparts.length) {
123 return 1;
124 }
125
126 int result = aparts[i].compareTo(bparts[i]);
127 if (result != 0) {
128 return result;
129 }
130 }
131 }
132 });
133
134 // create the root node and the package nodes
135 DefaultMutableTreeNode root = new DefaultMutableTreeNode();
136 for (String packageName : sortedPackageNames) {
137 ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName);
138 packages.put(packageName, node);
139 root.add(node);
140 }
141
142 // put the classes into packages
143 Multimap<String,ClassEntry> packagedClassEntries = ArrayListMultimap.create();
144 for (ClassEntry classEntry : classEntries) {
145 packagedClassEntries.put(classEntry.getPackageName(), classEntry);
146 }
147
148 // build the class nodes
149 for (String packageName : packagedClassEntries.keySet()) {
150 // sort the class entries
151 List<ClassEntry> classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName));
152 Collections.sort(classEntriesInPackage, m_comparator);
153
154 // create the nodes in order
155 for (ClassEntry classEntry : classEntriesInPackage) {
156 ClassSelectorPackageNode node = packages.get(packageName);
157 node.add(new ClassSelectorClassNode(classEntry));
158 }
159 }
160
161 // finally, update the tree control
162 setModel(new DefaultTreeModel(root));
163 }
164}
diff --git a/src/cuchaz/enigma/gui/ClassSelectorClassNode.java b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java
new file mode 100644
index 0000000..66e931b
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java
@@ -0,0 +1,35 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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 return m_classEntry.getSimpleName();
34 }
35}
diff --git a/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java
new file mode 100644
index 0000000..451d380
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java
@@ -0,0 +1,33 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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}
diff --git a/src/cuchaz/enigma/gui/CrashDialog.java b/src/cuchaz/enigma/gui/CrashDialog.java
new file mode 100644
index 0000000..360091a
--- /dev/null
+++ b/src/cuchaz/enigma/gui/CrashDialog.java
@@ -0,0 +1,101 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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/DeobfuscatedHighlightPainter.java b/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java
new file mode 100644
index 0000000..26a3163
--- /dev/null
+++ b/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14
15public class DeobfuscatedHighlightPainter extends BoxHighlightPainter {
16
17 public DeobfuscatedHighlightPainter() {
18 // green ish
19 super(new Color(220, 255, 220), new Color(80, 160, 80));
20 }
21}
diff --git a/src/cuchaz/enigma/gui/Gui.java b/src/cuchaz/enigma/gui/Gui.java
new file mode 100644
index 0000000..e652202
--- /dev/null
+++ b/src/cuchaz/enigma/gui/Gui.java
@@ -0,0 +1,1164 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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.Rectangle;
20import java.awt.event.ActionEvent;
21import java.awt.event.ActionListener;
22import java.awt.event.InputEvent;
23import java.awt.event.KeyAdapter;
24import java.awt.event.KeyEvent;
25import java.awt.event.MouseAdapter;
26import java.awt.event.MouseEvent;
27import java.awt.event.WindowAdapter;
28import java.awt.event.WindowEvent;
29import java.io.File;
30import java.io.IOException;
31import java.lang.Thread.UncaughtExceptionHandler;
32import java.util.Collection;
33import java.util.Collections;
34import java.util.List;
35import java.util.Vector;
36import java.util.jar.JarFile;
37
38import javax.swing.BorderFactory;
39import javax.swing.JEditorPane;
40import javax.swing.JFileChooser;
41import javax.swing.JFrame;
42import javax.swing.JLabel;
43import javax.swing.JList;
44import javax.swing.JMenu;
45import javax.swing.JMenuBar;
46import javax.swing.JMenuItem;
47import javax.swing.JOptionPane;
48import javax.swing.JPanel;
49import javax.swing.JPopupMenu;
50import javax.swing.JScrollPane;
51import javax.swing.JSplitPane;
52import javax.swing.JTabbedPane;
53import javax.swing.JTextField;
54import javax.swing.JTree;
55import javax.swing.KeyStroke;
56import javax.swing.ListSelectionModel;
57import javax.swing.SwingUtilities;
58import javax.swing.Timer;
59import javax.swing.WindowConstants;
60import javax.swing.event.CaretEvent;
61import javax.swing.event.CaretListener;
62import javax.swing.text.BadLocationException;
63import javax.swing.text.Highlighter;
64import javax.swing.tree.DefaultTreeModel;
65import javax.swing.tree.TreeNode;
66import javax.swing.tree.TreePath;
67
68import jsyntaxpane.DefaultSyntaxKit;
69
70import com.google.common.collect.Lists;
71
72import cuchaz.enigma.Constants;
73import cuchaz.enigma.analysis.BehaviorReferenceTreeNode;
74import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
75import cuchaz.enigma.analysis.ClassInheritanceTreeNode;
76import cuchaz.enigma.analysis.EntryReference;
77import cuchaz.enigma.analysis.FieldReferenceTreeNode;
78import cuchaz.enigma.analysis.MethodImplementationsTreeNode;
79import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
80import cuchaz.enigma.analysis.ReferenceTreeNode;
81import cuchaz.enigma.analysis.Token;
82import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
83import cuchaz.enigma.mapping.ArgumentEntry;
84import cuchaz.enigma.mapping.ClassEntry;
85import cuchaz.enigma.mapping.ConstructorEntry;
86import cuchaz.enigma.mapping.Entry;
87import cuchaz.enigma.mapping.FieldEntry;
88import cuchaz.enigma.mapping.IllegalNameException;
89import cuchaz.enigma.mapping.MappingParseException;
90import cuchaz.enigma.mapping.MethodEntry;
91
92public class Gui {
93
94 private GuiController m_controller;
95
96 // controls
97 private JFrame m_frame;
98 private ClassSelector m_obfClasses;
99 private ClassSelector m_deobfClasses;
100 private JEditorPane m_editor;
101 private JPanel m_classesPanel;
102 private JSplitPane m_splitClasses;
103 private JPanel m_infoPanel;
104 private ObfuscatedHighlightPainter m_obfuscatedHighlightPainter;
105 private DeobfuscatedHighlightPainter m_deobfuscatedHighlightPainter;
106 private OtherHighlightPainter m_otherHighlightPainter;
107 private SelectionHighlightPainter m_selectionHighlightPainter;
108 private JTree m_inheritanceTree;
109 private JTree m_implementationsTree;
110 private JTree m_callsTree;
111 private JList<Token> m_tokens;
112 private JTabbedPane m_tabs;
113
114 // dynamic menu items
115 private JMenuItem m_closeJarMenu;
116 private JMenuItem m_openMappingsMenu;
117 private JMenuItem m_saveMappingsMenu;
118 private JMenuItem m_saveMappingsAsMenu;
119 private JMenuItem m_closeMappingsMenu;
120 private JMenuItem m_renameMenu;
121 private JMenuItem m_showInheritanceMenu;
122 private JMenuItem m_openEntryMenu;
123 private JMenuItem m_openPreviousMenu;
124 private JMenuItem m_showCallsMenu;
125 private JMenuItem m_showImplementationsMenu;
126 private JMenuItem m_toggleMappingMenu;
127 private JMenuItem m_exportSourceMenu;
128 private JMenuItem m_exportJarMenu;
129
130 // state
131 private EntryReference<Entry,Entry> m_reference;
132 private JFileChooser m_jarFileChooser;
133 private JFileChooser m_mappingsFileChooser;
134 private JFileChooser m_exportSourceFileChooser;
135 private JFileChooser m_exportJarFileChooser;
136
137 public Gui() {
138
139 // init frame
140 m_frame = new JFrame(Constants.Name);
141 final Container pane = m_frame.getContentPane();
142 pane.setLayout(new BorderLayout());
143
144 if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) {
145 // install a global exception handler to the event thread
146 CrashDialog.init(m_frame);
147 Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
148 @Override
149 public void uncaughtException(Thread thread, Throwable ex) {
150 ex.printStackTrace(System.err);
151 CrashDialog.show(ex);
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, "jsyntaxpane.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
741 // set the caret position to the token
742 m_editor.setCaretPosition(token.start);
743 m_editor.grabFocus();
744
745 try {
746 // make sure the token is visible in the scroll window
747 Rectangle start = m_editor.modelToView(token.start);
748 Rectangle end = m_editor.modelToView(token.end);
749 final Rectangle show = start.union(end);
750 show.grow(start.width * 10, start.height * 6);
751 SwingUtilities.invokeLater(new Runnable() {
752 @Override
753 public void run() {
754 m_editor.scrollRectToVisible(show);
755 }
756 });
757 } catch (BadLocationException ex) {
758 throw new Error(ex);
759 }
760
761 // highlight the token momentarily
762 final Timer timer = new Timer(200, new ActionListener() {
763 private int m_counter = 0;
764 private Object m_highlight = null;
765
766 @Override
767 public void actionPerformed(ActionEvent event) {
768 if (m_counter % 2 == 0) {
769 try {
770 m_highlight = m_editor.getHighlighter().addHighlight(token.start, token.end, m_selectionHighlightPainter);
771 } catch (BadLocationException ex) {
772 // don't care
773 }
774 } else if (m_highlight != null) {
775 m_editor.getHighlighter().removeHighlight(m_highlight);
776 }
777
778 if (m_counter++ > 6) {
779 Timer timer = (Timer)event.getSource();
780 timer.stop();
781 }
782 }
783 });
784 timer.start();
785
786 redraw();
787 }
788
789 public void showTokens(Collection<Token> tokens) {
790 Vector<Token> sortedTokens = new Vector<Token>(tokens);
791 Collections.sort(sortedTokens);
792 if (sortedTokens.size() > 1) {
793 // sort the tokens and update the tokens panel
794 m_tokens.setListData(sortedTokens);
795 m_tokens.setSelectedIndex(0);
796 } else {
797 m_tokens.setListData(new Vector<Token>());
798 }
799
800 // show the first token
801 showToken(sortedTokens.get(0));
802 }
803
804 public void setHighlightedTokens(Iterable<Token> obfuscatedTokens, Iterable<Token> deobfuscatedTokens, Iterable<Token> otherTokens) {
805
806 // remove any old highlighters
807 m_editor.getHighlighter().removeAllHighlights();
808
809 // color things based on the index
810 if (obfuscatedTokens != null) {
811 setHighlightedTokens(obfuscatedTokens, m_obfuscatedHighlightPainter);
812 }
813 if (deobfuscatedTokens != null) {
814 setHighlightedTokens(deobfuscatedTokens, m_deobfuscatedHighlightPainter);
815 }
816 if (otherTokens != null) {
817 setHighlightedTokens(otherTokens, m_otherHighlightPainter);
818 }
819
820 redraw();
821 }
822
823 private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) {
824 for (Token token : tokens) {
825 try {
826 m_editor.getHighlighter().addHighlight(token.start, token.end, painter);
827 } catch (BadLocationException ex) {
828 throw new IllegalArgumentException(ex);
829 }
830 }
831 }
832
833 private void clearReference() {
834 m_infoPanel.removeAll();
835 JLabel label = new JLabel("No identifier selected");
836 GuiTricks.unboldLabel(label);
837 label.setHorizontalAlignment(JLabel.CENTER);
838 m_infoPanel.add(label);
839
840 redraw();
841 }
842
843 private void showReference(EntryReference<Entry,Entry> reference) {
844 if (reference == null) {
845 clearReference();
846 return;
847 }
848
849 m_reference = reference;
850
851 m_infoPanel.removeAll();
852 if (reference.entry instanceof ClassEntry) {
853 showClassEntry((ClassEntry)m_reference.entry);
854 } else if (m_reference.entry instanceof FieldEntry) {
855 showFieldEntry((FieldEntry)m_reference.entry);
856 } else if (m_reference.entry instanceof MethodEntry) {
857 showMethodEntry((MethodEntry)m_reference.entry);
858 } else if (m_reference.entry instanceof ConstructorEntry) {
859 showConstructorEntry((ConstructorEntry)m_reference.entry);
860 } else if (m_reference.entry instanceof ArgumentEntry) {
861 showArgumentEntry((ArgumentEntry)m_reference.entry);
862 } else {
863 throw new Error("Unknown entry type: " + m_reference.entry.getClass().getName());
864 }
865
866 redraw();
867 }
868
869 private void showClassEntry(ClassEntry entry) {
870 addNameValue(m_infoPanel, "Class", entry.getName());
871 }
872
873 private void showFieldEntry(FieldEntry entry) {
874 addNameValue(m_infoPanel, "Field", entry.getName());
875 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
876 }
877
878 private void showMethodEntry(MethodEntry entry) {
879 addNameValue(m_infoPanel, "Method", entry.getName());
880 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
881 addNameValue(m_infoPanel, "Signature", entry.getSignature());
882 }
883
884 private void showConstructorEntry(ConstructorEntry entry) {
885 addNameValue(m_infoPanel, "Constructor", entry.getClassEntry().getName());
886 addNameValue(m_infoPanel, "Signature", entry.getSignature());
887 }
888
889 private void showArgumentEntry(ArgumentEntry entry) {
890 addNameValue(m_infoPanel, "Argument", entry.getName());
891 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
892 addNameValue(m_infoPanel, "Method", entry.getBehaviorEntry().getName());
893 addNameValue(m_infoPanel, "Index", Integer.toString(entry.getIndex()));
894 }
895
896 private void addNameValue(JPanel container, String name, String value) {
897 JPanel panel = new JPanel();
898 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
899 container.add(panel);
900
901 JLabel label = new JLabel(name + ":", JLabel.RIGHT);
902 label.setPreferredSize(new Dimension(100, label.getPreferredSize().height));
903 panel.add(label);
904
905 panel.add(GuiTricks.unboldLabel(new JLabel(value, JLabel.LEFT)));
906 }
907
908 private void onCaretMove(int pos) {
909
910 Token token = m_controller.getToken(pos);
911 boolean isToken = token != null;
912
913 m_reference = m_controller.getDeobfReference(token);
914 boolean isClassEntry = isToken && m_reference.entry instanceof ClassEntry;
915 boolean isFieldEntry = isToken && m_reference.entry instanceof FieldEntry;
916 boolean isMethodEntry = isToken && m_reference.entry instanceof MethodEntry;
917 boolean isConstructorEntry = isToken && m_reference.entry instanceof ConstructorEntry;
918 boolean isInJar = isToken && m_controller.entryIsInJar(m_reference.entry);
919 boolean isRenameable = isToken && m_controller.referenceIsRenameable(m_reference);
920
921 if (isToken) {
922 showReference(m_reference);
923 } else {
924 clearReference();
925 }
926
927 m_renameMenu.setEnabled(isRenameable && isToken);
928 m_showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry);
929 m_showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry);
930 m_showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry);
931 m_openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry));
932 m_openPreviousMenu.setEnabled(m_controller.hasPreviousLocation());
933 m_toggleMappingMenu.setEnabled(isRenameable && isToken);
934
935 if (isToken && m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
936 m_toggleMappingMenu.setText("Reset to obfuscated");
937 } else {
938 m_toggleMappingMenu.setText("Mark as deobfuscated");
939 }
940 }
941
942 private void navigateTo(Entry entry) {
943 if (!m_controller.entryIsInJar(entry)) {
944 // entry is not in the jar. Ignore it
945 return;
946 }
947 if (m_reference != null) {
948 m_controller.savePreviousReference(m_reference);
949 }
950 m_controller.openDeclaration(entry);
951 }
952
953 private void navigateTo(EntryReference<Entry,Entry> reference) {
954 if (!m_controller.entryIsInJar(reference.getLocationClassEntry())) {
955 // reference is not in the jar. Ignore it
956 return;
957 }
958 if (m_reference != null) {
959 m_controller.savePreviousReference(m_reference);
960 }
961 m_controller.openReference(reference);
962 }
963
964 private void startRename() {
965
966 // init the text box
967 final JTextField text = new JTextField();
968 text.setText(m_reference.getNamableName());
969 text.setPreferredSize(new Dimension(360, text.getPreferredSize().height));
970 text.addKeyListener(new KeyAdapter() {
971 @Override
972 public void keyPressed(KeyEvent event) {
973 switch (event.getKeyCode()) {
974 case KeyEvent.VK_ENTER:
975 finishRename(text, true);
976 break;
977
978 case KeyEvent.VK_ESCAPE:
979 finishRename(text, false);
980 break;
981 }
982 }
983 });
984
985 // find the label with the name and replace it with the text box
986 JPanel panel = (JPanel)m_infoPanel.getComponent(0);
987 panel.remove(panel.getComponentCount() - 1);
988 panel.add(text);
989 text.grabFocus();
990 text.selectAll();
991
992 redraw();
993 }
994
995 private void finishRename(JTextField text, boolean saveName) {
996 String newName = text.getText();
997 if (saveName && newName != null && newName.length() > 0) {
998 try {
999 m_controller.rename(m_reference, newName);
1000 } catch (IllegalNameException ex) {
1001 text.setBorder(BorderFactory.createLineBorder(Color.red, 1));
1002 text.setToolTipText(ex.getReason());
1003 GuiTricks.showToolTipNow(text);
1004 }
1005 return;
1006 }
1007
1008 // abort the rename
1009 JPanel panel = (JPanel)m_infoPanel.getComponent(0);
1010 panel.remove(panel.getComponentCount() - 1);
1011 panel.add(GuiTricks.unboldLabel(new JLabel(m_reference.getNamableName(), JLabel.LEFT)));
1012
1013 m_editor.grabFocus();
1014
1015 redraw();
1016 }
1017
1018 private void showInheritance() {
1019
1020 if (m_reference == null) {
1021 return;
1022 }
1023
1024 m_inheritanceTree.setModel(null);
1025
1026 if (m_reference.entry instanceof ClassEntry) {
1027 // get the class inheritance
1028 ClassInheritanceTreeNode classNode = m_controller.getClassInheritance((ClassEntry)m_reference.entry);
1029
1030 // show the tree at the root
1031 TreePath path = getPathToRoot(classNode);
1032 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1033 m_inheritanceTree.expandPath(path);
1034 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
1035 } else if (m_reference.entry instanceof MethodEntry) {
1036 // get the method inheritance
1037 MethodInheritanceTreeNode classNode = m_controller.getMethodInheritance((MethodEntry)m_reference.entry);
1038
1039 // show the tree at the root
1040 TreePath path = getPathToRoot(classNode);
1041 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1042 m_inheritanceTree.expandPath(path);
1043 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
1044 }
1045
1046 m_tabs.setSelectedIndex(0);
1047 redraw();
1048 }
1049
1050 private void showImplementations() {
1051
1052 if (m_reference == null) {
1053 return;
1054 }
1055
1056 m_implementationsTree.setModel(null);
1057
1058 if (m_reference.entry instanceof ClassEntry) {
1059 // get the class implementations
1060 ClassImplementationsTreeNode node = m_controller.getClassImplementations((ClassEntry)m_reference.entry);
1061 if (node != null) {
1062 // show the tree at the root
1063 TreePath path = getPathToRoot(node);
1064 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1065 m_implementationsTree.expandPath(path);
1066 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1067 }
1068 } else if (m_reference.entry instanceof MethodEntry) {
1069 // get the method implementations
1070 MethodImplementationsTreeNode node = m_controller.getMethodImplementations((MethodEntry)m_reference.entry);
1071 if (node != null) {
1072 // show the tree at the root
1073 TreePath path = getPathToRoot(node);
1074 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1075 m_implementationsTree.expandPath(path);
1076 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1077 }
1078 }
1079
1080 m_tabs.setSelectedIndex(1);
1081 redraw();
1082 }
1083
1084 private void showCalls() {
1085
1086 if (m_reference == null) {
1087 return;
1088 }
1089
1090 if (m_reference.entry instanceof ClassEntry) {
1091 // look for calls to the default constructor
1092 // TODO: get a list of all the constructors and find calls to all of them
1093 BehaviorReferenceTreeNode node = m_controller.getMethodReferences(new ConstructorEntry((ClassEntry)m_reference.entry, "()V"));
1094 m_callsTree.setModel(new DefaultTreeModel(node));
1095 } else if (m_reference.entry instanceof FieldEntry) {
1096 FieldReferenceTreeNode node = m_controller.getFieldReferences((FieldEntry)m_reference.entry);
1097 m_callsTree.setModel(new DefaultTreeModel(node));
1098 } else if (m_reference.entry instanceof MethodEntry) {
1099 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((MethodEntry)m_reference.entry);
1100 m_callsTree.setModel(new DefaultTreeModel(node));
1101 } else if (m_reference.entry instanceof ConstructorEntry) {
1102 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((ConstructorEntry)m_reference.entry);
1103 m_callsTree.setModel(new DefaultTreeModel(node));
1104 }
1105
1106 m_tabs.setSelectedIndex(2);
1107 redraw();
1108 }
1109
1110 private void toggleMapping() {
1111 if (m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
1112 m_controller.removeMapping(m_reference);
1113 } else {
1114 m_controller.markAsDeobfuscated(m_reference);
1115 }
1116 }
1117
1118 private TreePath getPathToRoot(TreeNode node) {
1119 List<TreeNode> nodes = Lists.newArrayList();
1120 TreeNode n = node;
1121 do {
1122 nodes.add(n);
1123 n = n.getParent();
1124 } while (n != null);
1125 Collections.reverse(nodes);
1126 return new TreePath(nodes.toArray());
1127 }
1128
1129 private void close() {
1130 if (!m_controller.isDirty()) {
1131 // everything is saved, we can exit safely
1132 m_frame.dispose();
1133 } else {
1134 // ask to save before closing
1135 String[] options = { "Save and exit", "Discard changes", "Cancel" };
1136 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,
1137 JOptionPane.QUESTION_MESSAGE, null, options, options[2]);
1138 switch (response) {
1139 case JOptionPane.YES_OPTION: // save and exit
1140 if (m_mappingsFileChooser.getSelectedFile() != null || m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
1141 try {
1142 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
1143 m_frame.dispose();
1144 } catch (IOException ex) {
1145 throw new Error(ex);
1146 }
1147 }
1148 break;
1149
1150 case JOptionPane.NO_OPTION:
1151 // don't save, exit
1152 m_frame.dispose();
1153 break;
1154
1155 // cancel means do nothing
1156 }
1157 }
1158 }
1159
1160 private void redraw() {
1161 m_frame.validate();
1162 m_frame.repaint();
1163 }
1164}
diff --git a/src/cuchaz/enigma/gui/GuiController.java b/src/cuchaz/enigma/gui/GuiController.java
new file mode 100644
index 0000000..61fea9c
--- /dev/null
+++ b/src/cuchaz/enigma/gui/GuiController.java
@@ -0,0 +1,355 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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 MethodImplementationsTreeNode rootNode = m_deobfuscator.getJarIndex().getMethodImplementations(
190 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
191 obfMethodEntry
192 );
193 if (rootNode == null) {
194 return null;
195 }
196 return MethodImplementationsTreeNode.findNode(rootNode, obfMethodEntry);
197 }
198
199 public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) {
200 FieldEntry obfFieldEntry = m_deobfuscator.obfuscateEntry(deobfFieldEntry);
201 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(
202 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
203 obfFieldEntry
204 );
205 rootNode.load(m_deobfuscator.getJarIndex(), true);
206 return rootNode;
207 }
208
209 public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) {
210 BehaviorEntry obfBehaviorEntry = m_deobfuscator.obfuscateEntry(deobfBehaviorEntry);
211 BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode(
212 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
213 obfBehaviorEntry
214 );
215 rootNode.load(m_deobfuscator.getJarIndex(), true);
216 return rootNode;
217 }
218
219 public void rename(EntryReference<Entry,Entry> deobfReference, String newName) {
220 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
221 m_deobfuscator.rename(obfReference.getNameableEntry(), newName);
222 m_isDirty = true;
223 refreshClasses();
224 refreshCurrentClass(obfReference);
225 }
226
227 public void removeMapping(EntryReference<Entry,Entry> deobfReference) {
228 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
229 m_deobfuscator.removeMapping(obfReference.getNameableEntry());
230 m_isDirty = true;
231 refreshClasses();
232 refreshCurrentClass(obfReference);
233 }
234
235 public void markAsDeobfuscated(EntryReference<Entry,Entry> deobfReference) {
236 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
237 m_deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry());
238 m_isDirty = true;
239 refreshClasses();
240 refreshCurrentClass(obfReference);
241 }
242
243 public void openDeclaration(Entry deobfEntry) {
244 if (deobfEntry == null) {
245 throw new IllegalArgumentException("Entry cannot be null!");
246 }
247 openReference(new EntryReference<Entry,Entry>(deobfEntry, deobfEntry.getName()));
248 }
249
250 public void openReference(EntryReference<Entry,Entry> deobfReference) {
251 if (deobfReference == null) {
252 throw new IllegalArgumentException("Reference cannot be null!");
253 }
254
255 // get the reference target class
256 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
257 ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOuterClassEntry();
258 if (!m_deobfuscator.isObfuscatedIdentifier(obfClassEntry)) {
259 throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!");
260 }
261 if (m_currentObfClass == null || !m_currentObfClass.equals(obfClassEntry)) {
262 // deobfuscate the class, then navigate to the reference
263 m_currentObfClass = obfClassEntry;
264 deobfuscate(m_currentObfClass, obfReference);
265 } else {
266 showReference(obfReference);
267 }
268 }
269
270 private void showReference(EntryReference<Entry,Entry> obfReference) {
271 EntryReference<Entry,Entry> deobfReference = m_deobfuscator.deobfuscateReference(obfReference);
272 Collection<Token> tokens = m_index.getReferenceTokens(deobfReference);
273 if (tokens.isEmpty()) {
274 // DEBUG
275 System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, m_currentObfClass));
276 } else {
277 m_gui.showTokens(tokens);
278 }
279 }
280
281 public void savePreviousReference(EntryReference<Entry,Entry> deobfReference) {
282 m_referenceStack.push(m_deobfuscator.obfuscateReference(deobfReference));
283 }
284
285 public void openPreviousReference() {
286 if (hasPreviousLocation()) {
287 openReference(m_deobfuscator.deobfuscateReference(m_referenceStack.pop()));
288 }
289 }
290
291 public boolean hasPreviousLocation() {
292 return !m_referenceStack.isEmpty();
293 }
294
295 private void refreshClasses() {
296 List<ClassEntry> obfClasses = Lists.newArrayList();
297 List<ClassEntry> deobfClasses = Lists.newArrayList();
298 m_deobfuscator.getSeparatedClasses(obfClasses, deobfClasses);
299 m_gui.setObfClasses(obfClasses);
300 m_gui.setDeobfClasses(deobfClasses);
301 }
302
303 private void refreshCurrentClass() {
304 refreshCurrentClass(null);
305 }
306
307 private void refreshCurrentClass(EntryReference<Entry,Entry> obfReference) {
308 if (m_currentObfClass != null) {
309 deobfuscate(m_currentObfClass, obfReference);
310 }
311 }
312
313 private void deobfuscate(final ClassEntry classEntry, final EntryReference<Entry,Entry> obfReference) {
314
315 m_gui.setSource("(deobfuscating...)");
316
317 // run the deobfuscator in a separate thread so we don't block the GUI event queue
318 new Thread() {
319 @Override
320 public void run() {
321 // decompile,deobfuscate the bytecode
322 CompilationUnit sourceTree = m_deobfuscator.getSourceTree(classEntry.getClassName());
323 if (sourceTree == null) {
324 // decompilation of this class is not supported
325 m_gui.setSource("Unable to find class: " + classEntry);
326 return;
327 }
328 String source = m_deobfuscator.getSource(sourceTree);
329 m_index = m_deobfuscator.getSourceIndex(sourceTree, source);
330 m_gui.setSource(m_index.getSource());
331 if (obfReference != null) {
332 showReference(obfReference);
333 }
334
335 // set the highlighted tokens
336 List<Token> obfuscatedTokens = Lists.newArrayList();
337 List<Token> deobfuscatedTokens = Lists.newArrayList();
338 List<Token> otherTokens = Lists.newArrayList();
339 for (Token token : m_index.referenceTokens()) {
340 EntryReference<Entry,Entry> reference = m_index.getDeobfReference(token);
341 if (referenceIsRenameable(reference)) {
342 if (entryHasDeobfuscatedName(reference.getNameableEntry())) {
343 deobfuscatedTokens.add(token);
344 } else {
345 obfuscatedTokens.add(token);
346 }
347 } else {
348 otherTokens.add(token);
349 }
350 }
351 m_gui.setHighlightedTokens(obfuscatedTokens, deobfuscatedTokens, otherTokens);
352 }
353 }.start();
354 }
355}
diff --git a/src/cuchaz/enigma/gui/GuiTricks.java b/src/cuchaz/enigma/gui/GuiTricks.java
new file mode 100644
index 0000000..df9e221
--- /dev/null
+++ b/src/cuchaz/enigma/gui/GuiTricks.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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.MouseEvent;
15
16import javax.swing.JComponent;
17import javax.swing.JLabel;
18import javax.swing.ToolTipManager;
19
20public class GuiTricks {
21
22 public static JLabel unboldLabel(JLabel label) {
23 Font font = label.getFont();
24 label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD));
25 return label;
26 }
27
28 public static void showToolTipNow(JComponent component) {
29 // HACKHACK: trick the tooltip manager into showing the tooltip right now
30 ToolTipManager manager = ToolTipManager.sharedInstance();
31 int oldDelay = manager.getInitialDelay();
32 manager.setInitialDelay(0);
33 manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false));
34 manager.setInitialDelay(oldDelay);
35 }
36}
diff --git a/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java b/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java
new file mode 100644
index 0000000..177835f
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14
15public class ObfuscatedHighlightPainter extends BoxHighlightPainter {
16
17 public ObfuscatedHighlightPainter() {
18 // red ish
19 super(new Color(255, 220, 220), new Color(160, 80, 80));
20 }
21}
diff --git a/src/cuchaz/enigma/gui/OtherHighlightPainter.java b/src/cuchaz/enigma/gui/OtherHighlightPainter.java
new file mode 100644
index 0000000..4e9c870
--- /dev/null
+++ b/src/cuchaz/enigma/gui/OtherHighlightPainter.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Color;
14
15public class OtherHighlightPainter extends BoxHighlightPainter {
16
17 public OtherHighlightPainter() {
18 // grey
19 super(null, new Color(180, 180, 180));
20 }
21}
diff --git a/src/cuchaz/enigma/gui/ProgressDialog.java b/src/cuchaz/enigma/gui/ProgressDialog.java
new file mode 100644
index 0000000..b864fdb
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ProgressDialog.java
@@ -0,0 +1,105 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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/gui/ReadableToken.java b/src/cuchaz/enigma/gui/ReadableToken.java
new file mode 100644
index 0000000..66bcbc2
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ReadableToken.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
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/cuchaz/enigma/gui/RenameListener.java
new file mode 100644
index 0000000..abeda0c
--- /dev/null
+++ b/src/cuchaz/enigma/gui/RenameListener.java
@@ -0,0 +1,17 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import cuchaz.enigma.mapping.Entry;
14
15public interface RenameListener {
16 void rename(Entry obfEntry, String newName);
17}
diff --git a/src/cuchaz/enigma/gui/SelectionHighlightPainter.java b/src/cuchaz/enigma/gui/SelectionHighlightPainter.java
new file mode 100644
index 0000000..5e189d2
--- /dev/null
+++ b/src/cuchaz/enigma/gui/SelectionHighlightPainter.java
@@ -0,0 +1,34 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BasicStroke;
14import java.awt.Color;
15import java.awt.Graphics;
16import java.awt.Graphics2D;
17import java.awt.Rectangle;
18import java.awt.Shape;
19
20import javax.swing.text.Highlighter;
21import javax.swing.text.JTextComponent;
22
23public class SelectionHighlightPainter implements Highlighter.HighlightPainter {
24
25 @Override
26 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) {
27 // draw a thick border
28 Graphics2D g2d = (Graphics2D)g;
29 Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end);
30 g2d.setColor(Color.black);
31 g2d.setStroke(new BasicStroke(2.0f));
32 g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
33 }
34}
diff --git a/src/cuchaz/enigma/gui/TokenListCellRenderer.java b/src/cuchaz/enigma/gui/TokenListCellRenderer.java
new file mode 100644
index 0000000..a49be37
--- /dev/null
+++ b/src/cuchaz/enigma/gui/TokenListCellRenderer.java
@@ -0,0 +1,38 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Component;
14
15import javax.swing.DefaultListCellRenderer;
16import javax.swing.JLabel;
17import javax.swing.JList;
18import javax.swing.ListCellRenderer;
19
20import cuchaz.enigma.analysis.Token;
21
22public class TokenListCellRenderer implements ListCellRenderer<Token> {
23
24 private GuiController m_controller;
25 private DefaultListCellRenderer m_defaultRenderer;
26
27 public TokenListCellRenderer(GuiController controller) {
28 m_controller = controller;
29 m_defaultRenderer = new DefaultListCellRenderer();
30 }
31
32 @Override
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);
35 label.setText(m_controller.getReadableToken(token).toString());
36 return label;
37 }
38}
diff --git a/src/cuchaz/enigma/mapping/ArgumentEntry.java b/src/cuchaz/enigma/mapping/ArgumentEntry.java
new file mode 100644
index 0000000..2c15f4e
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ArgumentEntry.java
@@ -0,0 +1,116 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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 String 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
new file mode 100644
index 0000000..f4d8e77
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ArgumentMapping.java
@@ -0,0 +1,44 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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 int getIndex() {
29 return m_index;
30 }
31
32 public String getName() {
33 return m_name;
34 }
35
36 public void setName(String val) {
37 m_name = NameValidator.validateArgumentName(val);
38 }
39
40 @Override
41 public int compareTo(ArgumentMapping other) {
42 return Integer.compare(m_index, other.m_index);
43 }
44}
diff --git a/src/cuchaz/enigma/mapping/BehaviorEntry.java b/src/cuchaz/enigma/mapping/BehaviorEntry.java
new file mode 100644
index 0000000..f4200b8
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/BehaviorEntry.java
@@ -0,0 +1,15 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public interface BehaviorEntry extends Entry {
14 String getSignature();
15}
diff --git a/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java b/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java
new file mode 100644
index 0000000..386faeb
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java
@@ -0,0 +1,57 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import javassist.CtBehavior;
14import javassist.CtConstructor;
15import javassist.CtMethod;
16import javassist.bytecode.Descriptor;
17
18public class BehaviorEntryFactory {
19
20 public static BehaviorEntry create(String className, String name, String signature) {
21 return create(new ClassEntry(className), name, signature);
22 }
23
24 public static BehaviorEntry create(ClassEntry classEntry, String name, String signature) {
25 if (name.equals("<init>")) {
26 return new ConstructorEntry(classEntry, signature);
27 } else if (name.equals("<clinit>")) {
28 return new ConstructorEntry(classEntry);
29 } else {
30 return new MethodEntry(classEntry, name, signature);
31 }
32 }
33
34 public static BehaviorEntry create(CtBehavior behavior) {
35 String className = Descriptor.toJvmName(behavior.getDeclaringClass().getName());
36 if (behavior instanceof CtMethod) {
37 return create(className, behavior.getName(), behavior.getSignature());
38 } else if (behavior instanceof CtConstructor) {
39 CtConstructor constructor = (CtConstructor)behavior;
40 if (constructor.isClassInitializer()) {
41 return create(className, "<clinit>", null);
42 } else {
43 return create(className, "<init>", constructor.getSignature());
44 }
45 } else {
46 throw new IllegalArgumentException("Unable to create BehaviorEntry from " + behavior);
47 }
48 }
49
50 public static BehaviorEntry createObf(ClassEntry classEntry, MethodMapping methodMapping) {
51 return create(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature());
52 }
53
54 public static BehaviorEntry createDeobf(ClassEntry classEntry, MethodMapping methodMapping) {
55 return create(classEntry, methodMapping.getDeobfName(), methodMapping.getObfSignature());
56 }
57}
diff --git a/src/cuchaz/enigma/mapping/ClassEntry.java b/src/cuchaz/enigma/mapping/ClassEntry.java
new file mode 100644
index 0000000..cf41001
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ClassEntry.java
@@ -0,0 +1,123 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15public class ClassEntry implements Entry, Serializable {
16
17 private static final long serialVersionUID = 4235460580973955811L;
18
19 private String m_name;
20
21 public ClassEntry(String className) {
22 if (className == null) {
23 throw new IllegalArgumentException("Class name cannot be null!");
24 }
25 if (className.indexOf('.') >= 0) {
26 throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className);
27 }
28
29 m_name = className;
30
31 if (isInnerClass() && getInnerClassName().indexOf('/') >= 0) {
32 throw new IllegalArgumentException("Inner class must not have a package: " + className);
33 }
34 }
35
36 public ClassEntry(ClassEntry other) {
37 m_name = other.m_name;
38 }
39
40 @Override
41 public String getName() {
42 return m_name;
43 }
44
45 @Override
46 public String getClassName() {
47 return m_name;
48 }
49
50 @Override
51 public ClassEntry getClassEntry() {
52 return this;
53 }
54
55 @Override
56 public ClassEntry cloneToNewClass(ClassEntry classEntry) {
57 return classEntry;
58 }
59
60 @Override
61 public int hashCode() {
62 return m_name.hashCode();
63 }
64
65 @Override
66 public boolean equals(Object other) {
67 if (other instanceof ClassEntry) {
68 return equals((ClassEntry)other);
69 }
70 return false;
71 }
72
73 public boolean equals(ClassEntry other) {
74 return m_name.equals(other.m_name);
75 }
76
77 @Override
78 public String toString() {
79 return m_name;
80 }
81
82 public boolean isInnerClass() {
83 return m_name.lastIndexOf('$') >= 0;
84 }
85
86 public String getOuterClassName() {
87 if (isInnerClass()) {
88 return m_name.substring(0, m_name.lastIndexOf('$'));
89 }
90 return m_name;
91 }
92
93 public String getInnerClassName() {
94 if (!isInnerClass()) {
95 throw new Error("This is not an inner class!");
96 }
97 return m_name.substring(m_name.lastIndexOf('$') + 1);
98 }
99
100 public ClassEntry getOuterClassEntry() {
101 return new ClassEntry(getOuterClassName());
102 }
103
104 public boolean isInDefaultPackage() {
105 return m_name.indexOf('/') < 0;
106 }
107
108 public String getPackageName() {
109 int pos = m_name.lastIndexOf('/');
110 if (pos > 0) {
111 return m_name.substring(0, pos);
112 }
113 return null;
114 }
115
116 public String getSimpleName() {
117 int pos = m_name.lastIndexOf('/');
118 if (pos > 0) {
119 return m_name.substring(pos + 1);
120 }
121 return m_name;
122 }
123}
diff --git a/src/cuchaz/enigma/mapping/ClassMapping.java b/src/cuchaz/enigma/mapping/ClassMapping.java
new file mode 100644
index 0000000..dbb8717
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ClassMapping.java
@@ -0,0 +1,405 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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_obfName;
24 private String m_deobfName;
25 private Map<String,ClassMapping> m_innerClassesByObf;
26 private Map<String,ClassMapping> m_innerClassesByDeobf;
27 private Map<String,FieldMapping> m_fieldsByObf;
28 private Map<String,FieldMapping> m_fieldsByDeobf;
29 private Map<String,MethodMapping> m_methodsByObf;
30 private Map<String,MethodMapping> m_methodsByDeobf;
31
32 public ClassMapping(String obfName) {
33 this(obfName, null);
34 }
35
36 public ClassMapping(String obfName, String deobfName) {
37 m_obfName = obfName;
38 m_deobfName = NameValidator.validateClassName(deobfName, false);
39 m_innerClassesByObf = Maps.newHashMap();
40 m_innerClassesByDeobf = Maps.newHashMap();
41 m_fieldsByObf = Maps.newHashMap();
42 m_fieldsByDeobf = Maps.newHashMap();
43 m_methodsByObf = Maps.newHashMap();
44 m_methodsByDeobf = Maps.newHashMap();
45 }
46
47 public String getObfName() {
48 return m_obfName;
49 }
50
51 public String getDeobfName() {
52 return m_deobfName;
53 }
54
55 public void setDeobfName(String val) {
56 m_deobfName = NameValidator.validateClassName(val, false);
57 }
58
59 //// INNER CLASSES ////////
60
61 public Iterable<ClassMapping> innerClasses() {
62 assert (m_innerClassesByObf.size() >= m_innerClassesByDeobf.size());
63 return m_innerClassesByObf.values();
64 }
65
66 public void addInnerClassMapping(ClassMapping classMapping) {
67 assert (isSimpleClassName(classMapping.getObfName()));
68 boolean obfWasAdded = m_innerClassesByObf.put(classMapping.getObfName(), classMapping) == null;
69 assert (obfWasAdded);
70 if (classMapping.getDeobfName() != null) {
71 assert (isSimpleClassName(classMapping.getDeobfName()));
72 boolean deobfWasAdded = m_innerClassesByDeobf.put(classMapping.getDeobfName(), classMapping) == null;
73 assert (deobfWasAdded);
74 }
75 }
76
77 public void removeInnerClassMapping(ClassMapping classMapping) {
78 boolean obfWasRemoved = m_innerClassesByObf.remove(classMapping.getObfName()) != null;
79 assert (obfWasRemoved);
80 if (classMapping.getDeobfName() != null) {
81 boolean deobfWasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null;
82 assert (deobfWasRemoved);
83 }
84 }
85
86 public ClassMapping getOrCreateInnerClass(String obfName) {
87 assert (isSimpleClassName(obfName));
88 ClassMapping classMapping = m_innerClassesByObf.get(obfName);
89 if (classMapping == null) {
90 classMapping = new ClassMapping(obfName);
91 boolean wasAdded = m_innerClassesByObf.put(obfName, classMapping) == null;
92 assert (wasAdded);
93 }
94 return classMapping;
95 }
96
97 public ClassMapping getInnerClassByObf(String obfName) {
98 assert (isSimpleClassName(obfName));
99 return m_innerClassesByObf.get(obfName);
100 }
101
102 public ClassMapping getInnerClassByDeobf(String deobfName) {
103 assert (isSimpleClassName(deobfName));
104 return m_innerClassesByDeobf.get(deobfName);
105 }
106
107 public ClassMapping getInnerClassByDeobfThenObf(String name) {
108 ClassMapping classMapping = getInnerClassByDeobf(name);
109 if (classMapping == null) {
110 classMapping = getInnerClassByObf(name);
111 }
112 return classMapping;
113 }
114
115 public String getObfInnerClassName(String deobfName) {
116 assert (isSimpleClassName(deobfName));
117 ClassMapping classMapping = m_innerClassesByDeobf.get(deobfName);
118 if (classMapping != null) {
119 return classMapping.getObfName();
120 }
121 return null;
122 }
123
124 public String getDeobfInnerClassName(String obfName) {
125 assert (isSimpleClassName(obfName));
126 ClassMapping classMapping = m_innerClassesByObf.get(obfName);
127 if (classMapping != null) {
128 return classMapping.getDeobfName();
129 }
130 return null;
131 }
132
133 public void setInnerClassName(String obfName, String deobfName) {
134 assert (isSimpleClassName(obfName));
135 ClassMapping classMapping = getOrCreateInnerClass(obfName);
136 if (classMapping.getDeobfName() != null) {
137 boolean wasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null;
138 assert (wasRemoved);
139 }
140 classMapping.setDeobfName(deobfName);
141 if (deobfName != null) {
142 assert (isSimpleClassName(deobfName));
143 boolean wasAdded = m_innerClassesByDeobf.put(deobfName, classMapping) == null;
144 assert (wasAdded);
145 }
146 }
147
148 //// FIELDS ////////
149
150 public Iterable<FieldMapping> fields() {
151 assert (m_fieldsByObf.size() == m_fieldsByDeobf.size());
152 return m_fieldsByObf.values();
153 }
154
155 public boolean containsObfField(String obfName) {
156 return m_fieldsByObf.containsKey(obfName);
157 }
158
159 public boolean containsDeobfField(String deobfName) {
160 return m_fieldsByDeobf.containsKey(deobfName);
161 }
162
163 public void addFieldMapping(FieldMapping fieldMapping) {
164 if (m_fieldsByObf.containsKey(fieldMapping.getObfName())) {
165 throw new Error("Already have mapping for " + m_obfName + "." + fieldMapping.getObfName());
166 }
167 if (m_fieldsByDeobf.containsKey(fieldMapping.getDeobfName())) {
168 throw new Error("Already have mapping for " + m_deobfName + "." + fieldMapping.getDeobfName());
169 }
170 boolean obfWasAdded = m_fieldsByObf.put(fieldMapping.getObfName(), fieldMapping) == null;
171 assert (obfWasAdded);
172 boolean deobfWasAdded = m_fieldsByDeobf.put(fieldMapping.getDeobfName(), fieldMapping) == null;
173 assert (deobfWasAdded);
174 assert (m_fieldsByObf.size() == m_fieldsByDeobf.size());
175 }
176
177 public void removeFieldMapping(FieldMapping fieldMapping) {
178 boolean obfWasRemoved = m_fieldsByObf.remove(fieldMapping.getObfName()) != null;
179 assert (obfWasRemoved);
180 if (fieldMapping.getDeobfName() != null) {
181 boolean deobfWasRemoved = m_fieldsByDeobf.remove(fieldMapping.getDeobfName()) != null;
182 assert (deobfWasRemoved);
183 }
184 }
185
186 public FieldMapping getFieldByObf(String obfName) {
187 return m_fieldsByObf.get(obfName);
188 }
189
190 public FieldMapping getFieldByDeobf(String deobfName) {
191 return m_fieldsByDeobf.get(deobfName);
192 }
193
194 public String getObfFieldName(String deobfName) {
195 FieldMapping fieldMapping = m_fieldsByDeobf.get(deobfName);
196 if (fieldMapping != null) {
197 return fieldMapping.getObfName();
198 }
199 return null;
200 }
201
202 public String getDeobfFieldName(String obfName) {
203 FieldMapping fieldMapping = m_fieldsByObf.get(obfName);
204 if (fieldMapping != null) {
205 return fieldMapping.getDeobfName();
206 }
207 return null;
208 }
209
210 public void setFieldName(String obfName, String deobfName) {
211 FieldMapping fieldMapping = m_fieldsByObf.get(obfName);
212 if (fieldMapping == null) {
213 fieldMapping = new FieldMapping(obfName, deobfName);
214 boolean obfWasAdded = m_fieldsByObf.put(obfName, fieldMapping) == null;
215 assert (obfWasAdded);
216 } else {
217 boolean wasRemoved = m_fieldsByDeobf.remove(fieldMapping.getDeobfName()) != null;
218 assert (wasRemoved);
219 }
220 fieldMapping.setDeobfName(deobfName);
221 if (deobfName != null) {
222 boolean wasAdded = m_fieldsByDeobf.put(deobfName, fieldMapping) == null;
223 assert (wasAdded);
224 }
225 }
226
227 //// METHODS ////////
228
229 public Iterable<MethodMapping> methods() {
230 assert (m_methodsByObf.size() >= m_methodsByDeobf.size());
231 return m_methodsByObf.values();
232 }
233
234 public boolean containsObfMethod(String obfName, String obfSignature) {
235 return m_methodsByObf.containsKey(getMethodKey(obfName, obfSignature));
236 }
237
238 public boolean containsDeobfMethod(String deobfName, String deobfSignature) {
239 return m_methodsByDeobf.containsKey(getMethodKey(deobfName, deobfSignature));
240 }
241
242 public void addMethodMapping(MethodMapping methodMapping) {
243 String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature());
244 if (m_methodsByObf.containsKey(obfKey)) {
245 throw new Error("Already have mapping for " + m_obfName + "." + obfKey);
246 }
247 boolean wasAdded = m_methodsByObf.put(obfKey, methodMapping) == null;
248 assert (wasAdded);
249 if (methodMapping.getDeobfName() != null) {
250 String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature());
251 if (m_methodsByDeobf.containsKey(deobfKey)) {
252 throw new Error("Already have mapping for " + m_deobfName + "." + deobfKey);
253 }
254 boolean deobfWasAdded = m_methodsByDeobf.put(deobfKey, methodMapping) == null;
255 assert (deobfWasAdded);
256 }
257 assert (m_methodsByObf.size() >= m_methodsByDeobf.size());
258 }
259
260 public void removeMethodMapping(MethodMapping methodMapping) {
261 boolean obfWasRemoved = m_methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature())) != null;
262 assert (obfWasRemoved);
263 if (methodMapping.getDeobfName() != null) {
264 boolean deobfWasRemoved = m_methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null;
265 assert (deobfWasRemoved);
266 }
267 }
268
269 public MethodMapping getMethodByObf(String obfName, String signature) {
270 return m_methodsByObf.get(getMethodKey(obfName, signature));
271 }
272
273 public MethodMapping getMethodByDeobf(String deobfName, String signature) {
274 return m_methodsByDeobf.get(getMethodKey(deobfName, signature));
275 }
276
277 private String getMethodKey(String name, String signature) {
278 if (name == null) {
279 throw new IllegalArgumentException("name cannot be null!");
280 }
281 if (signature == null) {
282 throw new IllegalArgumentException("signature cannot be null!");
283 }
284 return name + signature;
285 }
286
287 public void setMethodName(String obfName, String obfSignature, String deobfName) {
288 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfName, obfSignature));
289 if (methodMapping == null) {
290 methodMapping = createMethodMapping(obfName, obfSignature);
291 } else if (methodMapping.getDeobfName() != null) {
292 boolean wasRemoved = m_methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null;
293 assert (wasRemoved);
294 }
295 methodMapping.setDeobfName(deobfName);
296 if (deobfName != null) {
297 boolean wasAdded = m_methodsByDeobf.put(getMethodKey(deobfName, obfSignature), methodMapping) == null;
298 assert (wasAdded);
299 }
300 }
301
302 //// ARGUMENTS ////////
303
304 public void setArgumentName(String obfMethodName, String obfMethodSignature, int argumentIndex, String argumentName) {
305 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature));
306 if (methodMapping == null) {
307 methodMapping = createMethodMapping(obfMethodName, obfMethodSignature);
308 }
309 methodMapping.setArgumentName(argumentIndex, argumentName);
310 }
311
312 public void removeArgumentName(String obfMethodName, String obfMethodSignature, int argumentIndex) {
313 m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)).removeArgumentName(argumentIndex);
314 }
315
316 private MethodMapping createMethodMapping(String obfName, String obfSignature) {
317 MethodMapping methodMapping = new MethodMapping(obfName, obfSignature);
318 boolean wasAdded = m_methodsByObf.put(getMethodKey(obfName, obfSignature), methodMapping) == null;
319 assert (wasAdded);
320 return methodMapping;
321 }
322
323 @Override
324 public String toString() {
325 StringBuilder buf = new StringBuilder();
326 buf.append(m_obfName);
327 buf.append(" <-> ");
328 buf.append(m_deobfName);
329 buf.append("\n");
330 buf.append("Fields:\n");
331 for (FieldMapping fieldMapping : fields()) {
332 buf.append("\t");
333 buf.append(fieldMapping.getObfName());
334 buf.append(" <-> ");
335 buf.append(fieldMapping.getDeobfName());
336 buf.append("\n");
337 }
338 buf.append("Methods:\n");
339 for (MethodMapping methodMapping : m_methodsByObf.values()) {
340 buf.append(methodMapping.toString());
341 buf.append("\n");
342 }
343 buf.append("Inner Classes:\n");
344 for (ClassMapping classMapping : m_innerClassesByObf.values()) {
345 buf.append("\t");
346 buf.append(classMapping.getObfName());
347 buf.append(" <-> ");
348 buf.append(classMapping.getDeobfName());
349 buf.append("\n");
350 }
351 return buf.toString();
352 }
353
354 @Override
355 public int compareTo(ClassMapping other) {
356 // sort by a, b, c, ... aa, ab, etc
357 if (m_obfName.length() != other.m_obfName.length()) {
358 return m_obfName.length() - other.m_obfName.length();
359 }
360 return m_obfName.compareTo(other.m_obfName);
361 }
362
363 public boolean renameObfClass(String oldObfClassName, String newObfClassName) {
364
365 // rename inner classes
366 for (ClassMapping innerClassMapping : new ArrayList<ClassMapping>(m_innerClassesByObf.values())) {
367 if (innerClassMapping.renameObfClass(oldObfClassName, newObfClassName)) {
368 boolean wasRemoved = m_innerClassesByObf.remove(oldObfClassName) != null;
369 assert (wasRemoved);
370 boolean wasAdded = m_innerClassesByObf.put(newObfClassName, innerClassMapping) == null;
371 assert (wasAdded);
372 }
373 }
374
375 // rename method signatures
376 for (MethodMapping methodMapping : new ArrayList<MethodMapping>(m_methodsByObf.values())) {
377 String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature());
378 if (methodMapping.renameObfClass(oldObfClassName, newObfClassName)) {
379 boolean wasRemoved = m_methodsByObf.remove(oldMethodKey) != null;
380 assert (wasRemoved);
381 boolean wasAdded = m_methodsByObf.put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()), methodMapping) == null;
382 assert (wasAdded);
383 }
384 }
385
386 if (m_obfName.equals(oldObfClassName)) {
387 // rename this class
388 m_obfName = newObfClassName;
389 return true;
390 }
391 return false;
392 }
393
394 public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) {
395 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfBehaviorEntry.getName(), obfBehaviorEntry.getSignature()));
396 if (methodMapping != null) {
397 return methodMapping.containsArgument(name);
398 }
399 return false;
400 }
401
402 public static boolean isSimpleClassName(String name) {
403 return name.indexOf('/') < 0 && name.indexOf('$') < 0;
404 }
405}
diff --git a/src/cuchaz/enigma/mapping/ConstructorEntry.java b/src/cuchaz/enigma/mapping/ConstructorEntry.java
new file mode 100644
index 0000000..ea0535f
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ConstructorEntry.java
@@ -0,0 +1,116 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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 String m_signature;
23
24 public ConstructorEntry(ClassEntry classEntry) {
25 this(classEntry, null);
26 }
27
28 public ConstructorEntry(ClassEntry classEntry, String 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 String 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/Entry.java b/src/cuchaz/enigma/mapping/Entry.java
new file mode 100644
index 0000000..39e1507
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Entry.java
@@ -0,0 +1,18 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public interface Entry {
14 String getName();
15 String getClassName();
16 ClassEntry getClassEntry();
17 Entry cloneToNewClass(ClassEntry classEntry);
18}
diff --git a/src/cuchaz/enigma/mapping/EntryPair.java b/src/cuchaz/enigma/mapping/EntryPair.java
new file mode 100644
index 0000000..60411c4
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/EntryPair.java
@@ -0,0 +1,22 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
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/cuchaz/enigma/mapping/FieldEntry.java b/src/cuchaz/enigma/mapping/FieldEntry.java
new file mode 100644
index 0000000..6cc9eb7
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/FieldEntry.java
@@ -0,0 +1,88 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
24 // NOTE: this argument order is important for the MethodReader/MethodWriter
25 public FieldEntry(ClassEntry classEntry, String name) {
26 if (classEntry == null) {
27 throw new IllegalArgumentException("Class cannot be null!");
28 }
29 if (name == null) {
30 throw new IllegalArgumentException("Field name cannot be null!");
31 }
32
33 m_classEntry = classEntry;
34 m_name = name;
35 }
36
37 public FieldEntry(FieldEntry other) {
38 m_classEntry = new ClassEntry(other.m_classEntry);
39 m_name = other.m_name;
40 }
41
42 public FieldEntry(FieldEntry other, String newClassName) {
43 m_classEntry = new ClassEntry(newClassName);
44 m_name = other.m_name;
45 }
46
47 @Override
48 public ClassEntry getClassEntry() {
49 return m_classEntry;
50 }
51
52 @Override
53 public String getName() {
54 return m_name;
55 }
56
57 @Override
58 public String getClassName() {
59 return m_classEntry.getName();
60 }
61
62 @Override
63 public FieldEntry cloneToNewClass(ClassEntry classEntry) {
64 return new FieldEntry(this, classEntry.getName());
65 }
66
67 @Override
68 public int hashCode() {
69 return Util.combineHashesOrdered(m_classEntry, m_name);
70 }
71
72 @Override
73 public boolean equals(Object other) {
74 if (other instanceof FieldEntry) {
75 return equals((FieldEntry)other);
76 }
77 return false;
78 }
79
80 public boolean equals(FieldEntry other) {
81 return m_classEntry.equals(other.m_classEntry) && m_name.equals(other.m_name);
82 }
83
84 @Override
85 public String toString() {
86 return m_classEntry.getName() + "." + m_name;
87 }
88}
diff --git a/src/cuchaz/enigma/mapping/FieldMapping.java b/src/cuchaz/enigma/mapping/FieldMapping.java
new file mode 100644
index 0000000..5f5c270
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/FieldMapping.java
@@ -0,0 +1,43 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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> {
16
17 private static final long serialVersionUID = 8610742471440861315L;
18
19 private String m_obfName;
20 private String m_deobfName;
21
22 public FieldMapping(String obfName, String deobfName) {
23 m_obfName = obfName;
24 m_deobfName = NameValidator.validateFieldName(deobfName);
25 }
26
27 public String getObfName() {
28 return m_obfName;
29 }
30
31 public String getDeobfName() {
32 return m_deobfName;
33 }
34
35 public void setDeobfName(String val) {
36 m_deobfName = NameValidator.validateFieldName(val);
37 }
38
39 @Override
40 public int compareTo(FieldMapping other) {
41 return m_obfName.compareTo(other.m_obfName);
42 }
43}
diff --git a/src/cuchaz/enigma/mapping/IllegalNameException.java b/src/cuchaz/enigma/mapping/IllegalNameException.java
new file mode 100644
index 0000000..aacaf3b
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/IllegalNameException.java
@@ -0,0 +1,44 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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/JavassistUtil.java b/src/cuchaz/enigma/mapping/JavassistUtil.java
new file mode 100644
index 0000000..b011e0b
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/JavassistUtil.java
@@ -0,0 +1,55 @@
1package cuchaz.enigma.mapping;
2
3import javassist.CtBehavior;
4import javassist.CtClass;
5import javassist.CtConstructor;
6import javassist.CtField;
7import javassist.CtMethod;
8import javassist.bytecode.Descriptor;
9import cuchaz.enigma.mapping.BehaviorEntry;
10import cuchaz.enigma.mapping.ClassEntry;
11import cuchaz.enigma.mapping.ConstructorEntry;
12import cuchaz.enigma.mapping.FieldEntry;
13import cuchaz.enigma.mapping.MethodEntry;
14
15public class JavassistUtil {
16
17 public static ClassEntry getClassEntry(CtClass c) {
18 return new ClassEntry(Descriptor.toJvmName(c.getName()));
19 }
20
21 public static ClassEntry getSuperclassEntry(CtClass c) {
22 return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
23 }
24
25 public static MethodEntry getMethodEntry(CtMethod method) {
26 return new MethodEntry(
27 getClassEntry(method.getDeclaringClass()),
28 method.getName(),
29 method.getMethodInfo().getDescriptor()
30 );
31 }
32
33 public static ConstructorEntry getConstructorEntry(CtConstructor constructor) {
34 return new ConstructorEntry(
35 getClassEntry(constructor.getDeclaringClass()),
36 constructor.getMethodInfo().getDescriptor()
37 );
38 }
39
40 public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) {
41 if (behavior instanceof CtMethod) {
42 return getMethodEntry((CtMethod)behavior);
43 } else if (behavior instanceof CtConstructor) {
44 return getConstructorEntry((CtConstructor)behavior);
45 }
46 throw new Error("behavior is neither Method nor Constructor!");
47 }
48
49 public static FieldEntry getFieldEntry(CtField field) {
50 return new FieldEntry(
51 getClassEntry(field.getDeclaringClass()),
52 field.getName()
53 );
54 }
55}
diff --git a/src/cuchaz/enigma/mapping/MappingParseException.java b/src/cuchaz/enigma/mapping/MappingParseException.java
new file mode 100644
index 0000000..1974c22
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingParseException.java
@@ -0,0 +1,29 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public class MappingParseException extends Exception {
14
15 private static final long serialVersionUID = -5487280332892507236L;
16
17 private int m_line;
18 private String m_message;
19
20 public MappingParseException(int line, String message) {
21 m_line = line;
22 m_message = message;
23 }
24
25 @Override
26 public String getMessage() {
27 return "Line " + m_line + ": " + m_message;
28 }
29}
diff --git a/src/cuchaz/enigma/mapping/Mappings.java b/src/cuchaz/enigma/mapping/Mappings.java
new file mode 100644
index 0000000..cc560a8
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Mappings.java
@@ -0,0 +1,213 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.IOException;
14import java.io.InputStream;
15import java.io.ObjectInputStream;
16import java.io.Serializable;
17import java.util.ArrayList;
18import java.util.Collection;
19import java.util.Map;
20import java.util.Set;
21import java.util.zip.GZIPInputStream;
22
23import com.google.common.collect.Maps;
24import com.google.common.collect.Sets;
25
26import cuchaz.enigma.Util;
27import cuchaz.enigma.analysis.TranslationIndex;
28import cuchaz.enigma.mapping.SignatureUpdater.ClassNameUpdater;
29
30public class Mappings implements Serializable {
31
32 private static final long serialVersionUID = 4649790259460259026L;
33
34 protected Map<String,ClassMapping> m_classesByObf;
35 protected Map<String,ClassMapping> m_classesByDeobf;
36
37 public Mappings() {
38 m_classesByObf = Maps.newHashMap();
39 m_classesByDeobf = Maps.newHashMap();
40 }
41
42 public Mappings(Iterable<ClassMapping> classes) {
43 this();
44
45 for (ClassMapping classMapping : classes) {
46 m_classesByObf.put(classMapping.getObfName(), classMapping);
47 if (classMapping.getDeobfName() != null) {
48 m_classesByDeobf.put(classMapping.getDeobfName(), classMapping);
49 }
50 }
51 }
52
53 public static Mappings newFromResource(String resource) throws IOException {
54 InputStream in = null;
55 try {
56 in = Mappings.class.getResourceAsStream(resource);
57 return newFromStream(in);
58 } finally {
59 Util.closeQuietly(in);
60 }
61 }
62
63 public Collection<ClassMapping> classes() {
64 assert (m_classesByObf.size() >= m_classesByDeobf.size());
65 return m_classesByObf.values();
66 }
67
68 public void addClassMapping(ClassMapping classMapping) {
69 if (m_classesByObf.containsKey(classMapping.getObfName())) {
70 throw new Error("Already have mapping for " + classMapping.getObfName());
71 }
72 boolean obfWasAdded = m_classesByObf.put(classMapping.getObfName(), classMapping) == null;
73 assert (obfWasAdded);
74 if (classMapping.getDeobfName() != null) {
75 if (m_classesByDeobf.containsKey(classMapping.getDeobfName())) {
76 throw new Error("Already have mapping for " + classMapping.getDeobfName());
77 }
78 boolean deobfWasAdded = m_classesByDeobf.put(classMapping.getDeobfName(), classMapping) == null;
79 assert (deobfWasAdded);
80 }
81 }
82
83 public void removeClassMapping(ClassMapping classMapping) {
84 boolean obfWasRemoved = m_classesByObf.remove(classMapping.getObfName()) != null;
85 assert (obfWasRemoved);
86 if (classMapping.getDeobfName() != null) {
87 boolean deobfWasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
88 assert (deobfWasRemoved);
89 }
90 }
91
92 public ClassMapping getClassByObf(ClassEntry entry) {
93 return getClassByObf(entry.getName());
94 }
95
96 public ClassMapping getClassByObf(String obfName) {
97 return m_classesByObf.get(obfName);
98 }
99
100 public ClassMapping getClassByDeobf(ClassEntry entry) {
101 return getClassByDeobf(entry.getName());
102 }
103
104 public ClassMapping getClassByDeobf(String deobfName) {
105 return m_classesByDeobf.get(deobfName);
106 }
107
108 public Translator getTranslator(TranslationDirection direction, TranslationIndex index) {
109 switch (direction) {
110 case Deobfuscating:
111
112 return new Translator(direction, m_classesByObf, index);
113
114 case Obfuscating:
115
116 // fill in the missing deobf class entries with obf entries
117 Map<String,ClassMapping> classes = Maps.newHashMap();
118 for (ClassMapping classMapping : classes()) {
119 if (classMapping.getDeobfName() != null) {
120 classes.put(classMapping.getDeobfName(), classMapping);
121 } else {
122 classes.put(classMapping.getObfName(), classMapping);
123 }
124 }
125
126 // translate the translation index
127 // NOTE: this isn't actually recursive
128 TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.Deobfuscating, index));
129
130 return new Translator(direction, classes, deobfIndex);
131
132 default:
133 throw new Error("Invalid translation direction!");
134 }
135 }
136
137 public static Mappings newFromStream(InputStream in) throws IOException {
138 try {
139 return (Mappings)new ObjectInputStream(new GZIPInputStream(in)).readObject();
140 } catch (ClassNotFoundException ex) {
141 throw new Error(ex);
142 }
143 }
144
145 @Override
146 public String toString() {
147 StringBuilder buf = new StringBuilder();
148 for (ClassMapping classMapping : m_classesByObf.values()) {
149 buf.append(classMapping.toString());
150 buf.append("\n");
151 }
152 return buf.toString();
153 }
154
155 public void renameObfClass(String oldObfName, String newObfName) {
156 for (ClassMapping classMapping : new ArrayList<ClassMapping>(classes())) {
157 if (classMapping.renameObfClass(oldObfName, newObfName)) {
158 boolean wasRemoved = m_classesByObf.remove(oldObfName) != null;
159 assert (wasRemoved);
160 boolean wasAdded = m_classesByObf.put(newObfName, classMapping) == null;
161 assert (wasAdded);
162 }
163 }
164 }
165
166 public Set<String> getAllObfClassNames() {
167 final Set<String> classNames = Sets.newHashSet();
168 for (ClassMapping classMapping : classes()) {
169 // add the class name
170 classNames.add(classMapping.getObfName());
171
172 // add classes from method signatures
173 for (MethodMapping methodMapping : classMapping.methods()) {
174 SignatureUpdater.update(methodMapping.getObfSignature(), new ClassNameUpdater() {
175 @Override
176 public String update(String className) {
177 classNames.add(className);
178 return className;
179 }
180 });
181 }
182 }
183 return classNames;
184 }
185
186 public boolean containsDeobfClass(String deobfName) {
187 return m_classesByDeobf.containsKey(deobfName);
188 }
189
190 public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName) {
191 ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
192 if (classMapping != null) {
193 return classMapping.containsDeobfField(deobfName);
194 }
195 return false;
196 }
197
198 public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, String deobfSignature) {
199 ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
200 if (classMapping != null) {
201 return classMapping.containsDeobfMethod(deobfName, deobfSignature);
202 }
203 return false;
204 }
205
206 public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) {
207 ClassMapping classMapping = m_classesByObf.get(obfBehaviorEntry.getClassName());
208 if (classMapping != null) {
209 return classMapping.containsArgument(obfBehaviorEntry, name);
210 }
211 return false;
212 }
213}
diff --git a/src/cuchaz/enigma/mapping/MappingsReader.java b/src/cuchaz/enigma/mapping/MappingsReader.java
new file mode 100644
index 0000000..72e829d
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingsReader.java
@@ -0,0 +1,176 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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
20import cuchaz.enigma.Constants;
21import cuchaz.enigma.mapping.SignatureUpdater.ClassNameUpdater;
22
23public class MappingsReader {
24
25 public Mappings read(Reader in) throws IOException, MappingParseException {
26 return read(new BufferedReader(in));
27 }
28
29 public Mappings read(BufferedReader in) throws IOException, MappingParseException {
30 Mappings mappings = new Mappings();
31 Deque<Object> mappingStack = Queues.newArrayDeque();
32
33 int lineNumber = 0;
34 String line = null;
35 while ( (line = in.readLine()) != null) {
36 lineNumber++;
37
38 // strip comments
39 int commentPos = line.indexOf('#');
40 if (commentPos >= 0) {
41 line = line.substring(0, commentPos);
42 }
43
44 // skip blank lines
45 if (line.trim().length() <= 0) {
46 continue;
47 }
48
49 // get the indent of this line
50 int indent = 0;
51 for (int i = 0; i < line.length(); i++) {
52 if (line.charAt(i) != '\t') {
53 break;
54 }
55 indent++;
56 }
57
58 // handle stack pops
59 while (indent < mappingStack.size()) {
60 mappingStack.pop();
61 }
62
63 String[] parts = line.trim().split("\\s");
64 try {
65 // read the first token
66 String token = parts[0];
67
68 if (token.equalsIgnoreCase("CLASS")) {
69 ClassMapping classMapping;
70 if (indent == 0) {
71 // outer class
72 classMapping = readClass(parts, false);
73 mappings.addClassMapping(classMapping);
74 } else if (indent == 1) {
75 // inner class
76 if (! (mappingStack.getFirst() instanceof ClassMapping)) {
77 throw new MappingParseException(lineNumber, "Unexpected CLASS entry here!");
78 }
79
80 classMapping = readClass(parts, true);
81 ((ClassMapping)mappingStack.getFirst()).addInnerClassMapping(classMapping);
82 } else {
83 throw new MappingParseException(lineNumber, "Unexpected CLASS entry nesting!");
84 }
85 mappingStack.push(classMapping);
86 } else if (token.equalsIgnoreCase("FIELD")) {
87 if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof ClassMapping)) {
88 throw new MappingParseException(lineNumber, "Unexpected FIELD entry here!");
89 }
90 ((ClassMapping)mappingStack.getFirst()).addFieldMapping(readField(parts));
91 } else if (token.equalsIgnoreCase("METHOD")) {
92 if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof ClassMapping)) {
93 throw new MappingParseException(lineNumber, "Unexpected METHOD entry here!");
94 }
95 MethodMapping methodMapping = readMethod(parts);
96 ((ClassMapping)mappingStack.getFirst()).addMethodMapping(methodMapping);
97 mappingStack.push(methodMapping);
98 } else if (token.equalsIgnoreCase("ARG")) {
99 if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof MethodMapping)) {
100 throw new MappingParseException(lineNumber, "Unexpected ARG entry here!");
101 }
102 ((MethodMapping)mappingStack.getFirst()).addArgumentMapping(readArgument(parts));
103 }
104 } catch (ArrayIndexOutOfBoundsException | NumberFormatException ex) {
105 throw new MappingParseException(lineNumber, "Malformed line!");
106 }
107 }
108
109 return mappings;
110 }
111
112 private ArgumentMapping readArgument(String[] parts) {
113 return new ArgumentMapping(Integer.parseInt(parts[1]), parts[2]);
114 }
115
116 private ClassMapping readClass(String[] parts, boolean makeSimple) {
117 if (parts.length == 2) {
118 String obfName = processName(parts[1], makeSimple);
119 return new ClassMapping(obfName);
120 } else {
121 String obfName = processName(parts[1], makeSimple);
122 String deobfName = processName(parts[2], makeSimple);
123 return new ClassMapping(obfName, deobfName);
124 }
125 }
126
127 private String processName(String name, boolean makeSimple) {
128 if (makeSimple) {
129 return new ClassEntry(name).getSimpleName();
130 } else {
131 return moveClassOutOfDefaultPackage(name, Constants.NonePackage);
132 }
133 }
134
135 private String moveClassOutOfDefaultPackage(String className, String newPackageName) {
136 ClassEntry classEntry = new ClassEntry(className);
137 if (classEntry.isInDefaultPackage()) {
138 return newPackageName + "/" + classEntry.getName();
139 }
140 return className;
141 }
142
143 private FieldMapping readField(String[] parts) {
144 return new FieldMapping(parts[1], parts[2]);
145 }
146
147 private MethodMapping readMethod(String[] parts) {
148 if (parts.length == 3) {
149 String obfName = parts[1];
150 String obfSignature = moveSignatureOutOfDefaultPackage(parts[2], Constants.NonePackage);
151 return new MethodMapping(obfName, obfSignature);
152 } else {
153 String obfName = parts[1];
154 String deobfName = parts[2];
155 String obfSignature = moveSignatureOutOfDefaultPackage(parts[3], Constants.NonePackage);
156 if (obfName.equals(deobfName)) {
157 return new MethodMapping(obfName, obfSignature);
158 } else {
159 return new MethodMapping(obfName, obfSignature, deobfName);
160 }
161 }
162 }
163
164 private String moveSignatureOutOfDefaultPackage(String signature, final String newPackageName) {
165 return SignatureUpdater.update(signature, new ClassNameUpdater() {
166 @Override
167 public String update(String className) {
168 ClassEntry classEntry = new ClassEntry(className);
169 if (classEntry.isInDefaultPackage()) {
170 return newPackageName + "/" + className;
171 }
172 return className;
173 }
174 });
175 }
176}
diff --git a/src/cuchaz/enigma/mapping/MappingsRenamer.java b/src/cuchaz/enigma/mapping/MappingsRenamer.java
new file mode 100644
index 0000000..3aac65a
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingsRenamer.java
@@ -0,0 +1,237 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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.Set;
17import java.util.zip.GZIPOutputStream;
18
19import cuchaz.enigma.Constants;
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 deobfName = NameValidator.validateClassName(deobfName, !obf.isInnerClass());
34 ClassEntry targetEntry = new ClassEntry(deobfName);
35 if (m_mappings.containsDeobfClass(deobfName) || m_index.containsObfClass(targetEntry)) {
36 throw new IllegalNameException(deobfName, "There is already a class with that name");
37 }
38
39 ClassMapping classMapping = getOrCreateClassMapping(obf);
40
41 if (obf.isInnerClass()) {
42 classMapping.setInnerClassName(obf.getInnerClassName(), deobfName);
43 } else {
44 if (classMapping.getDeobfName() != null) {
45 boolean wasRemoved = m_mappings.m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
46 assert (wasRemoved);
47 }
48 classMapping.setDeobfName(deobfName);
49 boolean wasAdded = m_mappings.m_classesByDeobf.put(deobfName, classMapping) == null;
50 assert (wasAdded);
51 }
52 }
53
54 public void removeClassMapping(ClassEntry obf) {
55 ClassMapping classMapping = getClassMapping(obf);
56 if (obf.isInnerClass()) {
57 classMapping.setInnerClassName(obf.getName(), null);
58 } else {
59 boolean wasRemoved = m_mappings.m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
60 assert (wasRemoved);
61 classMapping.setDeobfName(null);
62 }
63 }
64
65 public void markClassAsDeobfuscated(ClassEntry obf) {
66 ClassMapping classMapping = getOrCreateClassMapping(obf);
67 if (obf.isInnerClass()) {
68 String innerClassName = Constants.NonePackage + "/" + obf.getInnerClassName();
69 classMapping.setInnerClassName(innerClassName, innerClassName);
70 } else {
71 classMapping.setDeobfName(obf.getName());
72 boolean wasAdded = m_mappings.m_classesByDeobf.put(obf.getName(), classMapping) == null;
73 assert (wasAdded);
74 }
75 }
76
77 public void setFieldName(FieldEntry obf, String deobfName) {
78 deobfName = NameValidator.validateFieldName(deobfName);
79 FieldEntry targetEntry = new FieldEntry(obf.getClassEntry(), deobfName);
80 if (m_mappings.containsDeobfField(obf.getClassEntry(), deobfName) || m_index.containsObfField(targetEntry)) {
81 throw new IllegalNameException(deobfName, "There is already a field with that name");
82 }
83
84 ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry());
85 classMapping.setFieldName(obf.getName(), deobfName);
86 }
87
88 public void removeFieldMapping(FieldEntry obf) {
89 ClassMapping classMapping = getClassMappingOrInnerClassMapping(obf.getClassEntry());
90 classMapping.setFieldName(obf.getName(), null);
91 }
92
93 public void markFieldAsDeobfuscated(FieldEntry obf) {
94 ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry());
95 classMapping.setFieldName(obf.getName(), obf.getName());
96 }
97
98 public void setMethodTreeName(MethodEntry obf, String deobfName) {
99 Set<MethodEntry> implementations = m_index.getRelatedMethodImplementations(obf);
100
101 deobfName = NameValidator.validateMethodName(deobfName);
102 for (MethodEntry entry : implementations) {
103 String deobfSignature = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateSignature(obf.getSignature());
104 MethodEntry targetEntry = new MethodEntry(entry.getClassEntry(), deobfName, deobfSignature);
105 if (m_mappings.containsDeobfMethod(entry.getClassEntry(), deobfName, entry.getSignature()) || m_index.containsObfBehavior(targetEntry)) {
106 String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(entry.getClassName());
107 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName);
108 }
109 }
110
111 for (MethodEntry entry : implementations) {
112 setMethodName(entry, deobfName);
113 }
114 }
115
116 public void setMethodName(MethodEntry obf, String deobfName) {
117 deobfName = NameValidator.validateMethodName(deobfName);
118 MethodEntry targetEntry = new MethodEntry(obf.getClassEntry(), deobfName, obf.getSignature());
119 if (m_mappings.containsDeobfMethod(obf.getClassEntry(), deobfName, obf.getSignature()) || m_index.containsObfBehavior(targetEntry)) {
120 String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(obf.getClassName());
121 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName);
122 }
123
124 ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry());
125 classMapping.setMethodName(obf.getName(), obf.getSignature(), deobfName);
126 }
127
128 public void removeMethodTreeMapping(MethodEntry obf) {
129 for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) {
130 removeMethodMapping(implementation);
131 }
132 }
133
134 public void removeMethodMapping(MethodEntry obf) {
135 ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry());
136 classMapping.setMethodName(obf.getName(), obf.getSignature(), null);
137 }
138
139 public void markMethodTreeAsDeobfuscated(MethodEntry obf) {
140 for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) {
141 markMethodAsDeobfuscated(implementation);
142 }
143 }
144
145 public void markMethodAsDeobfuscated(MethodEntry obf) {
146 ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry());
147 classMapping.setMethodName(obf.getName(), obf.getSignature(), obf.getName());
148 }
149
150 public void setArgumentName(ArgumentEntry obf, String deobfName) {
151 deobfName = NameValidator.validateArgumentName(deobfName);
152 // NOTE: don't need to check arguments for name collisions with names determined by Procyon
153 if (m_mappings.containsArgument(obf.getBehaviorEntry(), deobfName)) {
154 throw new IllegalNameException(deobfName, "There is already an argument with that name");
155 }
156
157 ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry());
158 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), deobfName);
159 }
160
161 public void removeArgumentMapping(ArgumentEntry obf) {
162 ClassMapping classMapping = getClassMappingOrInnerClassMapping(obf.getClassEntry());
163 classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex());
164 }
165
166 public void markArgumentAsDeobfuscated(ArgumentEntry obf) {
167 ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry());
168 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), obf.getName());
169 }
170
171 public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) {
172 classMapping.removeFieldMapping(fieldMapping);
173 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
174 if (!targetClassMapping.containsObfField(fieldMapping.getObfName())) {
175 if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName())) {
176 targetClassMapping.addFieldMapping(fieldMapping);
177 return true;
178 } else {
179 System.err.println("WARNING: deobf field was already there: " + obfClass + "." + fieldMapping.getDeobfName());
180 }
181 }
182 return false;
183 }
184
185 public boolean moveMethodToObfClass(ClassMapping classMapping, MethodMapping methodMapping, ClassEntry obfClass) {
186 classMapping.removeMethodMapping(methodMapping);
187 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
188 if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfSignature())) {
189 if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfSignature())) {
190 targetClassMapping.addMethodMapping(methodMapping);
191 return true;
192 } else {
193 System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfSignature());
194 }
195 }
196 return false;
197 }
198
199 public void write(OutputStream out) throws IOException {
200 // TEMP: just use the object output for now. We can find a more efficient storage format later
201 GZIPOutputStream gzipout = new GZIPOutputStream(out);
202 ObjectOutputStream oout = new ObjectOutputStream(gzipout);
203 oout.writeObject(this);
204 gzipout.finish();
205 }
206
207 private ClassMapping getClassMapping(ClassEntry obfClassEntry) {
208 return m_mappings.m_classesByObf.get(obfClassEntry.getOuterClassName());
209 }
210
211 private ClassMapping getOrCreateClassMapping(ClassEntry obfClassEntry) {
212 String obfClassName = obfClassEntry.getOuterClassName();
213 ClassMapping classMapping = m_mappings.m_classesByObf.get(obfClassName);
214 if (classMapping == null) {
215 classMapping = new ClassMapping(obfClassName);
216 boolean obfWasAdded = m_mappings.m_classesByObf.put(classMapping.getObfName(), classMapping) == null;
217 assert (obfWasAdded);
218 }
219 return classMapping;
220 }
221
222 private ClassMapping getClassMappingOrInnerClassMapping(ClassEntry obfClassEntry) {
223 ClassMapping classMapping = getClassMapping(obfClassEntry);
224 if (obfClassEntry.isInDefaultPackage()) {
225 classMapping = classMapping.getInnerClassByObf(obfClassEntry.getInnerClassName());
226 }
227 return classMapping;
228 }
229
230 private ClassMapping getOrCreateClassMappingOrInnerClassMapping(ClassEntry obfClassEntry) {
231 ClassMapping classMapping = getOrCreateClassMapping(obfClassEntry);
232 if (obfClassEntry.isInnerClass()) {
233 classMapping = classMapping.getOrCreateInnerClass(obfClassEntry.getInnerClassName());
234 }
235 return classMapping;
236 }
237}
diff --git a/src/cuchaz/enigma/mapping/MappingsWriter.java b/src/cuchaz/enigma/mapping/MappingsWriter.java
new file mode 100644
index 0000000..5ac409f
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingsWriter.java
@@ -0,0 +1,88 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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.getObfName());
35 } else {
36 out.format("%sCLASS %s %s\n", getIndent(depth), classMapping.getObfName(), 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\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName());
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
new file mode 100644
index 0000000..beb490d
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MethodEntry.java
@@ -0,0 +1,104 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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 String m_signature;
24
25 public MethodEntry(ClassEntry classEntry, String name, String 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 String 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
new file mode 100644
index 0000000..4dab3c6
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MethodMapping.java
@@ -0,0 +1,162 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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.TreeMap;
16
17import cuchaz.enigma.mapping.SignatureUpdater.ClassNameUpdater;
18
19public class MethodMapping implements Serializable, Comparable<MethodMapping> {
20
21 private static final long serialVersionUID = -4409570216084263978L;
22
23 private String m_obfName;
24 private String m_deobfName;
25 private String m_obfSignature;
26 private Map<Integer,ArgumentMapping> m_arguments;
27
28 public MethodMapping(String obfName, String obfSignature) {
29 this(obfName, obfSignature, null);
30 }
31
32 public MethodMapping(String obfName, String 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 = new TreeMap<Integer,ArgumentMapping>();
43 }
44
45 public String getObfName() {
46 return m_obfName;
47 }
48
49 public String getDeobfName() {
50 return m_deobfName;
51 }
52
53 public void setDeobfName(String val) {
54 m_deobfName = NameValidator.validateMethodName(val);
55 }
56
57 public String getObfSignature() {
58 return m_obfSignature;
59 }
60
61 public Iterable<ArgumentMapping> arguments() {
62 return m_arguments.values();
63 }
64
65 public boolean isConstructor() {
66 return m_obfName.startsWith("<");
67 }
68
69 public void addArgumentMapping(ArgumentMapping argumentMapping) {
70 boolean wasAdded = m_arguments.put(argumentMapping.getIndex(), argumentMapping) == null;
71 assert (wasAdded);
72 }
73
74 public String getObfArgumentName(int index) {
75 ArgumentMapping argumentMapping = m_arguments.get(index);
76 if (argumentMapping != null) {
77 return argumentMapping.getName();
78 }
79
80 return null;
81 }
82
83 public String getDeobfArgumentName(int index) {
84 ArgumentMapping argumentMapping = m_arguments.get(index);
85 if (argumentMapping != null) {
86 return argumentMapping.getName();
87 }
88
89 return null;
90 }
91
92 public void setArgumentName(int index, String name) {
93 ArgumentMapping argumentMapping = m_arguments.get(index);
94 if (argumentMapping == null) {
95 argumentMapping = new ArgumentMapping(index, name);
96 boolean wasAdded = m_arguments.put(index, argumentMapping) == null;
97 assert (wasAdded);
98 } else {
99 argumentMapping.setName(name);
100 }
101 }
102
103 public void removeArgumentName(int index) {
104 boolean wasRemoved = m_arguments.remove(index) != null;
105 assert (wasRemoved);
106 }
107
108 @Override
109 public String toString() {
110 StringBuilder buf = new StringBuilder();
111 buf.append("\t");
112 buf.append(m_obfName);
113 buf.append(" <-> ");
114 buf.append(m_deobfName);
115 buf.append("\n");
116 buf.append("\t");
117 buf.append(m_obfSignature);
118 buf.append("\n");
119 buf.append("\tArguments:\n");
120 for (ArgumentMapping argumentMapping : m_arguments.values()) {
121 buf.append("\t\t");
122 buf.append(argumentMapping.getIndex());
123 buf.append(" -> ");
124 buf.append(argumentMapping.getName());
125 buf.append("\n");
126 }
127 return buf.toString();
128 }
129
130 @Override
131 public int compareTo(MethodMapping other) {
132 return (m_obfName + m_obfSignature).compareTo(other.m_obfName + other.m_obfSignature);
133 }
134
135 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
136 // rename obf classes in the signature
137 String newSignature = SignatureUpdater.update(m_obfSignature, new ClassNameUpdater() {
138 @Override
139 public String update(String className) {
140 if (className.equals(oldObfClassName)) {
141 return newObfClassName;
142 }
143 return className;
144 }
145 });
146
147 if (newSignature != m_obfSignature) {
148 m_obfSignature = newSignature;
149 return true;
150 }
151 return false;
152 }
153
154 public boolean containsArgument(String name) {
155 for (ArgumentMapping argumentMapping : m_arguments.values()) {
156 if (argumentMapping.getName().equals(name)) {
157 return true;
158 }
159 }
160 return false;
161 }
162}
diff --git a/src/cuchaz/enigma/mapping/NameValidator.java b/src/cuchaz/enigma/mapping/NameValidator.java
new file mode 100644
index 0000000..35a17f9
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/NameValidator.java
@@ -0,0 +1,80 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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/SignatureUpdater.java b/src/cuchaz/enigma/mapping/SignatureUpdater.java
new file mode 100644
index 0000000..3477cd5
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/SignatureUpdater.java
@@ -0,0 +1,94 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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/TranslationDirection.java b/src/cuchaz/enigma/mapping/TranslationDirection.java
new file mode 100644
index 0000000..d1b14cd
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/TranslationDirection.java
@@ -0,0 +1,29 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
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/cuchaz/enigma/mapping/Translator.java b/src/cuchaz/enigma/mapping/Translator.java
new file mode 100644
index 0000000..a5a3e2f
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Translator.java
@@ -0,0 +1,235 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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.Maps;
16
17import cuchaz.enigma.analysis.TranslationIndex;
18import cuchaz.enigma.mapping.SignatureUpdater.ClassNameUpdater;
19
20public class Translator {
21
22 private TranslationDirection m_direction;
23 private Map<String,ClassMapping> m_classes;
24 private TranslationIndex m_index;
25
26 public Translator() {
27 m_direction = null;
28 m_classes = Maps.newHashMap();
29 }
30
31 public Translator(TranslationDirection direction, Map<String,ClassMapping> classes, TranslationIndex index) {
32 m_direction = direction;
33 m_classes = classes;
34 m_index = index;
35 }
36
37 @SuppressWarnings("unchecked")
38 public <T extends Entry> T translateEntry(T entry) {
39 if (entry instanceof ClassEntry) {
40 return (T)translateEntry((ClassEntry)entry);
41 } else if (entry instanceof FieldEntry) {
42 return (T)translateEntry((FieldEntry)entry);
43 } else if (entry instanceof MethodEntry) {
44 return (T)translateEntry((MethodEntry)entry);
45 } else if (entry instanceof ConstructorEntry) {
46 return (T)translateEntry((ConstructorEntry)entry);
47 } else if (entry instanceof ArgumentEntry) {
48 return (T)translateEntry((ArgumentEntry)entry);
49 } else {
50 throw new Error("Unknown entry type: " + entry.getClass().getName());
51 }
52 }
53
54 public String translateClass(String className) {
55 return translate(new ClassEntry(className));
56 }
57
58 public String translate(ClassEntry in) {
59 ClassMapping classMapping = m_classes.get(in.getOuterClassName());
60 if (classMapping != null) {
61 if (in.isInnerClass()) {
62 // translate the inner class
63 String translatedInnerClassName = m_direction.choose(
64 classMapping.getDeobfInnerClassName(in.getInnerClassName()),
65 classMapping.getObfInnerClassName(in.getInnerClassName())
66 );
67 if (translatedInnerClassName != null) {
68 // try to translate the outer name
69 String translatedOuterClassName = m_direction.choose(classMapping.getDeobfName(), classMapping.getObfName());
70 if (translatedOuterClassName != null) {
71 return translatedOuterClassName + "$" + translatedInnerClassName;
72 } else {
73 return in.getOuterClassName() + "$" + translatedInnerClassName;
74 }
75 }
76 } else {
77 // just return outer
78 return m_direction.choose(classMapping.getDeobfName(), classMapping.getObfName());
79 }
80 }
81 return null;
82 }
83
84 public ClassEntry translateEntry(ClassEntry in) {
85
86 // can we translate the inner class?
87 String name = translate(in);
88 if (name != null) {
89 return new ClassEntry(name);
90 }
91
92 if (in.isInnerClass()) {
93
94 // guess not. just translate the outer class name then
95 String outerClassName = translate(in.getOuterClassEntry());
96 if (outerClassName != null) {
97 return new ClassEntry(outerClassName + "$" + in.getInnerClassName());
98 }
99 }
100
101 return in;
102 }
103
104 public String translate(FieldEntry in) {
105
106 // resolve the class entry
107 ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in);
108 if (resolvedClassEntry != null) {
109
110 // look for the class
111 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
112 if (classMapping != null) {
113
114 // look for the field
115 String translatedName = m_direction.choose(
116 classMapping.getDeobfFieldName(in.getName()),
117 classMapping.getObfFieldName(in.getName())
118 );
119 if (translatedName != null) {
120 return translatedName;
121 }
122 }
123 }
124 return null;
125 }
126
127 public FieldEntry translateEntry(FieldEntry in) {
128 String name = translate(in);
129 if (name == null) {
130 name = in.getName();
131 }
132 return new FieldEntry(translateEntry(in.getClassEntry()), name);
133 }
134
135 public String translate(MethodEntry in) {
136
137 // resolve the class entry
138 ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in);
139 if (resolvedClassEntry != null) {
140
141 // look for class
142 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
143 if (classMapping != null) {
144
145 // look for the method
146 MethodMapping methodMapping = m_direction.choose(
147 classMapping.getMethodByObf(in.getName(), in.getSignature()),
148 classMapping.getMethodByDeobf(in.getName(), translateSignature(in.getSignature()))
149 );
150 if (methodMapping != null) {
151 return m_direction.choose(methodMapping.getDeobfName(), methodMapping.getObfName());
152 }
153 }
154 }
155 return null;
156 }
157
158 public MethodEntry translateEntry(MethodEntry in) {
159 String name = translate(in);
160 if (name == null) {
161 name = in.getName();
162 }
163 return new MethodEntry(translateEntry(in.getClassEntry()), name, translateSignature(in.getSignature()));
164 }
165
166 public ConstructorEntry translateEntry(ConstructorEntry in) {
167 if (in.isStatic()) {
168 return new ConstructorEntry(translateEntry(in.getClassEntry()));
169 } else {
170 return new ConstructorEntry(translateEntry(in.getClassEntry()), translateSignature(in.getSignature()));
171 }
172 }
173
174 public BehaviorEntry translateEntry(BehaviorEntry in) {
175 if (in instanceof MethodEntry) {
176 return translateEntry((MethodEntry)in);
177 } else if (in instanceof ConstructorEntry) {
178 return translateEntry((ConstructorEntry)in);
179 }
180 throw new Error("Wrong entry type!");
181 }
182
183 public String translate(ArgumentEntry in) {
184
185 // look for the class
186 ClassMapping classMapping = findClassMapping(in.getClassEntry());
187 if (classMapping != null) {
188
189 // look for the method
190 MethodMapping methodMapping = m_direction.choose(
191 classMapping.getMethodByObf(in.getMethodName(), in.getMethodSignature()),
192 classMapping.getMethodByDeobf(in.getMethodName(), translateSignature(in.getMethodSignature()))
193 );
194 if (methodMapping != null) {
195 return m_direction.choose(
196 methodMapping.getDeobfArgumentName(in.getIndex()),
197 methodMapping.getObfArgumentName(in.getIndex())
198 );
199 }
200 }
201 return null;
202 }
203
204 public ArgumentEntry translateEntry(ArgumentEntry in) {
205 String name = translate(in);
206 if (name == null) {
207 name = in.getName();
208 }
209 return new ArgumentEntry(translateEntry(in.getBehaviorEntry()), in.getIndex(), name);
210 }
211
212 public String translateSignature(String signature) {
213 return SignatureUpdater.update(signature, new ClassNameUpdater() {
214 @Override
215 public String update(String className) {
216 String translatedName = translateClass(className);
217 if (translatedName != null) {
218 return translatedName;
219 }
220 return className;
221 }
222 });
223 }
224
225 private ClassMapping findClassMapping(ClassEntry classEntry) {
226 ClassMapping classMapping = m_classes.get(classEntry.getOuterClassName());
227 if (classMapping != null && classEntry.isInnerClass()) {
228 classMapping = m_direction.choose(
229 classMapping.getInnerClassByObf(classEntry.getInnerClassName()),
230 classMapping.getInnerClassByDeobfThenObf(classEntry.getInnerClassName())
231 );
232 }
233 return classMapping;
234 }
235}
diff --git a/test/cuchaz/enigma/EntryFactory.java b/test/cuchaz/enigma/EntryFactory.java
new file mode 100644
index 0000000..d9317ef
--- /dev/null
+++ b/test/cuchaz/enigma/EntryFactory.java
@@ -0,0 +1,54 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.\
3 *
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the GNU Public License v3.0
6 * which accompanies this distribution, and is available at
7 * http://www.gnu.org/licenses/gpl.html
8 *
9 * Contributors:
10 * Jeff Martin - initial API and implementation
11 ******************************************************************************/
12package cuchaz.enigma;
13
14import cuchaz.enigma.analysis.EntryReference;
15import cuchaz.enigma.mapping.BehaviorEntry;
16import cuchaz.enigma.mapping.ClassEntry;
17import cuchaz.enigma.mapping.ConstructorEntry;
18import cuchaz.enigma.mapping.FieldEntry;
19import cuchaz.enigma.mapping.MethodEntry;
20
21public class EntryFactory {
22
23 public static ClassEntry newClass(String name) {
24 return new ClassEntry(name);
25 }
26
27 public static FieldEntry newField(String className, String fieldName) {
28 return new FieldEntry(newClass(className), fieldName);
29 }
30
31 public static MethodEntry newMethod(String className, String methodName, String methodSignature) {
32 return new MethodEntry(newClass(className), methodName, methodSignature);
33 }
34
35 public static ConstructorEntry newConstructor(String className, String signature) {
36 return new ConstructorEntry(newClass(className), signature);
37 }
38
39 public static EntryReference<FieldEntry,BehaviorEntry> newFieldReferenceByMethod(FieldEntry fieldEntry, String callerClassName, String callerName, String callerSignature) {
40 return new EntryReference<FieldEntry,BehaviorEntry>(fieldEntry, "", newMethod(callerClassName, callerName, callerSignature));
41 }
42
43 public static EntryReference<FieldEntry,BehaviorEntry> newFieldReferenceByConstructor(FieldEntry fieldEntry, String callerClassName, String callerSignature) {
44 return new EntryReference<FieldEntry,BehaviorEntry>(fieldEntry, "", newConstructor(callerClassName, callerSignature));
45 }
46
47 public static EntryReference<BehaviorEntry,BehaviorEntry> newBehaviorReferenceByMethod(BehaviorEntry behaviorEntry, String callerClassName, String callerName, String callerSignature) {
48 return new EntryReference<BehaviorEntry,BehaviorEntry>(behaviorEntry, "", newMethod(callerClassName, callerName, callerSignature));
49 }
50
51 public static EntryReference<BehaviorEntry,BehaviorEntry> newBehaviorReferenceByConstructor(BehaviorEntry behaviorEntry, String callerClassName, String callerSignature) {
52 return new EntryReference<BehaviorEntry,BehaviorEntry>(behaviorEntry, "", newConstructor(callerClassName, callerSignature));
53 }
54}
diff --git a/test/cuchaz/enigma/TestDeobfuscator.java b/test/cuchaz/enigma/TestDeobfuscator.java
new file mode 100644
index 0000000..129d7b2
--- /dev/null
+++ b/test/cuchaz/enigma/TestDeobfuscator.java
@@ -0,0 +1,54 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.\
3 *
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the GNU Public License v3.0
6 * which accompanies this distribution, and is available at
7 * http://www.gnu.org/licenses/gpl.html
8 *
9 * Contributors:
10 * Jeff Martin - initial API and implementation
11 ******************************************************************************/
12package cuchaz.enigma;
13
14import static org.junit.Assert.*;
15
16import java.io.IOException;
17import java.util.List;
18import java.util.jar.JarFile;
19
20import org.junit.Test;
21
22import com.google.common.collect.Lists;
23
24import cuchaz.enigma.mapping.ClassEntry;
25
26public class TestDeobfuscator {
27
28 private Deobfuscator getDeobfuscator() throws IOException {
29 return new Deobfuscator(new JarFile("build/testLoneClass.obf.jar"));
30 }
31
32 @Test
33 public void loadJar() throws Exception {
34 getDeobfuscator();
35 }
36
37 @Test
38 public void getClasses() throws Exception {
39 Deobfuscator deobfuscator = getDeobfuscator();
40 List<ClassEntry> obfClasses = Lists.newArrayList();
41 List<ClassEntry> deobfClasses = Lists.newArrayList();
42 deobfuscator.getSeparatedClasses(obfClasses, deobfClasses);
43 assertEquals(1, obfClasses.size());
44 assertEquals("none/a", obfClasses.get(0).getName());
45 assertEquals(1, deobfClasses.size());
46 assertEquals("cuchaz/enigma/inputs/Keep", deobfClasses.get(0).getName());
47 }
48
49 @Test
50 public void decompileClass() throws Exception {
51 Deobfuscator deobfuscator = getDeobfuscator();
52 deobfuscator.getSource(deobfuscator.getSourceTree("none/a"));
53 }
54}
diff --git a/test/cuchaz/enigma/TestInnerClasses.java b/test/cuchaz/enigma/TestInnerClasses.java
new file mode 100644
index 0000000..63c9b71
--- /dev/null
+++ b/test/cuchaz/enigma/TestInnerClasses.java
@@ -0,0 +1,89 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 *
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the GNU Public License v3.0
6 * which accompanies this distribution, and is available at
7 * http://www.gnu.org/licenses/gpl.html
8 *
9 * Contributors:
10 * Jeff Martin - initial API and implementation
11 ******************************************************************************/
12package cuchaz.enigma;
13
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.analysis.JarIndex;
22
23public class TestInnerClasses {
24
25 private JarIndex m_index;
26 private Deobfuscator m_deobfuscator;
27
28 private static final String AnonymousOuter = "none/a";
29 private static final String AnonymousInner = "b";
30 private static final String SimpleOuter = "none/g";
31 private static final String SimpleInner = "h";
32 private static final String ConstructorArgsOuter = "none/e";
33 private static final String ConstructorArgsInner = "f";
34 private static final String AnonymousWithScopeArgsOuter = "none/c";
35 private static final String AnonymousWithScopeArgsInner = "d";
36 private static final String AnonymousWithOuterAccessOuter = "none/i";
37 private static final String AnonymousWithOuterAccessInner = "j";
38
39 public TestInnerClasses() throws Exception {
40 m_index = new JarIndex();
41 JarFile jar = new JarFile("build/testInnerClasses.obf.jar");
42 m_index.indexJar(jar, true);
43 m_deobfuscator = new Deobfuscator(jar);
44 }
45
46 @Test
47 public void simple() {
48 assertThat(m_index.getOuterClass(SimpleInner), is(SimpleOuter));
49 assertThat(m_index.getInnerClasses(SimpleOuter), containsInAnyOrder(SimpleInner));
50 assertThat(m_index.isAnonymousClass(SimpleInner), is(false));
51 decompile(SimpleOuter);
52 }
53
54 @Test
55 public void anonymous() {
56 assertThat(m_index.getOuterClass(AnonymousInner), is(AnonymousOuter));
57 assertThat(m_index.getInnerClasses(AnonymousOuter), containsInAnyOrder(AnonymousInner));
58 assertThat(m_index.isAnonymousClass(AnonymousInner), is(true));
59 decompile(AnonymousOuter);
60 }
61
62 @Test
63 public void constructorArgs() {
64 assertThat(m_index.getOuterClass(ConstructorArgsInner), is(ConstructorArgsOuter));
65 assertThat(m_index.getInnerClasses(ConstructorArgsOuter), containsInAnyOrder(ConstructorArgsInner));
66 assertThat(m_index.isAnonymousClass(ConstructorArgsInner), is(false));
67 decompile(ConstructorArgsOuter);
68 }
69
70 @Test
71 public void anonymousWithScopeArgs() {
72 assertThat(m_index.getOuterClass(AnonymousWithScopeArgsInner), is(AnonymousWithScopeArgsOuter));
73 assertThat(m_index.getInnerClasses(AnonymousWithScopeArgsOuter), containsInAnyOrder(AnonymousWithScopeArgsInner));
74 assertThat(m_index.isAnonymousClass(AnonymousWithScopeArgsInner), is(true));
75 decompile(AnonymousWithScopeArgsOuter);
76 }
77
78 @Test
79 public void anonymousWithOuterAccess() {
80 assertThat(m_index.getOuterClass(AnonymousWithOuterAccessInner), is(AnonymousWithOuterAccessOuter));
81 assertThat(m_index.getInnerClasses(AnonymousWithOuterAccessOuter), containsInAnyOrder(AnonymousWithOuterAccessInner));
82 assertThat(m_index.isAnonymousClass(AnonymousWithOuterAccessInner), is(true));
83 decompile(AnonymousWithOuterAccessOuter);
84 }
85
86 private void decompile(String name) {
87 m_deobfuscator.getSourceTree(name);
88 }
89}
diff --git a/test/cuchaz/enigma/TestJarIndexConstructorReferences.java b/test/cuchaz/enigma/TestJarIndexConstructorReferences.java
new file mode 100644
index 0000000..8e3ad6d
--- /dev/null
+++ b/test/cuchaz/enigma/TestJarIndexConstructorReferences.java
@@ -0,0 +1,124 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static cuchaz.enigma.EntryFactory.*;
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;
27import cuchaz.enigma.mapping.ConstructorEntry;
28
29public class TestJarIndexConstructorReferences {
30
31 private JarIndex m_index;
32
33 private ClassEntry m_baseClass = new ClassEntry("none/a");
34 private ClassEntry m_subClass = new ClassEntry("none/d");
35 private ClassEntry m_subsubClass = new ClassEntry("none/e");
36 private ClassEntry m_defaultClass = new ClassEntry("none/c");
37 private ClassEntry m_callerClass = new ClassEntry("none/b");
38
39 public TestJarIndexConstructorReferences() throws Exception {
40 File jarFile = new File("build/testConstructors.obf.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 = new ConstructorEntry(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 = new ConstructorEntry(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 = new ConstructorEntry(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 = new ConstructorEntry(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 = new ConstructorEntry(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 = new ConstructorEntry(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 = new ConstructorEntry(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 = new ConstructorEntry(m_defaultClass, "()V");
120 assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder(
121 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "g", "()V")
122 ));
123 }
124}
diff --git a/test/cuchaz/enigma/TestJarIndexInheritanceTree.java b/test/cuchaz/enigma/TestJarIndexInheritanceTree.java
new file mode 100644
index 0000000..4d66397
--- /dev/null
+++ b/test/cuchaz/enigma/TestJarIndexInheritanceTree.java
@@ -0,0 +1,228 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static cuchaz.enigma.EntryFactory.*;
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.EntryReference;
25import cuchaz.enigma.analysis.JarIndex;
26import cuchaz.enigma.analysis.TranslationIndex;
27import cuchaz.enigma.mapping.BehaviorEntry;
28import cuchaz.enigma.mapping.ClassEntry;
29import cuchaz.enigma.mapping.ConstructorEntry;
30import cuchaz.enigma.mapping.FieldEntry;
31import cuchaz.enigma.mapping.MethodEntry;
32
33public class TestJarIndexInheritanceTree {
34
35 private JarIndex m_index;
36
37 private ClassEntry m_baseClass = new ClassEntry("none/a");
38 private ClassEntry m_subClassA = new ClassEntry("none/b");
39 private ClassEntry m_subClassAA = new ClassEntry("none/d");
40 private ClassEntry m_subClassB = new ClassEntry("none/c");
41 private FieldEntry m_nameField = new FieldEntry(m_baseClass, "a");
42 private FieldEntry m_numThingsField = new FieldEntry(m_subClassB, "a");
43
44 public TestJarIndexInheritanceTree() throws Exception {
45 m_index = new JarIndex();
46 m_index.indexJar(new JarFile("build/testInheritanceTree.obf.jar"), false);
47 }
48
49 @Test
50 public void obfEntries() {
51 assertThat(m_index.getObfClassEntries(), containsInAnyOrder(
52 newClass("cuchaz/enigma/inputs/Keep"),
53 m_baseClass,
54 m_subClassA,
55 m_subClassAA,
56 m_subClassB
57 ));
58 }
59
60 @Test
61 public void translationIndex() {
62
63 TranslationIndex index = m_index.getTranslationIndex();
64
65 // base class
66 assertThat(index.getSuperclass(m_baseClass), is(nullValue()));
67 assertThat(index.getAncestry(m_baseClass), is(empty()));
68 assertThat(index.getSubclass(m_baseClass), containsInAnyOrder(
69 m_subClassA,
70 m_subClassB
71 ));
72
73 // subclass a
74 assertThat(index.getSuperclass(m_subClassA), is(m_baseClass));
75 assertThat(index.getAncestry(m_subClassA), contains(m_baseClass));
76 assertThat(index.getSubclass(m_subClassA), contains(m_subClassAA));
77
78 // subclass aa
79 assertThat(index.getSuperclass(m_subClassAA), is(m_subClassA));
80 assertThat(index.getAncestry(m_subClassAA), contains(m_subClassA, m_baseClass));
81 assertThat(index.getSubclass(m_subClassAA), is(empty()));
82
83 // subclass b
84 assertThat(index.getSuperclass(m_subClassB), is(m_baseClass));
85 assertThat(index.getAncestry(m_subClassB), contains(m_baseClass));
86 assertThat(index.getSubclass(m_subClassB), is(empty()));
87 }
88
89 @Test
90 public void access() {
91 assertThat(m_index.getAccess(m_nameField), is(Access.Private));
92 assertThat(m_index.getAccess(m_numThingsField), is(Access.Private));
93 }
94
95 @Test
96 public void relatedMethodImplementations() {
97
98 Set<MethodEntry> entries;
99
100 // getName()
101 entries = m_index.getRelatedMethodImplementations(new MethodEntry(m_baseClass, "a", "()Ljava/lang/String;"));
102 assertThat(entries, containsInAnyOrder(
103 new MethodEntry(m_baseClass, "a", "()Ljava/lang/String;"),
104 new MethodEntry(m_subClassAA, "a", "()Ljava/lang/String;")
105 ));
106 entries = m_index.getRelatedMethodImplementations(new MethodEntry(m_subClassAA, "a", "()Ljava/lang/String;"));
107 assertThat(entries, containsInAnyOrder(
108 new MethodEntry(m_baseClass, "a", "()Ljava/lang/String;"),
109 new MethodEntry(m_subClassAA, "a", "()Ljava/lang/String;")
110 ));
111
112 // doBaseThings()
113 entries = m_index.getRelatedMethodImplementations(new MethodEntry(m_baseClass, "a", "()V"));
114 assertThat(entries, containsInAnyOrder(
115 new MethodEntry(m_baseClass, "a", "()V"),
116 new MethodEntry(m_subClassAA, "a", "()V"),
117 new MethodEntry(m_subClassB, "a", "()V")
118 ));
119 entries = m_index.getRelatedMethodImplementations(new MethodEntry(m_subClassAA, "a", "()V"));
120 assertThat(entries, containsInAnyOrder(
121 new MethodEntry(m_baseClass, "a", "()V"),
122 new MethodEntry(m_subClassAA, "a", "()V"),
123 new MethodEntry(m_subClassB, "a", "()V")
124 ));
125 entries = m_index.getRelatedMethodImplementations(new MethodEntry(m_subClassB, "a", "()V"));
126 assertThat(entries, containsInAnyOrder(
127 new MethodEntry(m_baseClass, "a", "()V"),
128 new MethodEntry(m_subClassAA, "a", "()V"),
129 new MethodEntry(m_subClassB, "a", "()V")
130 ));
131
132 // doBThings
133 entries = m_index.getRelatedMethodImplementations(new MethodEntry(m_subClassB, "b", "()V"));
134 assertThat(entries, containsInAnyOrder(new MethodEntry(m_subClassB, "b", "()V")));
135 }
136
137 @Test
138 @SuppressWarnings("unchecked")
139 public void fieldReferences() {
140 Collection<EntryReference<FieldEntry,BehaviorEntry>> references;
141
142 // name
143 references = m_index.getFieldReferences(m_nameField);
144 assertThat(references, containsInAnyOrder(
145 newFieldReferenceByConstructor(m_nameField, m_baseClass.getName(), "(Ljava/lang/String;)V"),
146 newFieldReferenceByMethod(m_nameField, m_baseClass.getName(), "a", "()Ljava/lang/String;")
147 ));
148
149 // numThings
150 references = m_index.getFieldReferences(m_numThingsField);
151 assertThat(references, containsInAnyOrder(
152 newFieldReferenceByConstructor(m_numThingsField, m_subClassB.getName(), "()V"),
153 newFieldReferenceByMethod(m_numThingsField, m_subClassB.getName(), "b", "()V")
154 ));
155 }
156
157 @Test
158 @SuppressWarnings("unchecked")
159 public void behaviorReferences() {
160
161 BehaviorEntry source;
162 Collection<EntryReference<BehaviorEntry,BehaviorEntry>> references;
163
164 // baseClass constructor
165 source = new ConstructorEntry(m_baseClass, "(Ljava/lang/String;)V");
166 references = m_index.getBehaviorReferences(source);
167 assertThat(references, containsInAnyOrder(
168 newBehaviorReferenceByConstructor(source, m_subClassA.getName(), "(Ljava/lang/String;)V"),
169 newBehaviorReferenceByConstructor(source, m_subClassB.getName(), "()V")
170 ));
171
172 // subClassA constructor
173 source = new ConstructorEntry(m_subClassA, "(Ljava/lang/String;)V");
174 references = m_index.getBehaviorReferences(source);
175 assertThat(references, containsInAnyOrder(
176 newBehaviorReferenceByConstructor(source, m_subClassAA.getName(), "()V")
177 ));
178
179 // baseClass.getName()
180 source = new MethodEntry(m_baseClass, "a", "()Ljava/lang/String;");
181 references = m_index.getBehaviorReferences(source);
182 assertThat(references, containsInAnyOrder(
183 newBehaviorReferenceByMethod(source, m_subClassAA.getName(), "a", "()Ljava/lang/String;"),
184 newBehaviorReferenceByMethod(source, m_subClassB.getName(), "a", "()V")
185 ));
186
187 // subclassAA.getName()
188 source = new MethodEntry(m_subClassAA, "a", "()Ljava/lang/String;");
189 references = m_index.getBehaviorReferences(source);
190 assertThat(references, containsInAnyOrder(
191 newBehaviorReferenceByMethod(source, m_subClassAA.getName(), "a", "()V")
192 ));
193 }
194
195 @Test
196 public void containsEntries() {
197
198 // classes
199 assertThat(m_index.containsObfClass(m_baseClass), is(true));
200 assertThat(m_index.containsObfClass(m_subClassA), is(true));
201 assertThat(m_index.containsObfClass(m_subClassAA), is(true));
202 assertThat(m_index.containsObfClass(m_subClassB), is(true));
203
204 // fields
205 assertThat(m_index.containsObfField(m_nameField), is(true));
206 assertThat(m_index.containsObfField(m_numThingsField), is(true));
207
208 // methods
209 // getName()
210 assertThat(m_index.containsObfBehavior(new MethodEntry(m_baseClass, "a", "()Ljava/lang/String;")), is(true));
211 assertThat(m_index.containsObfBehavior(new MethodEntry(m_subClassA, "a", "()Ljava/lang/String;")), is(false));
212 assertThat(m_index.containsObfBehavior(new MethodEntry(m_subClassAA, "a", "()Ljava/lang/String;")), is(true));
213 assertThat(m_index.containsObfBehavior(new MethodEntry(m_subClassB, "a", "()Ljava/lang/String;")), is(false));
214
215 // doBaseThings()
216 assertThat(m_index.containsObfBehavior(new MethodEntry(m_baseClass, "a", "()V")), is(true));
217 assertThat(m_index.containsObfBehavior(new MethodEntry(m_subClassA, "a", "()V")), is(false));
218 assertThat(m_index.containsObfBehavior(new MethodEntry(m_subClassAA, "a", "()V")), is(true));
219 assertThat(m_index.containsObfBehavior(new MethodEntry(m_subClassB, "a", "()V")), is(true));
220
221 // doBThings()
222 assertThat(m_index.containsObfBehavior(new MethodEntry(m_baseClass, "b", "()V")), is(false));
223 assertThat(m_index.containsObfBehavior(new MethodEntry(m_subClassA, "b", "()V")), is(false));
224 assertThat(m_index.containsObfBehavior(new MethodEntry(m_subClassAA, "b", "()V")), is(false));
225 assertThat(m_index.containsObfBehavior(new MethodEntry(m_subClassB, "b", "()V")), is(true));
226
227 }
228}
diff --git a/test/cuchaz/enigma/TestJarIndexLoneClass.java b/test/cuchaz/enigma/TestJarIndexLoneClass.java
new file mode 100644
index 0000000..a061b72
--- /dev/null
+++ b/test/cuchaz/enigma/TestJarIndexLoneClass.java
@@ -0,0 +1,169 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 *
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the GNU Public License v3.0
6 * which accompanies this distribution, and is available at
7 * http://www.gnu.org/licenses/gpl.html
8 *
9 * Contributors:
10 * Jeff Martin - initial API and implementation
11 ******************************************************************************/
12package cuchaz.enigma;
13
14import static cuchaz.enigma.EntryFactory.*;
15import static org.hamcrest.MatcherAssert.*;
16import static org.hamcrest.Matchers.*;
17
18import java.util.Collection;
19import java.util.Set;
20import java.util.jar.JarFile;
21
22import org.junit.Test;
23
24import cuchaz.enigma.analysis.Access;
25import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
26import cuchaz.enigma.analysis.ClassInheritanceTreeNode;
27import cuchaz.enigma.analysis.EntryReference;
28import cuchaz.enigma.analysis.JarIndex;
29import cuchaz.enigma.analysis.MethodImplementationsTreeNode;
30import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
31import cuchaz.enigma.mapping.BehaviorEntry;
32import cuchaz.enigma.mapping.ClassEntry;
33import cuchaz.enigma.mapping.FieldEntry;
34import cuchaz.enigma.mapping.MethodEntry;
35import cuchaz.enigma.mapping.Translator;
36
37public class TestJarIndexLoneClass {
38
39 private JarIndex m_index;
40
41 public TestJarIndexLoneClass() throws Exception {
42 m_index = new JarIndex();
43 m_index.indexJar(new JarFile("build/testLoneClass.obf.jar"), false);
44 }
45
46 @Test
47 public void obfEntries() {
48 assertThat(m_index.getObfClassEntries(), containsInAnyOrder(
49 newClass("cuchaz/enigma/inputs/Keep"),
50 newClass("none/a")
51 ));
52 }
53
54 @Test
55 public void translationIndex() {
56 assertThat(m_index.getTranslationIndex().getSuperclass(new ClassEntry("none/a")), is(nullValue()));
57 assertThat(m_index.getTranslationIndex().getSuperclass(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(nullValue()));
58 assertThat(m_index.getTranslationIndex().getAncestry(new ClassEntry("none/a")), is(empty()));
59 assertThat(m_index.getTranslationIndex().getAncestry(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(empty()));
60 assertThat(m_index.getTranslationIndex().getSubclass(new ClassEntry("none/a")), is(empty()));
61 assertThat(m_index.getTranslationIndex().getSubclass(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(empty()));
62 }
63
64 @Test
65 public void access() {
66 assertThat(m_index.getAccess(newField("none/a", "a")), is(Access.Private));
67 assertThat(m_index.getAccess(newMethod("none/a", "a", "()Ljava/lang/String;")), is(Access.Public));
68 assertThat(m_index.getAccess(newField("none/a", "b")), 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 MethodImplementationsTreeNode node = m_index.getMethodImplementations(new Translator(), source);
98 assertThat(node, is(nullValue()));
99 }
100
101 @Test
102 public void relatedMethodImplementations() {
103 Set<MethodEntry> entries = m_index.getRelatedMethodImplementations(newMethod("none/a", "a", "()Ljava/lang/String;"));
104 assertThat(entries, containsInAnyOrder(
105 newMethod("none/a", "a", "()Ljava/lang/String;")
106 ));
107 }
108
109 @Test
110 @SuppressWarnings("unchecked")
111 public void fieldReferences() {
112 FieldEntry source = newField("none/a", "a");
113 Collection<EntryReference<FieldEntry,BehaviorEntry>> references = m_index.getFieldReferences(source);
114 assertThat(references, containsInAnyOrder(
115 newFieldReferenceByConstructor(source, "none/a", "(Ljava/lang/String;)V"),
116 newFieldReferenceByMethod(source, "none/a", "a", "()Ljava/lang/String;")
117 ));
118 }
119
120 @Test
121 public void behaviorReferences() {
122 assertThat(m_index.getBehaviorReferences(newMethod("none/a", "a", "()Ljava/lang/String;")), is(empty()));
123 }
124
125 @Test
126 public void innerClasses() {
127 assertThat(m_index.getInnerClasses("none/a"), is(empty()));
128 }
129
130 @Test
131 public void outerClass() {
132 assertThat(m_index.getOuterClass("a"), is(nullValue()));
133 }
134
135 @Test
136 public void isAnonymousClass() {
137 assertThat(m_index.isAnonymousClass("none/a"), is(false));
138 }
139
140 @Test
141 public void interfaces() {
142 assertThat(m_index.getInterfaces("none/a"), is(empty()));
143 }
144
145 @Test
146 public void implementingClasses() {
147 assertThat(m_index.getImplementingClasses("none/a"), is(empty()));
148 }
149
150 @Test
151 public void isInterface() {
152 assertThat(m_index.isInterface("none/a"), is(false));
153 }
154
155 @Test
156 public void bridgeMethods() {
157 assertThat(m_index.getBridgeMethod(newMethod("none/a", "a", "()Ljava/lang/String;")), is(nullValue()));
158 }
159
160 @Test
161 public void contains() {
162 assertThat(m_index.containsObfClass(newClass("none/a")), is(true));
163 assertThat(m_index.containsObfClass(newClass("none/b")), is(false));
164 assertThat(m_index.containsObfField(newField("none/a", "a")), is(true));
165 assertThat(m_index.containsObfField(newField("none/a", "b")), is(false));
166 assertThat(m_index.containsObfBehavior(newMethod("none/a", "a", "()Ljava/lang/String;")), is(true));
167 assertThat(m_index.containsObfBehavior(newMethod("none/a", "b", "()Ljava/lang/String;")), is(false));
168 }
169}
diff --git a/test/cuchaz/enigma/TestSourceIndex.java b/test/cuchaz/enigma/TestSourceIndex.java
new file mode 100644
index 0000000..70a5ee4
--- /dev/null
+++ b/test/cuchaz/enigma/TestSourceIndex.java
@@ -0,0 +1,49 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 *
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the GNU Public License v3.0
6 * which accompanies this distribution, and is available at
7 * http://www.gnu.org/licenses/gpl.html
8 *
9 * Contributors:
10 * Jeff Martin - initial API and implementation
11 ******************************************************************************/
12package cuchaz.enigma;
13
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
26 // TEMP
27 @Test
28 public void indexEverything() throws Exception {
29 Deobfuscator deobfuscator = new Deobfuscator(new JarFile("input/1.8.jar"));
30
31 // get all classes that aren't inner classes
32 Set<ClassEntry> classEntries = Sets.newHashSet();
33 for (ClassEntry obfClassEntry : deobfuscator.getJarIndex().getObfClassEntries()) {
34 if (!obfClassEntry.isInnerClass()) {
35 classEntries.add(obfClassEntry);
36 }
37 }
38
39 for (ClassEntry obfClassEntry : classEntries) {
40 try {
41 CompilationUnit tree = deobfuscator.getSourceTree(obfClassEntry.getName());
42 String source = deobfuscator.getSource(tree);
43 deobfuscator.getSourceIndex(tree, source);
44 } catch (Throwable t) {
45 throw new Error("Unable to index " + obfClassEntry, t);
46 }
47 }
48 }
49}
diff --git a/test/cuchaz/enigma/TestTokensConstructors.java b/test/cuchaz/enigma/TestTokensConstructors.java
new file mode 100644
index 0000000..56424ae
--- /dev/null
+++ b/test/cuchaz/enigma/TestTokensConstructors.java
@@ -0,0 +1,135 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static cuchaz.enigma.EntryFactory.*;
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() throws Exception {
26 super(new JarFile("build/testConstructors.obf.jar"));
27 }
28
29 @Test
30 public void baseDeclarations() {
31 assertThat(getDeclarationToken(newConstructor("none/a", "()V")), is("a"));
32 assertThat(getDeclarationToken(newConstructor("none/a", "(I)V")), is("a"));
33 }
34
35 @Test
36 public void subDeclarations() {
37 assertThat(getDeclarationToken(newConstructor("none/d", "()V")), is("d"));
38 assertThat(getDeclarationToken(newConstructor("none/d", "(I)V")), is("d"));
39 assertThat(getDeclarationToken(newConstructor("none/d", "(II)V")), is("d"));
40 assertThat(getDeclarationToken(newConstructor("none/d", "(III)V")), is("d"));
41 }
42
43 @Test
44 public void subsubDeclarations() {
45 assertThat(getDeclarationToken(newConstructor("none/e", "(I)V")), is("e"));
46 }
47
48 @Test
49 public void defaultDeclarations() {
50 assertThat(getDeclarationToken(newConstructor("none/c", "()V")), nullValue());
51 }
52
53 @Test
54 public void baseDefaultReferences() {
55 BehaviorEntry source = newConstructor("none/a", "()V");
56 assertThat(
57 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "a", "()V")),
58 containsInAnyOrder("a")
59 );
60 assertThat(
61 getReferenceTokens(newBehaviorReferenceByConstructor(source, "none/d", "()V")),
62 is(empty()) // implicit call, not decompiled to token
63 );
64 assertThat(
65 getReferenceTokens(newBehaviorReferenceByConstructor(source, "none/d", "(III)V")),
66 is(empty()) // implicit call, not decompiled to token
67 );
68 }
69
70 @Test
71 public void baseIntReferences() {
72 BehaviorEntry source = newConstructor("none/a", "(I)V");
73 assertThat(
74 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "b", "()V")),
75 containsInAnyOrder("a")
76 );
77 }
78
79 @Test
80 public void subDefaultReferences() {
81 BehaviorEntry source = newConstructor("none/d", "()V");
82 assertThat(
83 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "c", "()V")),
84 containsInAnyOrder("d")
85 );
86 assertThat(
87 getReferenceTokens(newBehaviorReferenceByConstructor(source, "none/d", "(I)V")),
88 containsInAnyOrder("this")
89 );
90 }
91
92 @Test
93 public void subIntReferences() {
94 BehaviorEntry source = newConstructor("none/d", "(I)V");
95 assertThat(getReferenceTokens(
96 newBehaviorReferenceByMethod(source, "none/b", "d", "()V")),
97 containsInAnyOrder("d")
98 );
99 assertThat(getReferenceTokens(
100 newBehaviorReferenceByConstructor(source, "none/d", "(II)V")),
101 containsInAnyOrder("this")
102 );
103 assertThat(getReferenceTokens(
104 newBehaviorReferenceByConstructor(source, "none/e", "(I)V")),
105 containsInAnyOrder("super")
106 );
107 }
108
109 @Test
110 public void subIntIntReferences() {
111 BehaviorEntry source = newConstructor("none/d", "(II)V");
112 assertThat(
113 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "e", "()V")),
114 containsInAnyOrder("d")
115 );
116 }
117
118 @Test
119 public void subsubIntReferences() {
120 BehaviorEntry source = newConstructor("none/e", "(I)V");
121 assertThat(
122 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "f", "()V")),
123 containsInAnyOrder("e")
124 );
125 }
126
127 @Test
128 public void defaultConstructableReferences() {
129 BehaviorEntry source = newConstructor("none/c", "()V");
130 assertThat(
131 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "g", "()V")),
132 containsInAnyOrder("c")
133 );
134 }
135}
diff --git a/test/cuchaz/enigma/TokenChecker.java b/test/cuchaz/enigma/TokenChecker.java
new file mode 100644
index 0000000..febea2a
--- /dev/null
+++ b/test/cuchaz/enigma/TokenChecker.java
@@ -0,0 +1,64 @@
1/*******************************************************************************
2 * Copyright (c) 2014 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Public License v3.0
5 * which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/gpl.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) throws IOException {
31 m_deobfuscator = new Deobfuscator(jarFile);
32 }
33
34 protected String getDeclarationToken(Entry entry) {
35 // decompile the class
36 CompilationUnit tree = m_deobfuscator.getSourceTree(entry.getClassName());
37 // DEBUG
38 // tree.acceptVisitor( new TreeDumpVisitor( new File( "tree." + entry.getClassName().replace( '/', '.' ) + ".txt" ) ), null );
39 String source = m_deobfuscator.getSource(tree);
40 SourceIndex index = m_deobfuscator.getSourceIndex(tree, source);
41
42 // get the token value
43 Token token = index.getDeclarationToken(entry);
44 if (token == null) {
45 return null;
46 }
47 return source.substring(token.start, token.end);
48 }
49
50 @SuppressWarnings("unchecked")
51 protected Collection<String> getReferenceTokens(EntryReference<? extends Entry,? extends Entry> reference) {
52 // decompile the class
53 CompilationUnit tree = m_deobfuscator.getSourceTree(reference.context.getClassName());
54 String source = m_deobfuscator.getSource(tree);
55 SourceIndex index = m_deobfuscator.getSourceIndex(tree, source);
56
57 // get the token values
58 List<String> values = Lists.newArrayList();
59 for (Token token : index.getReferenceTokens((EntryReference<Entry,Entry>)reference)) {
60 values.add(source.substring(token.start, token.end));
61 }
62 return values;
63 }
64}
diff --git a/test/cuchaz/enigma/inputs/Keep.java b/test/cuchaz/enigma/inputs/Keep.java
new file mode 100644
index 0000000..390e82f
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/Keep.java
@@ -0,0 +1,7 @@
1package cuchaz.enigma.inputs;
2
3public class Keep {
4 public static void main(String[] args) {
5 System.out.println("Keep me!");
6 }
7}
diff --git a/test/cuchaz/enigma/inputs/constructors/BaseClass.java b/test/cuchaz/enigma/inputs/constructors/BaseClass.java
new file mode 100644
index 0000000..9345308
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/constructors/BaseClass.java
@@ -0,0 +1,15 @@
1package cuchaz.enigma.inputs.constructors;
2
3// none/a
4public class BaseClass {
5
6 // <init>()V
7 public BaseClass() {
8 System.out.println("Default constructor");
9 }
10
11 // <init>(I)V
12 public BaseClass(int i) {
13 System.out.println("Int constructor " + i);
14 }
15}
diff --git a/test/cuchaz/enigma/inputs/constructors/Caller.java b/test/cuchaz/enigma/inputs/constructors/Caller.java
new file mode 100644
index 0000000..5727875
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/constructors/Caller.java
@@ -0,0 +1,47 @@
1package cuchaz.enigma.inputs.constructors;
2
3// none/b
4public class Caller {
5
6 // a()V
7 public void callBaseDefault() {
8 // none/a.<init>()V
9 System.out.println(new BaseClass());
10 }
11
12 // b()V
13 public void callBaseInt() {
14 // none/a.<init>(I)V
15 System.out.println(new BaseClass(5));
16 }
17
18 // c()V
19 public void callSubDefault() {
20 // none/d.<init>()V
21 System.out.println(new SubClass());
22 }
23
24 // d()V
25 public void callSubInt() {
26 // none/d.<init>(I)V
27 System.out.println(new SubClass(6));
28 }
29
30 // e()V
31 public void callSubIntInt() {
32 // none/d.<init>(II)V
33 System.out.println(new SubClass(4, 2));
34 }
35
36 // f()V
37 public void callSubSubInt() {
38 // none/e.<init>(I)V
39 System.out.println(new SubSubClass(3));
40 }
41
42 // g()V
43 public void callDefaultConstructable() {
44 // none/c.<init>()V
45 System.out.println(new DefaultConstructable());
46 }
47}
diff --git a/test/cuchaz/enigma/inputs/constructors/DefaultConstructable.java b/test/cuchaz/enigma/inputs/constructors/DefaultConstructable.java
new file mode 100644
index 0000000..26a3ddb
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/constructors/DefaultConstructable.java
@@ -0,0 +1,5 @@
1package cuchaz.enigma.inputs.constructors;
2
3public class DefaultConstructable {
4 // only default constructor
5}
diff --git a/test/cuchaz/enigma/inputs/constructors/SubClass.java b/test/cuchaz/enigma/inputs/constructors/SubClass.java
new file mode 100644
index 0000000..fecfa2b
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/constructors/SubClass.java
@@ -0,0 +1,28 @@
1package cuchaz.enigma.inputs.constructors;
2
3// none/d extends none/a
4public class SubClass extends BaseClass {
5
6 // <init>()V
7 public SubClass() {
8 // none/a.<init>()V
9 }
10
11 // <init>(I)V
12 public SubClass(int num) {
13 // <init>()V
14 this();
15 System.out.println("SubClass " + num);
16 }
17
18 // <init>(II)V
19 public SubClass(int a, int b) {
20 // <init>(I)V
21 this(a + b);
22 }
23
24 // <init>(III)V
25 public SubClass(int a, int b, int c) {
26 // none/a.<init>()V
27 }
28}
diff --git a/test/cuchaz/enigma/inputs/constructors/SubSubClass.java b/test/cuchaz/enigma/inputs/constructors/SubSubClass.java
new file mode 100644
index 0000000..ab84161
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/constructors/SubSubClass.java
@@ -0,0 +1,11 @@
1package cuchaz.enigma.inputs.constructors;
2
3// none/e extends none/d
4public class SubSubClass extends SubClass {
5
6 // <init>(I)V
7 public SubSubClass(int i) {
8 // none/c.<init>(I)V
9 super(i);
10 }
11}
diff --git a/test/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java b/test/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java
new file mode 100644
index 0000000..5b416c4
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java
@@ -0,0 +1,21 @@
1package cuchaz.enigma.inputs.inheritanceTree;
2
3// none/a
4public abstract class BaseClass {
5
6 // a
7 private String m_name;
8
9 // <init>(Ljava/lang/String;)V
10 protected BaseClass(String name) {
11 m_name = name;
12 }
13
14 // a()Ljava/lang/String;
15 public String getName() {
16 return m_name;
17 }
18
19 // a()V
20 public abstract void doBaseThings();
21}
diff --git a/test/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java b/test/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java
new file mode 100644
index 0000000..7a99d51
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java
@@ -0,0 +1,11 @@
1package cuchaz.enigma.inputs.inheritanceTree;
2
3// none/b extends none/a
4public abstract class SubclassA extends BaseClass {
5
6 // <init>(Ljava/lang/String;)V
7 protected SubclassA(String name) {
8 // call to none/a.<init>(Ljava/lang/String)V
9 super(name);
10 }
11}
diff --git a/test/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java b/test/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java
new file mode 100644
index 0000000..c9485d3
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java
@@ -0,0 +1,30 @@
1package cuchaz.enigma.inputs.inheritanceTree;
2
3// none/c extends none/a
4public class SubclassB extends BaseClass {
5
6 // a
7 private int m_numThings;
8
9 // <init>()V
10 protected SubclassB() {
11 // none/a.<init>(Ljava/lang/String;)V
12 super("B");
13
14 // access to a
15 m_numThings = 4;
16 }
17
18 @Override
19 // a()V
20 public void doBaseThings() {
21 // call to none/a.a()Ljava/lang/String;
22 System.out.println("Base things by B! " + getName());
23 }
24
25 // b()V
26 public void doBThings() {
27 // access to a
28 System.out.println("" + m_numThings + " B things!");
29 }
30}
diff --git a/test/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java b/test/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java
new file mode 100644
index 0000000..afd03ac
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java
@@ -0,0 +1,24 @@
1package cuchaz.enigma.inputs.inheritanceTree;
2
3// none/d extends none/b
4public class SubsubclassAA extends SubclassA {
5
6 protected SubsubclassAA() {
7 // call to none/b.<init>(Ljava/lang/String;)V
8 super("AA");
9 }
10
11 @Override
12 // a()Ljava/lang/String;
13 public String getName() {
14 // call to none/b.a()Ljava/lang/String;
15 return "subsub" + super.getName();
16 }
17
18 @Override
19 // a()V
20 public void doBaseThings() {
21 // call to none/d.a()Ljava/lang/String;
22 System.out.println("Base things by " + getName());
23 }
24}
diff --git a/test/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java b/test/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java
new file mode 100644
index 0000000..f7118f6
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java
@@ -0,0 +1,14 @@
1package cuchaz.enigma.inputs.innerClasses;
2
3public class A_Anonymous {
4
5 public void foo() {
6 Runnable runnable = new Runnable() {
7 @Override
8 public void run() {
9 // don't care
10 }
11 };
12 runnable.run();
13 }
14}
diff --git a/test/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java b/test/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java
new file mode 100644
index 0000000..42fba9a
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java
@@ -0,0 +1,13 @@
1package cuchaz.enigma.inputs.innerClasses;
2
3public class B_AnonymousWithScopeArgs {
4
5 public static void foo(final D_Simple arg) {
6 System.out.println(new Object() {
7 @Override
8 public String toString() {
9 return arg.toString();
10 }
11 });
12 }
13}
diff --git a/test/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java b/test/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java
new file mode 100644
index 0000000..8fa6c5b
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java
@@ -0,0 +1,20 @@
1package cuchaz.enigma.inputs.innerClasses;
2
3@SuppressWarnings("unused")
4public class C_ConstructorArgs {
5
6 class Inner {
7
8 private int a;
9
10 public Inner(int a) {
11 this.a = a;
12 }
13 }
14
15 Inner i;
16
17 public void foo() {
18 i = new Inner(5);
19 }
20}
diff --git a/test/cuchaz/enigma/inputs/innerClasses/D_Simple.java b/test/cuchaz/enigma/inputs/innerClasses/D_Simple.java
new file mode 100644
index 0000000..c4fc0ef
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/innerClasses/D_Simple.java
@@ -0,0 +1,8 @@
1package cuchaz.enigma.inputs.innerClasses;
2
3public class D_Simple {
4
5 class Inner {
6 // nothing to do
7 }
8}
diff --git a/test/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java b/test/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java
new file mode 100644
index 0000000..e1de53c
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java
@@ -0,0 +1,21 @@
1package cuchaz.enigma.inputs.innerClasses;
2
3public class E_AnonymousWithOuterAccess {
4
5 // reproduction of error case documented at:
6 // https://bitbucket.org/cuchaz/enigma/issue/61/stackoverflowerror-when-deobfuscating
7
8 public Object makeInner() {
9 outerMethod();
10 return new Object() {
11 @Override
12 public String toString() {
13 return outerMethod();
14 }
15 };
16 }
17
18 private String outerMethod() {
19 return "foo";
20 }
21}
diff --git a/test/cuchaz/enigma/inputs/loneClass/LoneClass.java b/test/cuchaz/enigma/inputs/loneClass/LoneClass.java
new file mode 100644
index 0000000..18c716e
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/loneClass/LoneClass.java
@@ -0,0 +1,14 @@
1package cuchaz.enigma.inputs.loneClass;
2
3public class LoneClass {
4
5 private String m_name;
6
7 public LoneClass(String name) {
8 m_name = name;
9 }
10
11 public String getName() {
12 return m_name;
13 }
14}