summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar jeff2015-02-08 21:29:25 -0500
committerGravatar jeff2015-02-08 21:29:25 -0500
commited9b5cdfc648e86fd463bfa8d86b94c41671e14c (patch)
tree2619bbc7e04dfa3b82f8dfd3b1d31f529766cd4b
downloadenigma-fork-ed9b5cdfc648e86fd463bfa8d86b94c41671e14c.tar.gz
enigma-fork-ed9b5cdfc648e86fd463bfa8d86b94c41671e14c.tar.xz
enigma-fork-ed9b5cdfc648e86fd463bfa8d86b94c41671e14c.zip
switch all classes to new signature/type system
-rw-r--r--.classpath11
-rw-r--r--.hgignore9
-rw-r--r--.project17
-rw-r--r--.settings/org.eclipse.jdt.core.prefs13
-rw-r--r--build.py120
-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.java734
-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.java164
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java115
-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.java127
-rw-r--r--src/cuchaz/enigma/bytecode/ClassRenamer.java110
-rw-r--r--src/cuchaz/enigma/bytecode/ClassTranslator.java144
-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.java53
-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.java406
-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.java1165
-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/ClassNameReplacer.java5
-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.java83
-rw-r--r--src/cuchaz/enigma/mapping/MappingParseException.java29
-rw-r--r--src/cuchaz/enigma/mapping/Mappings.java188
-rw-r--r--src/cuchaz/enigma/mapping/MappingsReader.java175
-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.java161
-rw-r--r--src/cuchaz/enigma/mapping/NameValidator.java80
-rw-r--r--src/cuchaz/enigma/mapping/Signature.java109
-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.java239
-rw-r--r--src/cuchaz/enigma/mapping/Type.java218
-rw-r--r--test/cuchaz/enigma/EntryFactory.java67
-rw-r--r--test/cuchaz/enigma/TestDeobfuscator.java58
-rw-r--r--test/cuchaz/enigma/TestInnerClasses.java90
-rw-r--r--test/cuchaz/enigma/TestJarIndexConstructorReferences.java124
-rw-r--r--test/cuchaz/enigma/TestJarIndexInheritanceTree.java228
-rw-r--r--test/cuchaz/enigma/TestJarIndexLoneClass.java165
-rw-r--r--test/cuchaz/enigma/TestSignature.java266
-rw-r--r--test/cuchaz/enigma/TestSourceIndex.java50
-rw-r--r--test/cuchaz/enigma/TestTokensConstructors.java136
-rw-r--r--test/cuchaz/enigma/TestTranslator.java39
-rw-r--r--test/cuchaz/enigma/TestType.java229
-rw-r--r--test/cuchaz/enigma/TokenChecker.java65
-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
-rw-r--r--test/cuchaz/enigma/inputs/translation/A.java8
-rw-r--r--test/cuchaz/enigma/resources/translation.mappings4
132 files changed, 15543 insertions, 0 deletions
diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..16e0b31
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,11 @@
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 combineaccessrules="false" kind="src" path="/procyon"/>
8 <classpathentry kind="lib" path="lib/deps.jar"/>
9 <classpathentry kind="lib" path="lib/test-deps.jar"/>
10 <classpathentry kind="output" path="bin"/>
11</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..4729498
--- /dev/null
+++ b/build.py
@@ -0,0 +1,120 @@
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 buildTestJar("testTranslation", "cuchaz/enigma/inputs/translation/*.class")
109
110def taskBuild():
111 ssjb.file.delete(DirBuild)
112 ssjb.file.mkdir(DirBuild)
113 buildStandaloneJar(DirBuild)
114 buildLibJar(DirBuild)
115
116ssjb.registerTask("getDeps", taskGetDeps)
117ssjb.registerTask("buildTestJars", taskBuildTestJars)
118ssjb.registerTask("build", taskBuild)
119ssjb.run()
120
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..cfa03a1
--- /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..3aac8bd
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/JarIndex.java
@@ -0,0 +1,734 @@
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.bytecode.AccessFlag;
27import javassist.bytecode.Descriptor;
28import javassist.bytecode.FieldInfo;
29import javassist.expr.ConstructorCall;
30import javassist.expr.ExprEditor;
31import javassist.expr.FieldAccess;
32import javassist.expr.MethodCall;
33import javassist.expr.NewExpr;
34
35import com.google.common.collect.HashMultimap;
36import com.google.common.collect.Lists;
37import com.google.common.collect.Maps;
38import com.google.common.collect.Multimap;
39import com.google.common.collect.Sets;
40
41import cuchaz.enigma.Constants;
42import cuchaz.enigma.bytecode.ClassRenamer;
43import cuchaz.enigma.mapping.ArgumentEntry;
44import cuchaz.enigma.mapping.BehaviorEntry;
45import cuchaz.enigma.mapping.BehaviorEntryFactory;
46import cuchaz.enigma.mapping.ClassEntry;
47import cuchaz.enigma.mapping.ConstructorEntry;
48import cuchaz.enigma.mapping.Entry;
49import cuchaz.enigma.mapping.FieldEntry;
50import cuchaz.enigma.mapping.JavassistUtil;
51import cuchaz.enigma.mapping.MethodEntry;
52import cuchaz.enigma.mapping.Translator;
53
54public class JarIndex {
55
56 private Set<ClassEntry> m_obfClassEntries;
57 private TranslationIndex m_translationIndex;
58 private Multimap<String,String> m_interfaces;
59 private Map<Entry,Access> m_access;
60 private Map<FieldEntry,ClassEntry> m_fieldClasses; // TODO: this will become obsolete!
61 private Multimap<String,MethodEntry> m_methodImplementations;
62 private Multimap<BehaviorEntry,EntryReference<BehaviorEntry,BehaviorEntry>> m_behaviorReferences;
63 private Multimap<FieldEntry,EntryReference<FieldEntry,BehaviorEntry>> m_fieldReferences;
64 private Multimap<String,String> m_innerClasses;
65 private Map<String,String> m_outerClasses;
66 private Map<String,BehaviorEntry> m_anonymousClasses;
67
68 public JarIndex() {
69 m_obfClassEntries = Sets.newHashSet();
70 m_translationIndex = new TranslationIndex();
71 m_interfaces = HashMultimap.create();
72 m_access = Maps.newHashMap();
73 m_fieldClasses = Maps.newHashMap();
74 m_methodImplementations = HashMultimap.create();
75 m_behaviorReferences = HashMultimap.create();
76 m_fieldReferences = HashMultimap.create();
77 m_innerClasses = HashMultimap.create();
78 m_outerClasses = Maps.newHashMap();
79 m_anonymousClasses = Maps.newHashMap();
80 }
81
82 public void indexJar(JarFile jar, boolean buildInnerClasses) {
83
84 // step 1: read the class names
85 for (ClassEntry classEntry : JarClassIterator.getClassEntries(jar)) {
86 if (classEntry.isInDefaultPackage()) {
87 // move out of default package
88 classEntry = new ClassEntry(Constants.NonePackage + "/" + classEntry.getName());
89 }
90 m_obfClassEntries.add(classEntry);
91 }
92
93 // step 2: index field/method/constructor access
94 for (CtClass c : JarClassIterator.classes(jar)) {
95 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
96 for (CtField field : c.getDeclaredFields()) {
97 m_access.put(JavassistUtil.getFieldEntry(field), Access.get(field));
98 }
99 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
100 m_access.put(JavassistUtil.getBehaviorEntry(behavior), Access.get(behavior));
101 }
102 }
103
104 // step 3: index extends, implements, fields, and methods
105 for (CtClass c : JarClassIterator.classes(jar)) {
106 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
107 m_translationIndex.indexClass(c);
108 String className = Descriptor.toJvmName(c.getName());
109 for (String interfaceName : c.getClassFile().getInterfaces()) {
110 className = Descriptor.toJvmName(className);
111 interfaceName = Descriptor.toJvmName(interfaceName);
112 if (className.equals(interfaceName)) {
113 throw new IllegalArgumentException("Class cannot be its own interface! " + className);
114 }
115 m_interfaces.put(className, interfaceName);
116 }
117 for (CtField field : c.getDeclaredFields()) {
118 indexField(field);
119 }
120 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
121 indexBehavior(behavior);
122 }
123 }
124
125 // step 4: index field, method, constructor references
126 for (CtClass c : JarClassIterator.classes(jar)) {
127 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
128 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
129 indexBehaviorReferences(behavior);
130 }
131 }
132
133 if (buildInnerClasses) {
134 // step 5: index inner classes and anonymous classes
135 for (CtClass c : JarClassIterator.classes(jar)) {
136 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
137 String outerClassName = findOuterClass(c);
138 if (outerClassName != null) {
139 String innerClassName = c.getSimpleName();
140 m_innerClasses.put(outerClassName, innerClassName);
141 boolean innerWasAdded = m_outerClasses.put(innerClassName, outerClassName) == null;
142 assert (innerWasAdded);
143
144 BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassName);
145 if (enclosingBehavior != null) {
146 m_anonymousClasses.put(innerClassName, enclosingBehavior);
147
148 // DEBUG
149 // System.out.println( "ANONYMOUS: " + outerClassName + "$" + innerClassName );
150 } else {
151 // DEBUG
152 // System.out.println( "INNER: " + outerClassName + "$" + innerClassName );
153 }
154 }
155 }
156
157 // step 6: update other indices with inner class info
158 Map<String,String> renames = Maps.newHashMap();
159 for (Map.Entry<String,String> entry : m_outerClasses.entrySet()) {
160 renames.put(Constants.NonePackage + "/" + entry.getKey(), entry.getValue() + "$" + entry.getKey());
161 }
162 EntryRenamer.renameClassesInSet(renames, m_obfClassEntries);
163 m_translationIndex.renameClasses(renames);
164 EntryRenamer.renameClassesInMultimap(renames, m_interfaces);
165 EntryRenamer.renameClassesInMultimap(renames, m_methodImplementations);
166 EntryRenamer.renameClassesInMultimap(renames, m_behaviorReferences);
167 EntryRenamer.renameClassesInMultimap(renames, m_fieldReferences);
168 EntryRenamer.renameClassesInMap(renames, m_access);
169 }
170 }
171
172 private void indexField(CtField field) {
173 // get the field entry
174 String className = Descriptor.toJvmName(field.getDeclaringClass().getName());
175 FieldEntry fieldEntry = new FieldEntry(new ClassEntry(className), field.getName());
176
177 // is the field a class type?
178 if (field.getSignature().startsWith("L")) {
179 ClassEntry fieldTypeEntry = new ClassEntry(field.getSignature().substring(1, field.getSignature().length() - 1));
180 m_fieldClasses.put(fieldEntry, fieldTypeEntry);
181 }
182 }
183
184 private void indexBehavior(CtBehavior behavior) {
185 // get the behavior entry
186 final BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(behavior);
187 if (behaviorEntry instanceof MethodEntry) {
188 MethodEntry methodEntry = (MethodEntry)behaviorEntry;
189
190 // index implementation
191 m_methodImplementations.put(behaviorEntry.getClassName(), methodEntry);
192 }
193 // looks like we don't care about constructors here
194 }
195
196 private void indexBehaviorReferences(CtBehavior behavior) {
197 // index method calls
198 final BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(behavior);
199 try {
200 behavior.instrument(new ExprEditor() {
201 @Override
202 public void edit(MethodCall call) {
203 MethodEntry calledMethodEntry = JavassistUtil.getMethodEntry(call);
204 ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledMethodEntry);
205 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) {
206 calledMethodEntry = new MethodEntry(
207 resolvedClassEntry,
208 calledMethodEntry.getName(),
209 calledMethodEntry.getSignature()
210 );
211 }
212 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
213 calledMethodEntry,
214 call.getMethodName(),
215 behaviorEntry
216 );
217 m_behaviorReferences.put(calledMethodEntry, reference);
218 }
219
220 @Override
221 public void edit(FieldAccess call) {
222 FieldEntry calledFieldEntry = JavassistUtil.getFieldEntry(call);
223 ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledFieldEntry);
224 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) {
225 calledFieldEntry = new FieldEntry(resolvedClassEntry, call.getFieldName());
226 }
227 EntryReference<FieldEntry,BehaviorEntry> reference = new EntryReference<FieldEntry,BehaviorEntry>(
228 calledFieldEntry,
229 call.getFieldName(),
230 behaviorEntry
231 );
232 m_fieldReferences.put(calledFieldEntry, reference);
233 }
234
235 @Override
236 public void edit(ConstructorCall call) {
237 ConstructorEntry calledConstructorEntry = JavassistUtil.getConstructorEntry(call);
238 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
239 calledConstructorEntry,
240 call.getMethodName(),
241 behaviorEntry
242 );
243 m_behaviorReferences.put(calledConstructorEntry, reference);
244 }
245
246 @Override
247 public void edit(NewExpr call) {
248 ConstructorEntry calledConstructorEntry = JavassistUtil.getConstructorEntry(call);
249 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
250 calledConstructorEntry,
251 call.getClassName(),
252 behaviorEntry
253 );
254 m_behaviorReferences.put(calledConstructorEntry, reference);
255 }
256 });
257 } catch (CannotCompileException ex) {
258 throw new Error(ex);
259 }
260 }
261
262 private String findOuterClass(CtClass c) {
263
264 // inner classes:
265 // have constructors that can (illegally) set synthetic fields
266 // the outer class is the only class that calls constructors
267
268 // use the synthetic fields to find the synthetic constructors
269 for (CtConstructor constructor : c.getDeclaredConstructors()) {
270 Set<String> syntheticFieldTypes = Sets.newHashSet();
271 if (!isIllegalConstructor(syntheticFieldTypes, constructor)) {
272 continue;
273 }
274
275 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
276 ConstructorEntry constructorEntry = JavassistUtil.getConstructorEntry(constructor);
277
278 // gather the classes from the illegally-set synthetic fields
279 Set<ClassEntry> illegallySetClasses = Sets.newHashSet();
280 for (String type : syntheticFieldTypes) {
281 if (type.startsWith("L")) {
282 ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1));
283 if (isSaneOuterClass(outerClassEntry, classEntry)) {
284 illegallySetClasses.add(outerClassEntry);
285 }
286 }
287 }
288
289 // who calls this constructor?
290 Set<ClassEntry> callerClasses = Sets.newHashSet();
291 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : getBehaviorReferences(constructorEntry)) {
292
293 // make sure it's not a call to super
294 if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) {
295
296 // is the entry a superclass of the context?
297 ClassEntry calledClassEntry = reference.entry.getClassEntry();
298 ClassEntry superclassEntry = m_translationIndex.getSuperclass(reference.context.getClassEntry());
299 if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) {
300 // it's a super call, skip
301 continue;
302 }
303 }
304
305 if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) {
306 callerClasses.add(reference.context.getClassEntry());
307 }
308 }
309
310 // do we have an answer yet?
311 if (callerClasses.isEmpty()) {
312 if (illegallySetClasses.size() == 1) {
313 return illegallySetClasses.iterator().next().getName();
314 } else {
315 System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry));
316 }
317 } else {
318 if (callerClasses.size() == 1) {
319 return callerClasses.iterator().next().getName();
320 } else {
321 // multiple callers, do the illegally set classes narrow it down?
322 Set<ClassEntry> intersection = Sets.newHashSet(callerClasses);
323 intersection.retainAll(illegallySetClasses);
324 if (intersection.size() == 1) {
325 return intersection.iterator().next().getName();
326 } else {
327 System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses));
328 }
329 }
330 }
331 }
332
333 return null;
334 }
335
336 private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) {
337
338 // clearly this would be silly
339 if (outerClassEntry.equals(innerClassEntry)) {
340 return false;
341 }
342
343 // is the outer class in the jar?
344 if (!m_obfClassEntries.contains(outerClassEntry)) {
345 return false;
346 }
347
348 return true;
349 }
350
351 @SuppressWarnings("unchecked")
352 private boolean isIllegalConstructor(Set<String> syntheticFieldTypes, CtConstructor constructor) {
353
354 // illegal constructors only set synthetic member fields, then call super()
355 String className = constructor.getDeclaringClass().getName();
356
357 // collect all the field accesses, constructor calls, and method calls
358 final List<FieldAccess> illegalFieldWrites = Lists.newArrayList();
359 final List<ConstructorCall> constructorCalls = Lists.newArrayList();
360 try {
361 constructor.instrument(new ExprEditor() {
362 @Override
363 public void edit(FieldAccess fieldAccess) {
364 if (fieldAccess.isWriter() && constructorCalls.isEmpty()) {
365 illegalFieldWrites.add(fieldAccess);
366 }
367 }
368
369 @Override
370 public void edit(ConstructorCall constructorCall) {
371 constructorCalls.add(constructorCall);
372 }
373 });
374 } catch (CannotCompileException ex) {
375 // we're not compiling anything... this is stupid
376 throw new Error(ex);
377 }
378
379 // are there any illegal field writes?
380 if (illegalFieldWrites.isEmpty()) {
381 return false;
382 }
383
384 // are all the writes to synthetic fields?
385 for (FieldAccess fieldWrite : illegalFieldWrites) {
386
387 // all illegal writes have to be to the local class
388 if (!fieldWrite.getClassName().equals(className)) {
389 System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName()));
390 return false;
391 }
392
393 // find the field
394 FieldInfo fieldInfo = null;
395 for (FieldInfo info : (List<FieldInfo>)constructor.getDeclaringClass().getClassFile().getFields()) {
396 if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) {
397 fieldInfo = info;
398 break;
399 }
400 }
401 if (fieldInfo == null) {
402 // field is in a superclass or something, can't be a local synthetic member
403 return false;
404 }
405
406 // is this field synthetic?
407 boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0;
408 if (isSynthetic) {
409 syntheticFieldTypes.add(fieldInfo.getDescriptor());
410 } else {
411 System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName()));
412 return false;
413 }
414 }
415
416 // we passed all the tests!
417 return true;
418 }
419
420 private BehaviorEntry isAnonymousClass(CtClass c, String outerClassName) {
421
422 ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
423
424 // anonymous classes:
425 // can't be abstract
426 // have only one constructor
427 // it's called exactly once by the outer class
428 // the type the instance is assigned to can't be this type
429
430 // is abstract?
431 if (Modifier.isAbstract(c.getModifiers())) {
432 return null;
433 }
434
435 // is there exactly one constructor?
436 if (c.getDeclaredConstructors().length != 1) {
437 return null;
438 }
439 CtConstructor constructor = c.getDeclaredConstructors()[0];
440
441 // is this constructor called exactly once?
442 ConstructorEntry constructorEntry = JavassistUtil.getConstructorEntry(constructor);
443 Collection<EntryReference<BehaviorEntry,BehaviorEntry>> references = getBehaviorReferences(constructorEntry);
444 if (references.size() != 1) {
445 return null;
446 }
447
448 // does the caller use this type?
449 BehaviorEntry caller = references.iterator().next().context;
450 for (FieldEntry fieldEntry : getReferencedFields(caller)) {
451 ClassEntry fieldClass = getFieldClass(fieldEntry);
452 if (fieldClass != null && fieldClass.equals(innerClassEntry)) {
453 // caller references this type, so it can't be anonymous
454 return null;
455 }
456 }
457 for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) {
458 if (behaviorEntry.getSignature().hasClass(innerClassEntry)) {
459 return null;
460 }
461 }
462
463 return caller;
464 }
465
466 public Set<ClassEntry> getObfClassEntries() {
467 return m_obfClassEntries;
468 }
469
470 public TranslationIndex getTranslationIndex() {
471 return m_translationIndex;
472 }
473
474 public Access getAccess(Entry entry) {
475 return m_access.get(entry);
476 }
477
478 public ClassEntry getFieldClass(FieldEntry fieldEntry) {
479 return m_fieldClasses.get(fieldEntry);
480 }
481
482 public ClassInheritanceTreeNode getClassInheritance(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
483
484 // get the root node
485 List<String> ancestry = Lists.newArrayList();
486 ancestry.add(obfClassEntry.getName());
487 for (ClassEntry classEntry : m_translationIndex.getAncestry(obfClassEntry)) {
488 ancestry.add(classEntry.getName());
489 }
490 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(
491 deobfuscatingTranslator,
492 ancestry.get(ancestry.size() - 1)
493 );
494
495 // expand all children recursively
496 rootNode.load(m_translationIndex, true);
497
498 return rootNode;
499 }
500
501 public ClassImplementationsTreeNode getClassImplementations(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
502
503 // is this even an interface?
504 if (isInterface(obfClassEntry.getClassName())) {
505 ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(deobfuscatingTranslator, obfClassEntry);
506 node.load(this);
507 return node;
508 }
509 return null;
510 }
511
512 public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
513
514 // travel to the ancestor implementation
515 ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry();
516 for (ClassEntry ancestorClassEntry : m_translationIndex.getAncestry(obfMethodEntry.getClassEntry())) {
517 MethodEntry ancestorMethodEntry = new MethodEntry(
518 new ClassEntry(ancestorClassEntry),
519 obfMethodEntry.getName(),
520 obfMethodEntry.getSignature()
521 );
522 if (containsObfBehavior(ancestorMethodEntry)) {
523 baseImplementationClassEntry = ancestorClassEntry;
524 }
525 }
526
527 // make a root node at the base
528 MethodEntry methodEntry = new MethodEntry(
529 baseImplementationClassEntry,
530 obfMethodEntry.getName(),
531 obfMethodEntry.getSignature()
532 );
533 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode(
534 deobfuscatingTranslator,
535 methodEntry,
536 containsObfBehavior(methodEntry)
537 );
538
539 // expand the full tree
540 rootNode.load(this, true);
541
542 return rootNode;
543 }
544
545 public MethodImplementationsTreeNode getMethodImplementations(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
546
547 MethodEntry interfaceMethodEntry;
548
549 // is this method on an interface?
550 if (isInterface(obfMethodEntry.getClassName())) {
551 interfaceMethodEntry = obfMethodEntry;
552 } else {
553 // get the interface class
554 List<MethodEntry> methodInterfaces = Lists.newArrayList();
555 for (String interfaceName : getInterfaces(obfMethodEntry.getClassName())) {
556 // is this method defined in this interface?
557 MethodEntry methodInterface = new MethodEntry(
558 new ClassEntry(interfaceName),
559 obfMethodEntry.getName(),
560 obfMethodEntry.getSignature()
561 );
562 if (containsObfBehavior(methodInterface)) {
563 methodInterfaces.add(methodInterface);
564 }
565 }
566 if (methodInterfaces.isEmpty()) {
567 return null;
568 }
569 if (methodInterfaces.size() > 1) {
570 throw new Error("Too many interfaces define this method! This is not yet supported by Enigma!");
571 }
572 interfaceMethodEntry = methodInterfaces.get(0);
573 }
574
575 MethodImplementationsTreeNode rootNode = new MethodImplementationsTreeNode(deobfuscatingTranslator, interfaceMethodEntry);
576 rootNode.load(this);
577 return rootNode;
578 }
579
580 public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) {
581 Set<MethodEntry> methodEntries = Sets.newHashSet();
582 getRelatedMethodImplementations(methodEntries, getMethodInheritance(null, obfMethodEntry));
583 return methodEntries;
584 }
585
586 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) {
587 MethodEntry methodEntry = node.getMethodEntry();
588 if (containsObfBehavior(methodEntry)) {
589 // collect the entry
590 methodEntries.add(methodEntry);
591 }
592
593 // look at interface methods too
594 MethodImplementationsTreeNode implementations = getMethodImplementations(null, methodEntry);
595 if (implementations != null) {
596 getRelatedMethodImplementations(methodEntries, implementations);
597 }
598
599 // recurse
600 for (int i = 0; i < node.getChildCount(); i++) {
601 getRelatedMethodImplementations(methodEntries, (MethodInheritanceTreeNode)node.getChildAt(i));
602 }
603 }
604
605 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) {
606 MethodEntry methodEntry = node.getMethodEntry();
607 if (containsObfBehavior(methodEntry)) {
608 // collect the entry
609 methodEntries.add(methodEntry);
610 }
611
612 // recurse
613 for (int i = 0; i < node.getChildCount(); i++) {
614 getRelatedMethodImplementations(methodEntries, (MethodImplementationsTreeNode)node.getChildAt(i));
615 }
616 }
617
618 public Collection<EntryReference<FieldEntry,BehaviorEntry>> getFieldReferences(FieldEntry fieldEntry) {
619 return m_fieldReferences.get(fieldEntry);
620 }
621
622 public Collection<FieldEntry> getReferencedFields(BehaviorEntry behaviorEntry) {
623 // linear search is fast enough for now
624 Set<FieldEntry> fieldEntries = Sets.newHashSet();
625 for (EntryReference<FieldEntry,BehaviorEntry> reference : m_fieldReferences.values()) {
626 if (reference.context == behaviorEntry) {
627 fieldEntries.add(reference.entry);
628 }
629 }
630 return fieldEntries;
631 }
632
633 public Collection<EntryReference<BehaviorEntry,BehaviorEntry>> getBehaviorReferences(BehaviorEntry behaviorEntry) {
634 return m_behaviorReferences.get(behaviorEntry);
635 }
636
637 public Collection<BehaviorEntry> getReferencedBehaviors(BehaviorEntry behaviorEntry) {
638 // linear search is fast enough for now
639 Set<BehaviorEntry> behaviorEntries = Sets.newHashSet();
640 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : m_behaviorReferences.values()) {
641 if (reference.context == behaviorEntry) {
642 behaviorEntries.add(reference.entry);
643 }
644 }
645 return behaviorEntries;
646 }
647
648 public Collection<String> getInnerClasses(String obfOuterClassName) {
649 return m_innerClasses.get(obfOuterClassName);
650 }
651
652 public String getOuterClass(String obfInnerClassName) {
653 // make sure we use the right name
654 if (new ClassEntry(obfInnerClassName).getPackageName() != null) {
655 throw new IllegalArgumentException("Don't reference obfuscated inner classes using packages: " + obfInnerClassName);
656 }
657 return m_outerClasses.get(obfInnerClassName);
658 }
659
660 public boolean isAnonymousClass(String obfInnerClassName) {
661 return m_anonymousClasses.containsKey(obfInnerClassName);
662 }
663
664 public BehaviorEntry getAnonymousClassCaller(String obfInnerClassName) {
665 return m_anonymousClasses.get(obfInnerClassName);
666 }
667
668 public Set<String> getInterfaces(String className) {
669 Set<String> interfaceNames = new HashSet<String>();
670 interfaceNames.addAll(m_interfaces.get(className));
671 for (ClassEntry ancestor : m_translationIndex.getAncestry(new ClassEntry(className))) {
672 interfaceNames.addAll(m_interfaces.get(ancestor.getName()));
673 }
674 return interfaceNames;
675 }
676
677 public Set<String> getImplementingClasses(String targetInterfaceName) {
678 // linear search is fast enough for now
679 Set<String> classNames = Sets.newHashSet();
680 for (Map.Entry<String,String> entry : m_interfaces.entries()) {
681 String className = entry.getKey();
682 String interfaceName = entry.getValue();
683 if (interfaceName.equals(targetInterfaceName)) {
684 classNames.add(className);
685 m_translationIndex.getSubclassNamesRecursively(classNames, new ClassEntry(className));
686 }
687 }
688 return classNames;
689 }
690
691 public boolean isInterface(String className) {
692 return m_interfaces.containsValue(className);
693 }
694
695 public boolean containsObfClass(ClassEntry obfClassEntry) {
696 return m_obfClassEntries.contains(obfClassEntry);
697 }
698
699 public boolean containsObfField(FieldEntry obfFieldEntry) {
700 return m_access.containsKey(obfFieldEntry);
701 }
702
703 public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) {
704 return m_access.containsKey(obfBehaviorEntry);
705 }
706
707 public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) {
708 // check the behavior
709 if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) {
710 return false;
711 }
712
713 // check the argument
714 if (obfArgumentEntry.getIndex() >= obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size()) {
715 return false;
716 }
717
718 return true;
719 }
720
721 public boolean containsObfEntry(Entry obfEntry) {
722 if (obfEntry instanceof ClassEntry) {
723 return containsObfClass((ClassEntry)obfEntry);
724 } else if (obfEntry instanceof FieldEntry) {
725 return containsObfField((FieldEntry)obfEntry);
726 } else if (obfEntry instanceof BehaviorEntry) {
727 return containsObfBehavior((BehaviorEntry)obfEntry);
728 } else if (obfEntry instanceof ArgumentEntry) {
729 return containsObfArgument((ArgumentEntry)obfEntry);
730 } else {
731 throw new Error("Entry type not supported: " + obfEntry.getClass().getName());
732 }
733 }
734}
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..4155128
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.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.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;
38import cuchaz.enigma.mapping.Signature;
39
40public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
41
42 private BehaviorEntry m_behaviorEntry;
43
44 public SourceIndexBehaviorVisitor(BehaviorEntry behaviorEntry) {
45 m_behaviorEntry = behaviorEntry;
46 }
47
48 @Override
49 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
50 return recurse(node, index);
51 }
52
53 @Override
54 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
55 return recurse(node, index);
56 }
57
58 @Override
59 public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) {
60 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
61
62 // get the behavior entry
63 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
64 BehaviorEntry behaviorEntry = null;
65 if (ref instanceof MethodReference) {
66 MethodReference methodRef = (MethodReference)ref;
67 if (methodRef.isConstructor()) {
68 behaviorEntry = new ConstructorEntry(classEntry, new Signature(ref.getSignature()));
69 } else if (methodRef.isTypeInitializer()) {
70 behaviorEntry = new ConstructorEntry(classEntry);
71 } else {
72 behaviorEntry = new MethodEntry(classEntry, ref.getName(), new Signature(ref.getSignature()));
73 }
74 }
75 if (behaviorEntry != null) {
76 // get the node for the token
77 AstNode tokenNode = null;
78 if (node.getTarget() instanceof MemberReferenceExpression) {
79 tokenNode = ((MemberReferenceExpression)node.getTarget()).getMemberNameToken();
80 } else if (node.getTarget() instanceof SuperReferenceExpression) {
81 tokenNode = node.getTarget();
82 } else if (node.getTarget() instanceof ThisReferenceExpression) {
83 tokenNode = node.getTarget();
84 }
85 if (tokenNode != null) {
86 index.addReference(tokenNode, behaviorEntry, m_behaviorEntry);
87 }
88 }
89
90 return recurse(node, index);
91 }
92
93 @Override
94 public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) {
95 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
96 if (ref != null) {
97 // make sure this is actually a field
98 if (ref.getSignature().indexOf('(') >= 0) {
99 throw new Error("Expected a field here! got " + ref);
100 }
101
102 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
103 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName());
104 index.addReference(node.getMemberNameToken(), fieldEntry, m_behaviorEntry);
105 }
106
107 return recurse(node, index);
108 }
109
110 @Override
111 public Void visitSimpleType(SimpleType node, SourceIndex index) {
112 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
113 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
114 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
115 index.addReference(node.getIdentifierToken(), classEntry, m_behaviorEntry);
116 }
117
118 return recurse(node, index);
119 }
120
121 @Override
122 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
123 ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION);
124 ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName());
125 MethodDefinition methodDef = (MethodDefinition)def.getMethod();
126 BehaviorEntry behaviorEntry;
127 if (methodDef.isConstructor()) {
128 behaviorEntry = new ConstructorEntry(classEntry, new Signature(methodDef.getSignature()));
129 } else {
130 behaviorEntry = new MethodEntry(classEntry, methodDef.getName(), new Signature(methodDef.getSignature()));
131 }
132 ArgumentEntry argumentEntry = new ArgumentEntry(behaviorEntry, def.getPosition(), node.getName());
133 index.addDeclaration(node.getNameToken(), argumentEntry);
134
135 return recurse(node, index);
136 }
137
138 @Override
139 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
140 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
141 if (ref != null) {
142 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
143 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName());
144 index.addReference(node.getIdentifierToken(), fieldEntry, m_behaviorEntry);
145 }
146
147 return recurse(node, index);
148 }
149
150 @Override
151 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
152 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
153 if (ref != null) {
154 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
155 ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(ref.getSignature()));
156 if (node.getType() instanceof SimpleType) {
157 SimpleType simpleTypeNode = (SimpleType)node.getType();
158 index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, m_behaviorEntry);
159 }
160 }
161
162 return recurse(node, index);
163 }
164}
diff --git a/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
new file mode 100644
index 0000000..7222035
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
@@ -0,0 +1,115 @@
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;
33import cuchaz.enigma.mapping.Signature;
34
35public class SourceIndexClassVisitor extends SourceIndexVisitor {
36
37 private ClassEntry m_classEntry;
38
39 public SourceIndexClassVisitor(ClassEntry classEntry) {
40 m_classEntry = classEntry;
41 }
42
43 @Override
44 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
45 // is this this class, or a subtype?
46 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
47 ClassEntry classEntry = new ClassEntry(def.getInternalName());
48 if (!classEntry.equals(m_classEntry)) {
49 // it's a sub-type, recurse
50 index.addDeclaration(node.getNameToken(), classEntry);
51 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
52 }
53
54 return recurse(node, index);
55 }
56
57 @Override
58 public Void visitSimpleType(SimpleType node, SourceIndex index) {
59 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
60 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
61 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
62 index.addReference(node.getIdentifierToken(), classEntry, m_classEntry);
63 }
64
65 return recurse(node, index);
66 }
67
68 @Override
69 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
70 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
71 ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName());
72 BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(classEntry, def.getName(), def.getSignature());
73 AstNode tokenNode = node.getNameToken();
74 if (behaviorEntry instanceof ConstructorEntry) {
75 ConstructorEntry constructorEntry = (ConstructorEntry)behaviorEntry;
76 if (constructorEntry.isStatic()) {
77 tokenNode = node.getModifiers().firstOrNullObject();
78 }
79 }
80 index.addDeclaration(tokenNode, behaviorEntry);
81 return node.acceptVisitor(new SourceIndexBehaviorVisitor(behaviorEntry), index);
82 }
83
84 @Override
85 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
86 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
87 ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName());
88 ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(def.getSignature()));
89 index.addDeclaration(node.getNameToken(), constructorEntry);
90 return node.acceptVisitor(new SourceIndexBehaviorVisitor(constructorEntry), index);
91 }
92
93 @Override
94 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
95 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
96 ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName());
97 FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName());
98 assert (node.getVariables().size() == 1);
99 VariableInitializer variable = node.getVariables().firstOrNullObject();
100 index.addDeclaration(variable.getNameToken(), fieldEntry);
101
102 return recurse(node, index);
103 }
104
105 @Override
106 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
107 // treat enum declarations as field declarations
108 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
109 ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName());
110 FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName());
111 index.addDeclaration(node.getNameToken(), fieldEntry);
112
113 return recurse(node, index);
114 }
115}
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..5284557
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/CheckCastIterator.java
@@ -0,0 +1,127 @@
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;
24import cuchaz.enigma.mapping.Signature;
25
26public class CheckCastIterator implements Iterator<CheckCast> {
27
28 public static class CheckCast {
29
30 public String className;
31 public MethodEntry prevMethodEntry;
32
33 public CheckCast(String className, MethodEntry prevMethodEntry) {
34 this.className = className;
35 this.prevMethodEntry = prevMethodEntry;
36 }
37 }
38
39 private ConstPool m_constants;
40 private CodeAttribute m_attribute;
41 private CodeIterator m_iter;
42 private CheckCast m_next;
43
44 public CheckCastIterator(CodeAttribute codeAttribute) throws BadBytecode {
45 m_constants = codeAttribute.getConstPool();
46 m_attribute = codeAttribute;
47 m_iter = m_attribute.iterator();
48
49 m_next = getNext();
50 }
51
52 @Override
53 public boolean hasNext() {
54 return m_next != null;
55 }
56
57 @Override
58 public CheckCast next() {
59 CheckCast out = m_next;
60 try {
61 m_next = getNext();
62 } catch (BadBytecode ex) {
63 throw new Error(ex);
64 }
65 return out;
66 }
67
68 @Override
69 public void remove() {
70 throw new UnsupportedOperationException();
71 }
72
73 private CheckCast getNext() throws BadBytecode {
74 int prevPos = 0;
75 while (m_iter.hasNext()) {
76 int pos = m_iter.next();
77 int opcode = m_iter.byteAt(pos);
78 switch (opcode) {
79 case Opcode.CHECKCAST:
80
81 // get the type of this op code (next two bytes are a classinfo index)
82 MethodEntry prevMethodEntry = getMethodEntry(prevPos);
83 if (prevMethodEntry != null) {
84 return new CheckCast(m_constants.getClassInfo(m_iter.s16bitAt(pos + 1)), prevMethodEntry);
85 }
86 break;
87 }
88 prevPos = pos;
89 }
90 return null;
91 }
92
93 private MethodEntry getMethodEntry(int pos) {
94 switch (m_iter.byteAt(pos)) {
95 case Opcode.INVOKEVIRTUAL:
96 case Opcode.INVOKESTATIC:
97 case Opcode.INVOKEDYNAMIC:
98 case Opcode.INVOKESPECIAL: {
99 int index = m_iter.s16bitAt(pos + 1);
100 return new MethodEntry(
101 new ClassEntry(Descriptor.toJvmName(m_constants.getMethodrefClassName(index))),
102 m_constants.getMethodrefName(index),
103 new Signature(m_constants.getMethodrefType(index))
104 );
105 }
106
107 case Opcode.INVOKEINTERFACE: {
108 int index = m_iter.s16bitAt(pos + 1);
109 return new MethodEntry(
110 new ClassEntry(Descriptor.toJvmName(m_constants.getInterfaceMethodrefClassName(index))),
111 m_constants.getInterfaceMethodrefName(index),
112 new Signature(m_constants.getInterfaceMethodrefType(index))
113 );
114 }
115 }
116 return null;
117 }
118
119 public Iterable<CheckCast> casts() {
120 return new Iterable<CheckCast>() {
121 @Override
122 public Iterator<CheckCast> iterator() {
123 return CheckCastIterator.this;
124 }
125 };
126 }
127}
diff --git a/src/cuchaz/enigma/bytecode/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..afd3a77
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/ClassTranslator.java
@@ -0,0 +1,144 @@
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.JavassistUtil;
30import cuchaz.enigma.mapping.MethodEntry;
31import cuchaz.enigma.mapping.Signature;
32import cuchaz.enigma.mapping.Translator;
33import cuchaz.enigma.mapping.Type;
34
35public class ClassTranslator {
36
37 private Translator m_translator;
38
39 public ClassTranslator(Translator translator) {
40 m_translator = translator;
41 }
42
43 public void translate(CtClass c) {
44
45 // NOTE: the order of these translations is very important
46
47 // translate all the field and method references in the code by editing the constant pool
48 ConstPool constants = c.getClassFile().getConstPool();
49 ConstPoolEditor editor = new ConstPoolEditor(constants);
50 for (int i = 1; i < constants.getSize(); i++) {
51 switch (constants.getTag(i)) {
52
53 case ConstPool.CONST_Fieldref: {
54
55 // translate the name
56 FieldEntry entry = new FieldEntry(
57 new ClassEntry(Descriptor.toJvmName(constants.getFieldrefClassName(i))),
58 constants.getFieldrefName(i)
59 );
60 FieldEntry translatedEntry = m_translator.translateEntry(entry);
61
62 // translate the type
63 Type type = new Type(constants.getFieldrefType(i));
64 Type translatedType = m_translator.translateType(type);
65
66 if (!entry.equals(translatedEntry) || !type.equals(translatedType)) {
67 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedType.toString());
68 }
69 }
70 break;
71
72 case ConstPool.CONST_Methodref:
73 case ConstPool.CONST_InterfaceMethodref: {
74
75 // translate the name and type
76 BehaviorEntry entry = BehaviorEntryFactory.create(
77 Descriptor.toJvmName(editor.getMemberrefClassname(i)),
78 editor.getMemberrefName(i),
79 editor.getMemberrefType(i)
80 );
81 BehaviorEntry translatedEntry = m_translator.translateEntry(entry);
82
83 if (!entry.getName().equals(translatedEntry.getName()) || !entry.getSignature().equals(translatedEntry.getSignature())) {
84 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString());
85 }
86 }
87 break;
88 }
89 }
90
91 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
92
93 // translate all the fields
94 for (CtField field : c.getDeclaredFields()) {
95
96 // translate the name
97 FieldEntry entry = new FieldEntry(classEntry, field.getName());
98 String translatedName = m_translator.translate(entry);
99 if (translatedName != null) {
100 field.setName(translatedName);
101 }
102
103 // translate the type
104 Type translatedType = m_translator.translateType(new Type(field.getFieldInfo().getDescriptor()));
105 field.getFieldInfo().setDescriptor(translatedType.toString());
106 }
107
108 // translate all the methods and constructors
109 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
110 if (behavior instanceof CtMethod) {
111 CtMethod method = (CtMethod)behavior;
112
113 // translate the name
114 MethodEntry entry = JavassistUtil.getMethodEntry(method);
115 String translatedName = m_translator.translate(entry);
116 if (translatedName != null) {
117 method.setName(translatedName);
118 }
119 }
120
121 // translate the type
122 Signature translatedSignature = m_translator.translateSignature(new Signature(behavior.getMethodInfo().getDescriptor()));
123 behavior.getMethodInfo().setDescriptor(translatedSignature.toString());
124 }
125
126 // translate all the class names referenced in the code
127 // the above code only changed method/field/reference names and types, but not the class names themselves
128 Map<ClassEntry,ClassEntry> map = Maps.newHashMap();
129 for (ClassEntry obfClassEntry : ClassRenamer.getAllClassEntries(c)) {
130 ClassEntry deobfClassEntry = m_translator.translateEntry(obfClassEntry);
131 if (!obfClassEntry.equals(deobfClassEntry)) {
132 map.put(obfClassEntry, deobfClassEntry);
133 }
134 }
135 ClassRenamer.renameClasses(c, map);
136
137 // translate the source file attribute too
138 ClassEntry deobfClassEntry = map.get(classEntry);
139 if (deobfClassEntry != null) {
140 String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOuterClassName()) + ".java";
141 c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile));
142 }
143 }
144}
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..817500b
--- /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().toString()));
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..5d4ca1a
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/MethodParameterWriter.java
@@ -0,0 +1,53 @@
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 cuchaz.enigma.mapping.ArgumentEntry;
19import cuchaz.enigma.mapping.BehaviorEntry;
20import cuchaz.enigma.mapping.BehaviorEntryFactory;
21import cuchaz.enigma.mapping.Translator;
22
23public class MethodParameterWriter {
24
25 private Translator m_translator;
26
27 public MethodParameterWriter(Translator translator) {
28 m_translator = translator;
29 }
30
31 public void writeMethodArguments(CtClass c) {
32
33 // Procyon will read method arguments from the "MethodParameters" attribute, so write those
34 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
35 BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(behavior);
36
37 // get the number of arguments
38 int numParams = behaviorEntry.getSignature().getArgumentTypes().size();
39 if (numParams <= 0) {
40 continue;
41 }
42
43 // get the list of argument names
44 List<String> names = new ArrayList<String>(numParams);
45 for (int i = 0; i < numParams; i++) {
46 names.add(m_translator.translate(new ArgumentEntry(behaviorEntry, i, "")));
47 }
48
49 // save the mappings to the class
50 MethodParametersAttribute.updateClass(behavior.getMethodInfo(), names);
51 }
52 }
53}
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..1be6123
--- /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.ClassNameReplacer;
54import cuchaz.enigma.mapping.Entry;
55import cuchaz.enigma.mapping.FieldEntry;
56import cuchaz.enigma.mapping.JavassistUtil;
57import cuchaz.enigma.mapping.Signature;
58
59public class ClassIdentity {
60
61 private ClassEntry m_classEntry;
62 private SidedClassNamer m_namer;
63 private Multiset<String> m_fields;
64 private Multiset<String> m_methods;
65 private Multiset<String> m_constructors;
66 private String m_staticInitializer;
67 private String m_extends;
68 private Multiset<String> m_implements;
69 private Multiset<String> m_implementations;
70 private Multiset<String> m_references;
71
72 public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) {
73 m_namer = namer;
74
75 // stuff from the bytecode
76
77 m_classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
78 m_fields = HashMultiset.create();
79 for (CtField field : c.getDeclaredFields()) {
80 m_fields.add(scrubSignature(field.getSignature()));
81 }
82 m_methods = HashMultiset.create();
83 for (CtMethod method : c.getDeclaredMethods()) {
84 m_methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method));
85 }
86 m_constructors = HashMultiset.create();
87 for (CtConstructor constructor : c.getDeclaredConstructors()) {
88 m_constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor));
89 }
90 m_staticInitializer = "";
91 if (c.getClassInitializer() != null) {
92 m_staticInitializer = getBehaviorSignature(c.getClassInitializer());
93 }
94 m_extends = "";
95 if (c.getClassFile().getSuperclass() != null) {
96 m_extends = scrubClassName(c.getClassFile().getSuperclass());
97 }
98 m_implements = HashMultiset.create();
99 for (String interfaceName : c.getClassFile().getInterfaces()) {
100 m_implements.add(scrubClassName(interfaceName));
101 }
102
103 // stuff from the jar index
104
105 m_implementations = HashMultiset.create();
106 ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, m_classEntry);
107 if (implementationsNode != null) {
108 @SuppressWarnings("unchecked")
109 Enumeration<ClassImplementationsTreeNode> implementations = implementationsNode.children();
110 while (implementations.hasMoreElements()) {
111 ClassImplementationsTreeNode node = implementations.nextElement();
112 m_implementations.add(scrubClassName(node.getClassEntry().getName()));
113 }
114 }
115
116 m_references = HashMultiset.create();
117 if (useReferences) {
118 for (CtField field : c.getDeclaredFields()) {
119 FieldEntry fieldEntry = new FieldEntry(m_classEntry, field.getName());
120 for (EntryReference<FieldEntry,BehaviorEntry> reference : index.getFieldReferences(fieldEntry)) {
121 addReference(reference);
122 }
123 }
124 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
125 BehaviorEntry behaviorEntry = JavassistUtil.getBehaviorEntry(behavior);
126 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(behaviorEntry)) {
127 addReference(reference);
128 }
129 }
130 }
131 }
132
133 private void addReference(EntryReference<? extends Entry,BehaviorEntry> reference) {
134 if (reference.context.getSignature() != null) {
135 m_references.add(String.format("%s_%s", scrubClassName(reference.context.getClassName()), scrubSignature(reference.context.getSignature())));
136 } else {
137 m_references.add(String.format("%s_<clinit>", scrubClassName(reference.context.getClassName())));
138 }
139 }
140
141 public ClassEntry getClassEntry() {
142 return m_classEntry;
143 }
144
145 @Override
146 public String toString() {
147 StringBuilder buf = new StringBuilder();
148 buf.append("class: ");
149 buf.append(m_classEntry.getName());
150 buf.append(" ");
151 buf.append(hashCode());
152 buf.append("\n");
153 for (String field : m_fields) {
154 buf.append("\tfield ");
155 buf.append(field);
156 buf.append("\n");
157 }
158 for (String method : m_methods) {
159 buf.append("\tmethod ");
160 buf.append(method);
161 buf.append("\n");
162 }
163 for (String constructor : m_constructors) {
164 buf.append("\tconstructor ");
165 buf.append(constructor);
166 buf.append("\n");
167 }
168 if (m_staticInitializer.length() > 0) {
169 buf.append("\tinitializer ");
170 buf.append(m_staticInitializer);
171 buf.append("\n");
172 }
173 if (m_extends.length() > 0) {
174 buf.append("\textends ");
175 buf.append(m_extends);
176 buf.append("\n");
177 }
178 for (String interfaceName : m_implements) {
179 buf.append("\timplements ");
180 buf.append(interfaceName);
181 buf.append("\n");
182 }
183 for (String implementation : m_implementations) {
184 buf.append("\timplemented by ");
185 buf.append(implementation);
186 buf.append("\n");
187 }
188 for (String reference : m_references) {
189 buf.append("\treference ");
190 buf.append(reference);
191 buf.append("\n");
192 }
193 return buf.toString();
194 }
195
196 private String scrubClassName(String className) {
197 return scrubSignature("L" + Descriptor.toJvmName(className) + ";");
198 }
199
200 private String scrubSignature(String signature) {
201 return scrubSignature(new Signature(signature));
202 }
203
204 private String scrubSignature(Signature signature) {
205
206 return new Signature(signature, new ClassNameReplacer() {
207
208 private Map<String,String> m_classNames = Maps.newHashMap();
209
210 @Override
211 public String replace(String className) {
212
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 }).toString();
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..ccf6b78
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassMatcher.java
@@ -0,0 +1,406 @@
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.JavassistUtil;
46import cuchaz.enigma.mapping.MappingParseException;
47import cuchaz.enigma.mapping.Mappings;
48import cuchaz.enigma.mapping.MappingsReader;
49import cuchaz.enigma.mapping.MappingsWriter;
50import cuchaz.enigma.mapping.MethodEntry;
51import cuchaz.enigma.mapping.MethodMapping;
52
53public class ClassMatcher {
54
55 public static void main(String[] args) throws IOException, MappingParseException {
56 // TEMP
57 JarFile sourceJar = new JarFile(new File("input/1.8-pre3.jar"));
58 JarFile destJar = new JarFile(new File("input/1.8.jar"));
59 File inMappingsFile = new File("../Enigma Mappings/1.8-pre3.mappings");
60 File outMappingsFile = new File("../Enigma Mappings/1.8.mappings");
61
62 // define a matching to use when the automated system cannot find a match
63 Map<String,String> fallbackMatching = Maps.newHashMap();
64 fallbackMatching.put("none/ayb", "none/ayf");
65 fallbackMatching.put("none/ayd", "none/ayd");
66 fallbackMatching.put("none/bgk", "unknown/bgk");
67
68 // do the conversion
69 Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile));
70 convertMappings(sourceJar, destJar, mappings, fallbackMatching);
71
72 // write out the converted mappings
73 FileWriter writer = new FileWriter(outMappingsFile);
74 new MappingsWriter().write(writer, mappings);
75 writer.close();
76 System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
77 }
78
79 private static void convertMappings(JarFile sourceJar, JarFile destJar, Mappings mappings, Map<String,String> fallbackMatching) {
80 // index jars
81 System.out.println("Indexing source jar...");
82 JarIndex sourceIndex = new JarIndex();
83 sourceIndex.indexJar(sourceJar, false);
84 System.out.println("Indexing dest jar...");
85 JarIndex destIndex = new JarIndex();
86 destIndex.indexJar(destJar, false);
87 TranslatingTypeLoader sourceLoader = new TranslatingTypeLoader(sourceJar, sourceIndex);
88 TranslatingTypeLoader destLoader = new TranslatingTypeLoader(destJar, destIndex);
89
90 // compute the matching
91 ClassMatching matching = computeMatching(sourceIndex, sourceLoader, destIndex, destLoader);
92 Map<String,Map.Entry<ClassIdentity,List<ClassIdentity>>> matchingIndex = matching.getIndex();
93
94 // get all the obf class names used in the mappings
95 Set<String> usedClassNames = mappings.getAllObfClassNames();
96 Set<String> allClassNames = Sets.newHashSet();
97 for (ClassEntry classEntry : sourceIndex.getObfClassEntries()) {
98 allClassNames.add(classEntry.getName());
99 }
100 usedClassNames.retainAll(allClassNames);
101 System.out.println("Used " + usedClassNames.size() + " classes in the mappings");
102
103 // probabilistically match the non-uniquely-matched source classes
104 for (Map.Entry<ClassIdentity,List<ClassIdentity>> entry : matchingIndex.values()) {
105 ClassIdentity sourceClass = entry.getKey();
106 List<ClassIdentity> destClasses = entry.getValue();
107
108 // skip classes that are uniquely matched
109 if (destClasses.size() == 1) {
110 continue;
111 }
112
113 // skip classes that aren't used in the mappings
114 if (!usedClassNames.contains(sourceClass.getClassEntry().getName())) {
115 continue;
116 }
117
118 System.out.println("No exact match for source class " + sourceClass.getClassEntry());
119
120 // find the closest classes
121 Multimap<Integer,ClassIdentity> scoredMatches = ArrayListMultimap.create();
122 for (ClassIdentity c : destClasses) {
123 scoredMatches.put(sourceClass.getMatchScore(c), c);
124 }
125 List<Integer> scores = new ArrayList<Integer>(scoredMatches.keySet());
126 Collections.sort(scores, Collections.reverseOrder());
127 printScoredMatches(sourceClass.getMaxMatchScore(), scores, scoredMatches);
128
129 // does the best match have a non-zero score and the same name?
130 int bestScore = scores.get(0);
131 Collection<ClassIdentity> bestMatches = scoredMatches.get(bestScore);
132 if (bestScore > 0 && bestMatches.size() == 1) {
133 ClassIdentity bestMatch = bestMatches.iterator().next();
134 if (bestMatch.getClassEntry().equals(sourceClass.getClassEntry())) {
135 // use it
136 System.out.println("\tAutomatically choosing likely match: " + bestMatch.getClassEntry().getName());
137 destClasses.clear();
138 destClasses.add(bestMatch);
139 }
140 }
141 }
142
143 // group the matching into unique and non-unique matches
144 BiMap<String,String> matchedClassNames = HashBiMap.create();
145 Set<String> unmatchedSourceClassNames = Sets.newHashSet();
146 for (String className : usedClassNames) {
147 // is there a match for this class?
148 Map.Entry<ClassIdentity,List<ClassIdentity>> entry = matchingIndex.get(className);
149 ClassIdentity sourceClass = entry.getKey();
150 List<ClassIdentity> matches = entry.getValue();
151
152 if (matches.size() == 1) {
153 // unique match! We're good to go!
154 matchedClassNames.put(sourceClass.getClassEntry().getName(), matches.get(0).getClassEntry().getName());
155 } else {
156 // no match, check the fallback matching
157 String fallbackMatch = fallbackMatching.get(className);
158 if (fallbackMatch != null) {
159 matchedClassNames.put(sourceClass.getClassEntry().getName(), fallbackMatch);
160 } else {
161 unmatchedSourceClassNames.add(className);
162 }
163 }
164 }
165
166 // report unmatched classes
167 if (!unmatchedSourceClassNames.isEmpty()) {
168 System.err.println("ERROR: there were unmatched classes!");
169 for (String className : unmatchedSourceClassNames) {
170 System.err.println("\t" + className);
171 }
172 return;
173 }
174
175 // get the class name changes from the matched class names
176 Map<String,String> classChanges = Maps.newHashMap();
177 for (Map.Entry<String,String> entry : matchedClassNames.entrySet()) {
178 if (!entry.getKey().equals(entry.getValue())) {
179 classChanges.put(entry.getKey(), entry.getValue());
180 System.out.println(String.format("Class change: %s -> %s", entry.getKey(), entry.getValue()));
181 /* DEBUG
182 System.out.println(String.format("\n%s\n%s",
183 new ClassIdentity(sourceLoader.loadClass(entry.getKey()), null, sourceIndex, false, false),
184 new ClassIdentity( destLoader.loadClass(entry.getValue()), null, destIndex, false, false)
185 ));
186 */
187 }
188 }
189
190 // sort the changes so classes are renamed in the correct order
191 // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b
192 LinkedHashMap<String,String> orderedClassChanges = Maps.newLinkedHashMap();
193 int numChangesLeft = classChanges.size();
194 while (!classChanges.isEmpty()) {
195 Iterator<Map.Entry<String,String>> iter = classChanges.entrySet().iterator();
196 while (iter.hasNext()) {
197 Map.Entry<String,String> entry = iter.next();
198 if (classChanges.get(entry.getValue()) == null) {
199 orderedClassChanges.put(entry.getKey(), entry.getValue());
200 iter.remove();
201 }
202 }
203
204 // did we remove any changes?
205 if (numChangesLeft - classChanges.size() > 0) {
206 // keep going
207 numChangesLeft = classChanges.size();
208 } else {
209 // can't sort anymore. There must be a loop
210 break;
211 }
212 }
213 if (classChanges.size() > 0) {
214 throw new Error(String.format("Unable to sort %d/%d class changes!", classChanges.size(), matchedClassNames.size()));
215 }
216
217 // convert the mappings in the correct class order
218 for (Map.Entry<String,String> entry : orderedClassChanges.entrySet()) {
219 mappings.renameObfClass(entry.getKey(), entry.getValue());
220 }
221
222 // check the method matches
223 System.out.println("Checking methods...");
224 for (ClassMapping classMapping : mappings.classes()) {
225 ClassEntry classEntry = new ClassEntry(classMapping.getObfName());
226 for (MethodMapping methodMapping : classMapping.methods()) {
227
228 // skip constructors
229 if (methodMapping.getObfName().equals("<init>")) {
230 continue;
231 }
232
233 MethodEntry methodEntry = new MethodEntry(
234 classEntry,
235 methodMapping.getObfName(),
236 methodMapping.getObfSignature()
237 );
238 if (!destIndex.containsObfBehavior(methodEntry)) {
239 System.err.println("WARNING: method doesn't match: " + methodEntry);
240
241 // show the available methods
242 System.err.println("\tAvailable dest methods:");
243 CtClass c = destLoader.loadClass(classMapping.getObfName());
244 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
245 System.err.println("\t\t" + JavassistUtil.getBehaviorEntry(behavior));
246 }
247
248 System.err.println("\tAvailable source methods:");
249 c = sourceLoader.loadClass(matchedClassNames.inverse().get(classMapping.getObfName()));
250 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
251 System.err.println("\t\t" + JavassistUtil.getBehaviorEntry(behavior));
252 }
253 }
254 }
255 }
256
257 System.out.println("Done!");
258 }
259
260 public static ClassMatching computeMatching(JarIndex sourceIndex, TranslatingTypeLoader sourceLoader, JarIndex destIndex, TranslatingTypeLoader destLoader) {
261
262 System.out.println("Matching classes...");
263
264 ClassMatching matching = null;
265 for (boolean useReferences : Arrays.asList(false, true)) {
266 int numMatches = 0;
267 do {
268 SidedClassNamer sourceNamer = null;
269 SidedClassNamer destNamer = null;
270 if (matching != null) {
271 // build a class namer
272 ClassNamer namer = new ClassNamer(matching.getUniqueMatches());
273 sourceNamer = namer.getSourceNamer();
274 destNamer = namer.getDestNamer();
275
276 // note the number of matches
277 numMatches = matching.getUniqueMatches().size();
278 }
279
280 // get the entries left to match
281 Set<ClassEntry> sourceClassEntries = Sets.newHashSet();
282 Set<ClassEntry> destClassEntries = Sets.newHashSet();
283 if (matching == null) {
284 sourceClassEntries.addAll(sourceIndex.getObfClassEntries());
285 destClassEntries.addAll(destIndex.getObfClassEntries());
286 matching = new ClassMatching();
287 } else {
288 for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : matching.getAmbiguousMatches().entrySet()) {
289 for (ClassIdentity c : entry.getKey()) {
290 sourceClassEntries.add(c.getClassEntry());
291 matching.removeSource(c);
292 }
293 for (ClassIdentity c : entry.getValue()) {
294 destClassEntries.add(c.getClassEntry());
295 matching.removeDest(c);
296 }
297 }
298 for (ClassIdentity c : matching.getUnmatchedSourceClasses()) {
299 sourceClassEntries.add(c.getClassEntry());
300 matching.removeSource(c);
301 }
302 for (ClassIdentity c : matching.getUnmatchedDestClasses()) {
303 destClassEntries.add(c.getClassEntry());
304 matching.removeDest(c);
305 }
306 }
307
308 // compute a matching for the classes
309 for (ClassEntry classEntry : sourceClassEntries) {
310 CtClass c = sourceLoader.loadClass(classEntry.getName());
311 ClassIdentity sourceClass = new ClassIdentity(c, sourceNamer, sourceIndex, useReferences);
312 matching.addSource(sourceClass);
313 }
314 for (ClassEntry classEntry : destClassEntries) {
315 CtClass c = destLoader.loadClass(classEntry.getName());
316 ClassIdentity destClass = new ClassIdentity(c, destNamer, destIndex, useReferences);
317 matching.matchDestClass(destClass);
318 }
319
320 // TEMP
321 System.out.println(matching);
322 } while (matching.getUniqueMatches().size() - numMatches > 0);
323 }
324
325 // check the class matches
326 System.out.println("Checking class matches...");
327 ClassNamer namer = new ClassNamer(matching.getUniqueMatches());
328 SidedClassNamer sourceNamer = namer.getSourceNamer();
329 SidedClassNamer destNamer = namer.getDestNamer();
330 for (Map.Entry<ClassIdentity,ClassIdentity> entry : matching.getUniqueMatches().entrySet()) {
331
332 // check source
333 ClassIdentity sourceClass = entry.getKey();
334 CtClass sourceC = sourceLoader.loadClass(sourceClass.getClassEntry().getName());
335 assert (sourceC != null) : "Unable to load source class " + sourceClass.getClassEntry();
336 assert (sourceClass.matches(sourceC)) : "Source " + sourceClass + " doesn't match " + new ClassIdentity(sourceC, sourceNamer, sourceIndex, false);
337
338 // check dest
339 ClassIdentity destClass = entry.getValue();
340 CtClass destC = destLoader.loadClass(destClass.getClassEntry().getName());
341 assert (destC != null) : "Unable to load dest class " + destClass.getClassEntry();
342 assert (destClass.matches(destC)) : "Dest " + destClass + " doesn't match " + new ClassIdentity(destC, destNamer, destIndex, false);
343 }
344
345 // warn about the ambiguous matchings
346 List<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>> ambiguousMatches = new ArrayList<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>>(matching.getAmbiguousMatches().entrySet());
347 Collections.sort(ambiguousMatches, new Comparator<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>>() {
348 @Override
349 public int compare(Map.Entry<List<ClassIdentity>,List<ClassIdentity>> a, Map.Entry<List<ClassIdentity>,List<ClassIdentity>> b) {
350 String aName = a.getKey().get(0).getClassEntry().getName();
351 String bName = b.getKey().get(0).getClassEntry().getName();
352 return aName.compareTo(bName);
353 }
354 });
355 for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : ambiguousMatches) {
356 System.out.println("Ambiguous matching:");
357 System.out.println("\tSource: " + getClassNames(entry.getKey()));
358 System.out.println("\tDest: " + getClassNames(entry.getValue()));
359 }
360
361 /* DEBUG
362 Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry = ambiguousMatches.get( 7 );
363 for (ClassIdentity c : entry.getKey()) {
364 System.out.println(c);
365 }
366 for(ClassIdentity c : entry.getKey()) {
367 System.out.println(decompile(sourceLoader, c.getClassEntry()));
368 }
369 */
370
371 return matching;
372 }
373
374 private static void printScoredMatches(int maxScore, List<Integer> scores, Multimap<Integer,ClassIdentity> scoredMatches) {
375 int numScoredMatchesShown = 0;
376 for (int score : scores) {
377 for (ClassIdentity scoredMatch : scoredMatches.get(score)) {
378 System.out.println(String.format("\tScore: %3d %3.0f%% %s", score, 100.0 * score / maxScore, scoredMatch.getClassEntry().getName()));
379 if (numScoredMatchesShown++ > 10) {
380 return;
381 }
382 }
383 }
384 }
385
386 private static List<String> getClassNames(Collection<ClassIdentity> classes) {
387 List<String> out = Lists.newArrayList();
388 for (ClassIdentity c : classes) {
389 out.add(c.getClassEntry().getName());
390 }
391 Collections.sort(out);
392 return out;
393 }
394
395 /* DEBUG
396 private static String decompile(TranslatingTypeLoader loader, ClassEntry classEntry) {
397 PlainTextOutput output = new PlainTextOutput();
398 DecompilerSettings settings = DecompilerSettings.javaDefaults();
399 settings.setForceExplicitImports(true);
400 settings.setShowSyntheticMembers(true);
401 settings.setTypeLoader(loader);
402 Decompiler.decompile(classEntry.getName(), output, settings);
403 return output.toString();
404 }
405 */
406}
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..ca39c42
--- /dev/null
+++ b/src/cuchaz/enigma/gui/Gui.java
@@ -0,0 +1,1165 @@
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;
91import cuchaz.enigma.mapping.Signature;
92
93public class Gui {
94
95 private GuiController m_controller;
96
97 // controls
98 private JFrame m_frame;
99 private ClassSelector m_obfClasses;
100 private ClassSelector m_deobfClasses;
101 private JEditorPane m_editor;
102 private JPanel m_classesPanel;
103 private JSplitPane m_splitClasses;
104 private JPanel m_infoPanel;
105 private ObfuscatedHighlightPainter m_obfuscatedHighlightPainter;
106 private DeobfuscatedHighlightPainter m_deobfuscatedHighlightPainter;
107 private OtherHighlightPainter m_otherHighlightPainter;
108 private SelectionHighlightPainter m_selectionHighlightPainter;
109 private JTree m_inheritanceTree;
110 private JTree m_implementationsTree;
111 private JTree m_callsTree;
112 private JList<Token> m_tokens;
113 private JTabbedPane m_tabs;
114
115 // dynamic menu items
116 private JMenuItem m_closeJarMenu;
117 private JMenuItem m_openMappingsMenu;
118 private JMenuItem m_saveMappingsMenu;
119 private JMenuItem m_saveMappingsAsMenu;
120 private JMenuItem m_closeMappingsMenu;
121 private JMenuItem m_renameMenu;
122 private JMenuItem m_showInheritanceMenu;
123 private JMenuItem m_openEntryMenu;
124 private JMenuItem m_openPreviousMenu;
125 private JMenuItem m_showCallsMenu;
126 private JMenuItem m_showImplementationsMenu;
127 private JMenuItem m_toggleMappingMenu;
128 private JMenuItem m_exportSourceMenu;
129 private JMenuItem m_exportJarMenu;
130
131 // state
132 private EntryReference<Entry,Entry> m_reference;
133 private JFileChooser m_jarFileChooser;
134 private JFileChooser m_mappingsFileChooser;
135 private JFileChooser m_exportSourceFileChooser;
136 private JFileChooser m_exportJarFileChooser;
137
138 public Gui() {
139
140 // init frame
141 m_frame = new JFrame(Constants.Name);
142 final Container pane = m_frame.getContentPane();
143 pane.setLayout(new BorderLayout());
144
145 if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) {
146 // install a global exception handler to the event thread
147 CrashDialog.init(m_frame);
148 Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
149 @Override
150 public void uncaughtException(Thread thread, Throwable ex) {
151 ex.printStackTrace(System.err);
152 CrashDialog.show(ex);
153 }
154 });
155 }
156
157 m_controller = new GuiController(this);
158
159 // init file choosers
160 m_jarFileChooser = new JFileChooser();
161 m_mappingsFileChooser = new JFileChooser();
162 m_exportSourceFileChooser = new JFileChooser();
163 m_exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
164 m_exportJarFileChooser = new JFileChooser();
165
166 // init obfuscated classes list
167 m_obfClasses = new ClassSelector(ClassSelector.ObfuscatedClassEntryComparator);
168 m_obfClasses.setListener(new ClassSelectionListener() {
169 @Override
170 public void onSelectClass(ClassEntry classEntry) {
171 navigateTo(classEntry);
172 }
173 });
174 JScrollPane obfScroller = new JScrollPane(m_obfClasses);
175 JPanel obfPanel = new JPanel();
176 obfPanel.setLayout(new BorderLayout());
177 obfPanel.add(new JLabel("Obfuscated Classes"), BorderLayout.NORTH);
178 obfPanel.add(obfScroller, BorderLayout.CENTER);
179
180 // init deobfuscated classes list
181 m_deobfClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
182 m_deobfClasses.setListener(new ClassSelectionListener() {
183 @Override
184 public void onSelectClass(ClassEntry classEntry) {
185 navigateTo(classEntry);
186 }
187 });
188 JScrollPane deobfScroller = new JScrollPane(m_deobfClasses);
189 JPanel deobfPanel = new JPanel();
190 deobfPanel.setLayout(new BorderLayout());
191 deobfPanel.add(new JLabel("De-obfuscated Classes"), BorderLayout.NORTH);
192 deobfPanel.add(deobfScroller, BorderLayout.CENTER);
193
194 // set up classes panel (don't add the splitter yet)
195 m_splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, obfPanel, deobfPanel);
196 m_splitClasses.setResizeWeight(0.3);
197 m_classesPanel = new JPanel();
198 m_classesPanel.setLayout(new BorderLayout());
199 m_classesPanel.setPreferredSize(new Dimension(250, 0));
200
201 // init info panel
202 m_infoPanel = new JPanel();
203 m_infoPanel.setLayout(new GridLayout(4, 1, 0, 0));
204 m_infoPanel.setPreferredSize(new Dimension(0, 100));
205 m_infoPanel.setBorder(BorderFactory.createTitledBorder("Identifier Info"));
206 clearReference();
207
208 // init editor
209 DefaultSyntaxKit.initKit();
210 m_obfuscatedHighlightPainter = new ObfuscatedHighlightPainter();
211 m_deobfuscatedHighlightPainter = new DeobfuscatedHighlightPainter();
212 m_otherHighlightPainter = new OtherHighlightPainter();
213 m_selectionHighlightPainter = new SelectionHighlightPainter();
214 m_editor = new JEditorPane();
215 m_editor.setEditable(false);
216 m_editor.setCaret(new BrowserCaret());
217 JScrollPane sourceScroller = new JScrollPane(m_editor);
218 m_editor.setContentType("text/java");
219 m_editor.addCaretListener(new CaretListener() {
220 @Override
221 public void caretUpdate(CaretEvent event) {
222 onCaretMove(event.getDot());
223 }
224 });
225 m_editor.addKeyListener(new KeyAdapter() {
226 @Override
227 public void keyPressed(KeyEvent event) {
228 switch (event.getKeyCode()) {
229 case KeyEvent.VK_R:
230 m_renameMenu.doClick();
231 break;
232
233 case KeyEvent.VK_I:
234 m_showInheritanceMenu.doClick();
235 break;
236
237 case KeyEvent.VK_M:
238 m_showImplementationsMenu.doClick();
239 break;
240
241 case KeyEvent.VK_N:
242 m_openEntryMenu.doClick();
243 break;
244
245 case KeyEvent.VK_P:
246 m_openPreviousMenu.doClick();
247 break;
248
249 case KeyEvent.VK_C:
250 m_showCallsMenu.doClick();
251 break;
252
253 case KeyEvent.VK_T:
254 m_toggleMappingMenu.doClick();
255 break;
256 }
257 }
258 });
259
260 // turn off token highlighting (it's wrong most of the time anyway...)
261 DefaultSyntaxKit kit = (DefaultSyntaxKit)m_editor.getEditorKit();
262 kit.toggleComponent(m_editor, "jsyntaxpane.components.TokenMarker");
263
264 // init editor popup menu
265 JPopupMenu popupMenu = new JPopupMenu();
266 m_editor.setComponentPopupMenu(popupMenu);
267 {
268 JMenuItem menu = new JMenuItem("Rename");
269 menu.addActionListener(new ActionListener() {
270 @Override
271 public void actionPerformed(ActionEvent event) {
272 startRename();
273 }
274 });
275 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0));
276 menu.setEnabled(false);
277 popupMenu.add(menu);
278 m_renameMenu = menu;
279 }
280 {
281 JMenuItem menu = new JMenuItem("Show Inheritance");
282 menu.addActionListener(new ActionListener() {
283 @Override
284 public void actionPerformed(ActionEvent event) {
285 showInheritance();
286 }
287 });
288 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, 0));
289 menu.setEnabled(false);
290 popupMenu.add(menu);
291 m_showInheritanceMenu = menu;
292 }
293 {
294 JMenuItem menu = new JMenuItem("Show Implementations");
295 menu.addActionListener(new ActionListener() {
296 @Override
297 public void actionPerformed(ActionEvent event) {
298 showImplementations();
299 }
300 });
301 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0));
302 menu.setEnabled(false);
303 popupMenu.add(menu);
304 m_showImplementationsMenu = menu;
305 }
306 {
307 JMenuItem menu = new JMenuItem("Show Calls");
308 menu.addActionListener(new ActionListener() {
309 @Override
310 public void actionPerformed(ActionEvent event) {
311 showCalls();
312 }
313 });
314 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0));
315 menu.setEnabled(false);
316 popupMenu.add(menu);
317 m_showCallsMenu = menu;
318 }
319 {
320 JMenuItem menu = new JMenuItem("Go to Declaration");
321 menu.addActionListener(new ActionListener() {
322 @Override
323 public void actionPerformed(ActionEvent event) {
324 navigateTo(m_reference.entry);
325 }
326 });
327 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0));
328 menu.setEnabled(false);
329 popupMenu.add(menu);
330 m_openEntryMenu = menu;
331 }
332 {
333 JMenuItem menu = new JMenuItem("Go to previous");
334 menu.addActionListener(new ActionListener() {
335 @Override
336 public void actionPerformed(ActionEvent event) {
337 m_controller.openPreviousReference();
338 }
339 });
340 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0));
341 menu.setEnabled(false);
342 popupMenu.add(menu);
343 m_openPreviousMenu = menu;
344 }
345 {
346 JMenuItem menu = new JMenuItem("Mark as deobfuscated");
347 menu.addActionListener(new ActionListener() {
348 @Override
349 public void actionPerformed(ActionEvent event) {
350 toggleMapping();
351 }
352 });
353 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0));
354 menu.setEnabled(false);
355 popupMenu.add(menu);
356 m_toggleMappingMenu = menu;
357 }
358
359 // init inheritance panel
360 m_inheritanceTree = new JTree();
361 m_inheritanceTree.setModel(null);
362 m_inheritanceTree.addMouseListener(new MouseAdapter() {
363 @Override
364 public void mouseClicked(MouseEvent event) {
365 if (event.getClickCount() == 2) {
366 // get the selected node
367 TreePath path = m_inheritanceTree.getSelectionPath();
368 if (path == null) {
369 return;
370 }
371
372 Object node = path.getLastPathComponent();
373 if (node instanceof ClassInheritanceTreeNode) {
374 ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode)node;
375 navigateTo(new ClassEntry(classNode.getObfClassName()));
376 } else if (node instanceof MethodInheritanceTreeNode) {
377 MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode)node;
378 if (methodNode.isImplemented()) {
379 navigateTo(methodNode.getMethodEntry());
380 }
381 }
382 }
383 }
384 });
385 JPanel inheritancePanel = new JPanel();
386 inheritancePanel.setLayout(new BorderLayout());
387 inheritancePanel.add(new JScrollPane(m_inheritanceTree));
388
389 // init implementations panel
390 m_implementationsTree = new JTree();
391 m_implementationsTree.setModel(null);
392 m_implementationsTree.addMouseListener(new MouseAdapter() {
393 @Override
394 public void mouseClicked(MouseEvent event) {
395 if (event.getClickCount() == 2) {
396 // get the selected node
397 TreePath path = m_implementationsTree.getSelectionPath();
398 if (path == null) {
399 return;
400 }
401
402 Object node = path.getLastPathComponent();
403 if (node instanceof ClassImplementationsTreeNode) {
404 ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode)node;
405 navigateTo(classNode.getClassEntry());
406 } else if (node instanceof MethodImplementationsTreeNode) {
407 MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode)node;
408 navigateTo(methodNode.getMethodEntry());
409 }
410 }
411 }
412 });
413 JPanel implementationsPanel = new JPanel();
414 implementationsPanel.setLayout(new BorderLayout());
415 implementationsPanel.add(new JScrollPane(m_implementationsTree));
416
417 // init call panel
418 m_callsTree = new JTree();
419 m_callsTree.setModel(null);
420 m_callsTree.addMouseListener(new MouseAdapter() {
421 @SuppressWarnings("unchecked")
422 @Override
423 public void mouseClicked(MouseEvent event) {
424 if (event.getClickCount() == 2) {
425 // get the selected node
426 TreePath path = m_callsTree.getSelectionPath();
427 if (path == null) {
428 return;
429 }
430
431 Object node = path.getLastPathComponent();
432 if (node instanceof ReferenceTreeNode) {
433 ReferenceTreeNode<Entry,Entry> referenceNode = ((ReferenceTreeNode<Entry,Entry>)node);
434 if (referenceNode.getReference() != null) {
435 navigateTo(referenceNode.getReference());
436 } else {
437 navigateTo(referenceNode.getEntry());
438 }
439 }
440 }
441 }
442 });
443 m_tokens = new JList<Token>();
444 m_tokens.setCellRenderer(new TokenListCellRenderer(m_controller));
445 m_tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
446 m_tokens.setLayoutOrientation(JList.VERTICAL);
447 m_tokens.addMouseListener(new MouseAdapter() {
448 @Override
449 public void mouseClicked(MouseEvent event) {
450 if (event.getClickCount() == 2) {
451 Token selected = m_tokens.getSelectedValue();
452 if (selected != null) {
453 showToken(selected);
454 }
455 }
456 }
457 });
458 m_tokens.setPreferredSize(new Dimension(0, 200));
459 m_tokens.setMinimumSize(new Dimension(0, 200));
460 JSplitPane callPanel = new JSplitPane(
461 JSplitPane.VERTICAL_SPLIT,
462 true,
463 new JScrollPane(m_callsTree),
464 new JScrollPane(m_tokens)
465 );
466 callPanel.setResizeWeight(1); // let the top side take all the slack
467 callPanel.resetToPreferredSizes();
468
469 // layout controls
470 JPanel centerPanel = new JPanel();
471 centerPanel.setLayout(new BorderLayout());
472 centerPanel.add(m_infoPanel, BorderLayout.NORTH);
473 centerPanel.add(sourceScroller, BorderLayout.CENTER);
474 m_tabs = new JTabbedPane();
475 m_tabs.setPreferredSize(new Dimension(250, 0));
476 m_tabs.addTab("Inheritance", inheritancePanel);
477 m_tabs.addTab("Implementations", implementationsPanel);
478 m_tabs.addTab("Call Graph", callPanel);
479 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, m_tabs);
480 splitRight.setResizeWeight(1); // let the left side take all the slack
481 splitRight.resetToPreferredSizes();
482 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, m_classesPanel, splitRight);
483 splitCenter.setResizeWeight(0); // let the right side take all the slack
484 pane.add(splitCenter, BorderLayout.CENTER);
485
486 // init menus
487 JMenuBar menuBar = new JMenuBar();
488 m_frame.setJMenuBar(menuBar);
489 {
490 JMenu menu = new JMenu("File");
491 menuBar.add(menu);
492 {
493 JMenuItem item = new JMenuItem("Open Jar...");
494 menu.add(item);
495 item.addActionListener(new ActionListener() {
496 @Override
497 public void actionPerformed(ActionEvent event) {
498 if (m_jarFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
499 // load the jar in a separate thread
500 new Thread() {
501 @Override
502 public void run() {
503 try {
504 m_controller.openJar(new JarFile(m_jarFileChooser.getSelectedFile()));
505 } catch (IOException ex) {
506 throw new Error(ex);
507 }
508 }
509 }.start();
510 }
511 }
512 });
513 }
514 {
515 JMenuItem item = new JMenuItem("Close Jar");
516 menu.add(item);
517 item.addActionListener(new ActionListener() {
518 @Override
519 public void actionPerformed(ActionEvent event) {
520 m_controller.closeJar();
521 }
522 });
523 m_closeJarMenu = item;
524 }
525 menu.addSeparator();
526 {
527 JMenuItem item = new JMenuItem("Open Mappings...");
528 menu.add(item);
529 item.addActionListener(new ActionListener() {
530 @Override
531 public void actionPerformed(ActionEvent event) {
532 if (m_mappingsFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
533 try {
534 m_controller.openMappings(m_mappingsFileChooser.getSelectedFile());
535 } catch (IOException ex) {
536 throw new Error(ex);
537 } catch (MappingParseException ex) {
538 JOptionPane.showMessageDialog(m_frame, ex.getMessage());
539 }
540 }
541 }
542 });
543 m_openMappingsMenu = item;
544 }
545 {
546 JMenuItem item = new JMenuItem("Save Mappings");
547 menu.add(item);
548 item.addActionListener(new ActionListener() {
549 @Override
550 public void actionPerformed(ActionEvent event) {
551 try {
552 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
553 } catch (IOException ex) {
554 throw new Error(ex);
555 }
556 }
557 });
558 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK));
559 m_saveMappingsMenu = item;
560 }
561 {
562 JMenuItem item = new JMenuItem("Save Mappings As...");
563 menu.add(item);
564 item.addActionListener(new ActionListener() {
565 @Override
566 public void actionPerformed(ActionEvent event) {
567 if (m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
568 try {
569 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
570 m_saveMappingsMenu.setEnabled(true);
571 } catch (IOException ex) {
572 throw new Error(ex);
573 }
574 }
575 }
576 });
577 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
578 m_saveMappingsAsMenu = item;
579 }
580 {
581 JMenuItem item = new JMenuItem("Close Mappings");
582 menu.add(item);
583 item.addActionListener(new ActionListener() {
584 @Override
585 public void actionPerformed(ActionEvent event) {
586 m_controller.closeMappings();
587 }
588 });
589 m_closeMappingsMenu = item;
590 }
591 menu.addSeparator();
592 {
593 JMenuItem item = new JMenuItem("Export Source...");
594 menu.add(item);
595 item.addActionListener(new ActionListener() {
596 @Override
597 public void actionPerformed(ActionEvent event) {
598 if (m_exportSourceFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
599 m_controller.exportSource(m_exportSourceFileChooser.getSelectedFile());
600 }
601 }
602 });
603 m_exportSourceMenu = item;
604 }
605 {
606 JMenuItem item = new JMenuItem("Export Jar...");
607 menu.add(item);
608 item.addActionListener(new ActionListener() {
609 @Override
610 public void actionPerformed(ActionEvent event) {
611 if (m_exportJarFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
612 m_controller.exportJar(m_exportJarFileChooser.getSelectedFile());
613 }
614 }
615 });
616 m_exportJarMenu = item;
617 }
618 menu.addSeparator();
619 {
620 JMenuItem item = new JMenuItem("Exit");
621 menu.add(item);
622 item.addActionListener(new ActionListener() {
623 @Override
624 public void actionPerformed(ActionEvent event) {
625 close();
626 }
627 });
628 }
629 }
630 {
631 JMenu menu = new JMenu("Help");
632 menuBar.add(menu);
633 {
634 JMenuItem item = new JMenuItem("About");
635 menu.add(item);
636 item.addActionListener(new ActionListener() {
637 @Override
638 public void actionPerformed(ActionEvent event) {
639 AboutDialog.show(m_frame);
640 }
641 });
642 }
643 }
644
645 // init state
646 onCloseJar();
647
648 m_frame.addWindowListener(new WindowAdapter() {
649 @Override
650 public void windowClosing(WindowEvent event) {
651 close();
652 }
653 });
654
655 // show the frame
656 pane.doLayout();
657 m_frame.setSize(1024, 576);
658 m_frame.setMinimumSize(new Dimension(640, 480));
659 m_frame.setVisible(true);
660 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
661 }
662
663 public JFrame getFrame() {
664 return m_frame;
665 }
666
667 public GuiController getController() {
668 return m_controller;
669 }
670
671 public void onStartOpenJar() {
672 m_classesPanel.removeAll();
673 JPanel panel = new JPanel();
674 panel.setLayout(new FlowLayout());
675 panel.add(new JLabel("Loading..."));
676 m_classesPanel.add(panel);
677 redraw();
678 }
679
680 public void onFinishOpenJar(String jarName) {
681 // update gui
682 m_frame.setTitle(Constants.Name + " - " + jarName);
683 m_classesPanel.removeAll();
684 m_classesPanel.add(m_splitClasses);
685 setSource(null);
686
687 // update menu
688 m_closeJarMenu.setEnabled(true);
689 m_openMappingsMenu.setEnabled(true);
690 m_saveMappingsMenu.setEnabled(false);
691 m_saveMappingsAsMenu.setEnabled(true);
692 m_closeMappingsMenu.setEnabled(true);
693 m_exportSourceMenu.setEnabled(true);
694 m_exportJarMenu.setEnabled(true);
695
696 redraw();
697 }
698
699 public void onCloseJar() {
700 // update gui
701 m_frame.setTitle(Constants.Name);
702 setObfClasses(null);
703 setDeobfClasses(null);
704 setSource(null);
705 m_classesPanel.removeAll();
706
707 // update menu
708 m_closeJarMenu.setEnabled(false);
709 m_openMappingsMenu.setEnabled(false);
710 m_saveMappingsMenu.setEnabled(false);
711 m_saveMappingsAsMenu.setEnabled(false);
712 m_closeMappingsMenu.setEnabled(false);
713 m_exportSourceMenu.setEnabled(false);
714 m_exportJarMenu.setEnabled(false);
715
716 redraw();
717 }
718
719 public void setObfClasses(Collection<ClassEntry> obfClasses) {
720 m_obfClasses.setClasses(obfClasses);
721 }
722
723 public void setDeobfClasses(Collection<ClassEntry> deobfClasses) {
724 m_deobfClasses.setClasses(deobfClasses);
725 }
726
727 public void setMappingsFile(File file) {
728 m_mappingsFileChooser.setSelectedFile(file);
729 m_saveMappingsMenu.setEnabled(file != null);
730 }
731
732 public void setSource(String source) {
733 m_editor.getHighlighter().removeAllHighlights();
734 m_editor.setText(source);
735 }
736
737 public void showToken(final Token token) {
738 if (token == null) {
739 throw new IllegalArgumentException("Token cannot be null!");
740 }
741
742 // set the caret position to the token
743 m_editor.setCaretPosition(token.start);
744 m_editor.grabFocus();
745
746 try {
747 // make sure the token is visible in the scroll window
748 Rectangle start = m_editor.modelToView(token.start);
749 Rectangle end = m_editor.modelToView(token.end);
750 final Rectangle show = start.union(end);
751 show.grow(start.width * 10, start.height * 6);
752 SwingUtilities.invokeLater(new Runnable() {
753 @Override
754 public void run() {
755 m_editor.scrollRectToVisible(show);
756 }
757 });
758 } catch (BadLocationException ex) {
759 throw new Error(ex);
760 }
761
762 // highlight the token momentarily
763 final Timer timer = new Timer(200, new ActionListener() {
764 private int m_counter = 0;
765 private Object m_highlight = null;
766
767 @Override
768 public void actionPerformed(ActionEvent event) {
769 if (m_counter % 2 == 0) {
770 try {
771 m_highlight = m_editor.getHighlighter().addHighlight(token.start, token.end, m_selectionHighlightPainter);
772 } catch (BadLocationException ex) {
773 // don't care
774 }
775 } else if (m_highlight != null) {
776 m_editor.getHighlighter().removeHighlight(m_highlight);
777 }
778
779 if (m_counter++ > 6) {
780 Timer timer = (Timer)event.getSource();
781 timer.stop();
782 }
783 }
784 });
785 timer.start();
786
787 redraw();
788 }
789
790 public void showTokens(Collection<Token> tokens) {
791 Vector<Token> sortedTokens = new Vector<Token>(tokens);
792 Collections.sort(sortedTokens);
793 if (sortedTokens.size() > 1) {
794 // sort the tokens and update the tokens panel
795 m_tokens.setListData(sortedTokens);
796 m_tokens.setSelectedIndex(0);
797 } else {
798 m_tokens.setListData(new Vector<Token>());
799 }
800
801 // show the first token
802 showToken(sortedTokens.get(0));
803 }
804
805 public void setHighlightedTokens(Iterable<Token> obfuscatedTokens, Iterable<Token> deobfuscatedTokens, Iterable<Token> otherTokens) {
806
807 // remove any old highlighters
808 m_editor.getHighlighter().removeAllHighlights();
809
810 // color things based on the index
811 if (obfuscatedTokens != null) {
812 setHighlightedTokens(obfuscatedTokens, m_obfuscatedHighlightPainter);
813 }
814 if (deobfuscatedTokens != null) {
815 setHighlightedTokens(deobfuscatedTokens, m_deobfuscatedHighlightPainter);
816 }
817 if (otherTokens != null) {
818 setHighlightedTokens(otherTokens, m_otherHighlightPainter);
819 }
820
821 redraw();
822 }
823
824 private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) {
825 for (Token token : tokens) {
826 try {
827 m_editor.getHighlighter().addHighlight(token.start, token.end, painter);
828 } catch (BadLocationException ex) {
829 throw new IllegalArgumentException(ex);
830 }
831 }
832 }
833
834 private void clearReference() {
835 m_infoPanel.removeAll();
836 JLabel label = new JLabel("No identifier selected");
837 GuiTricks.unboldLabel(label);
838 label.setHorizontalAlignment(JLabel.CENTER);
839 m_infoPanel.add(label);
840
841 redraw();
842 }
843
844 private void showReference(EntryReference<Entry,Entry> reference) {
845 if (reference == null) {
846 clearReference();
847 return;
848 }
849
850 m_reference = reference;
851
852 m_infoPanel.removeAll();
853 if (reference.entry instanceof ClassEntry) {
854 showClassEntry((ClassEntry)m_reference.entry);
855 } else if (m_reference.entry instanceof FieldEntry) {
856 showFieldEntry((FieldEntry)m_reference.entry);
857 } else if (m_reference.entry instanceof MethodEntry) {
858 showMethodEntry((MethodEntry)m_reference.entry);
859 } else if (m_reference.entry instanceof ConstructorEntry) {
860 showConstructorEntry((ConstructorEntry)m_reference.entry);
861 } else if (m_reference.entry instanceof ArgumentEntry) {
862 showArgumentEntry((ArgumentEntry)m_reference.entry);
863 } else {
864 throw new Error("Unknown entry type: " + m_reference.entry.getClass().getName());
865 }
866
867 redraw();
868 }
869
870 private void showClassEntry(ClassEntry entry) {
871 addNameValue(m_infoPanel, "Class", entry.getName());
872 }
873
874 private void showFieldEntry(FieldEntry entry) {
875 addNameValue(m_infoPanel, "Field", entry.getName());
876 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
877 }
878
879 private void showMethodEntry(MethodEntry entry) {
880 addNameValue(m_infoPanel, "Method", entry.getName());
881 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
882 addNameValue(m_infoPanel, "Signature", entry.getSignature().toString());
883 }
884
885 private void showConstructorEntry(ConstructorEntry entry) {
886 addNameValue(m_infoPanel, "Constructor", entry.getClassEntry().getName());
887 addNameValue(m_infoPanel, "Signature", entry.getSignature().toString());
888 }
889
890 private void showArgumentEntry(ArgumentEntry entry) {
891 addNameValue(m_infoPanel, "Argument", entry.getName());
892 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
893 addNameValue(m_infoPanel, "Method", entry.getBehaviorEntry().getName());
894 addNameValue(m_infoPanel, "Index", Integer.toString(entry.getIndex()));
895 }
896
897 private void addNameValue(JPanel container, String name, String value) {
898 JPanel panel = new JPanel();
899 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
900 container.add(panel);
901
902 JLabel label = new JLabel(name + ":", JLabel.RIGHT);
903 label.setPreferredSize(new Dimension(100, label.getPreferredSize().height));
904 panel.add(label);
905
906 panel.add(GuiTricks.unboldLabel(new JLabel(value, JLabel.LEFT)));
907 }
908
909 private void onCaretMove(int pos) {
910
911 Token token = m_controller.getToken(pos);
912 boolean isToken = token != null;
913
914 m_reference = m_controller.getDeobfReference(token);
915 boolean isClassEntry = isToken && m_reference.entry instanceof ClassEntry;
916 boolean isFieldEntry = isToken && m_reference.entry instanceof FieldEntry;
917 boolean isMethodEntry = isToken && m_reference.entry instanceof MethodEntry;
918 boolean isConstructorEntry = isToken && m_reference.entry instanceof ConstructorEntry;
919 boolean isInJar = isToken && m_controller.entryIsInJar(m_reference.entry);
920 boolean isRenameable = isToken && m_controller.referenceIsRenameable(m_reference);
921
922 if (isToken) {
923 showReference(m_reference);
924 } else {
925 clearReference();
926 }
927
928 m_renameMenu.setEnabled(isRenameable && isToken);
929 m_showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry);
930 m_showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry);
931 m_showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry);
932 m_openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry));
933 m_openPreviousMenu.setEnabled(m_controller.hasPreviousLocation());
934 m_toggleMappingMenu.setEnabled(isRenameable && isToken);
935
936 if (isToken && m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
937 m_toggleMappingMenu.setText("Reset to obfuscated");
938 } else {
939 m_toggleMappingMenu.setText("Mark as deobfuscated");
940 }
941 }
942
943 private void navigateTo(Entry entry) {
944 if (!m_controller.entryIsInJar(entry)) {
945 // entry is not in the jar. Ignore it
946 return;
947 }
948 if (m_reference != null) {
949 m_controller.savePreviousReference(m_reference);
950 }
951 m_controller.openDeclaration(entry);
952 }
953
954 private void navigateTo(EntryReference<Entry,Entry> reference) {
955 if (!m_controller.entryIsInJar(reference.getLocationClassEntry())) {
956 // reference is not in the jar. Ignore it
957 return;
958 }
959 if (m_reference != null) {
960 m_controller.savePreviousReference(m_reference);
961 }
962 m_controller.openReference(reference);
963 }
964
965 private void startRename() {
966
967 // init the text box
968 final JTextField text = new JTextField();
969 text.setText(m_reference.getNamableName());
970 text.setPreferredSize(new Dimension(360, text.getPreferredSize().height));
971 text.addKeyListener(new KeyAdapter() {
972 @Override
973 public void keyPressed(KeyEvent event) {
974 switch (event.getKeyCode()) {
975 case KeyEvent.VK_ENTER:
976 finishRename(text, true);
977 break;
978
979 case KeyEvent.VK_ESCAPE:
980 finishRename(text, false);
981 break;
982 }
983 }
984 });
985
986 // find the label with the name and replace it with the text box
987 JPanel panel = (JPanel)m_infoPanel.getComponent(0);
988 panel.remove(panel.getComponentCount() - 1);
989 panel.add(text);
990 text.grabFocus();
991 text.selectAll();
992
993 redraw();
994 }
995
996 private void finishRename(JTextField text, boolean saveName) {
997 String newName = text.getText();
998 if (saveName && newName != null && newName.length() > 0) {
999 try {
1000 m_controller.rename(m_reference, newName);
1001 } catch (IllegalNameException ex) {
1002 text.setBorder(BorderFactory.createLineBorder(Color.red, 1));
1003 text.setToolTipText(ex.getReason());
1004 GuiTricks.showToolTipNow(text);
1005 }
1006 return;
1007 }
1008
1009 // abort the rename
1010 JPanel panel = (JPanel)m_infoPanel.getComponent(0);
1011 panel.remove(panel.getComponentCount() - 1);
1012 panel.add(GuiTricks.unboldLabel(new JLabel(m_reference.getNamableName(), JLabel.LEFT)));
1013
1014 m_editor.grabFocus();
1015
1016 redraw();
1017 }
1018
1019 private void showInheritance() {
1020
1021 if (m_reference == null) {
1022 return;
1023 }
1024
1025 m_inheritanceTree.setModel(null);
1026
1027 if (m_reference.entry instanceof ClassEntry) {
1028 // get the class inheritance
1029 ClassInheritanceTreeNode classNode = m_controller.getClassInheritance((ClassEntry)m_reference.entry);
1030
1031 // show the tree at the root
1032 TreePath path = getPathToRoot(classNode);
1033 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1034 m_inheritanceTree.expandPath(path);
1035 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
1036 } else if (m_reference.entry instanceof MethodEntry) {
1037 // get the method inheritance
1038 MethodInheritanceTreeNode classNode = m_controller.getMethodInheritance((MethodEntry)m_reference.entry);
1039
1040 // show the tree at the root
1041 TreePath path = getPathToRoot(classNode);
1042 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1043 m_inheritanceTree.expandPath(path);
1044 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
1045 }
1046
1047 m_tabs.setSelectedIndex(0);
1048 redraw();
1049 }
1050
1051 private void showImplementations() {
1052
1053 if (m_reference == null) {
1054 return;
1055 }
1056
1057 m_implementationsTree.setModel(null);
1058
1059 if (m_reference.entry instanceof ClassEntry) {
1060 // get the class implementations
1061 ClassImplementationsTreeNode node = m_controller.getClassImplementations((ClassEntry)m_reference.entry);
1062 if (node != null) {
1063 // show the tree at the root
1064 TreePath path = getPathToRoot(node);
1065 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1066 m_implementationsTree.expandPath(path);
1067 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1068 }
1069 } else if (m_reference.entry instanceof MethodEntry) {
1070 // get the method implementations
1071 MethodImplementationsTreeNode node = m_controller.getMethodImplementations((MethodEntry)m_reference.entry);
1072 if (node != null) {
1073 // show the tree at the root
1074 TreePath path = getPathToRoot(node);
1075 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1076 m_implementationsTree.expandPath(path);
1077 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1078 }
1079 }
1080
1081 m_tabs.setSelectedIndex(1);
1082 redraw();
1083 }
1084
1085 private void showCalls() {
1086
1087 if (m_reference == null) {
1088 return;
1089 }
1090
1091 if (m_reference.entry instanceof ClassEntry) {
1092 // look for calls to the default constructor
1093 // TODO: get a list of all the constructors and find calls to all of them
1094 BehaviorReferenceTreeNode node = m_controller.getMethodReferences(new ConstructorEntry((ClassEntry)m_reference.entry, new Signature("()V")));
1095 m_callsTree.setModel(new DefaultTreeModel(node));
1096 } else if (m_reference.entry instanceof FieldEntry) {
1097 FieldReferenceTreeNode node = m_controller.getFieldReferences((FieldEntry)m_reference.entry);
1098 m_callsTree.setModel(new DefaultTreeModel(node));
1099 } else if (m_reference.entry instanceof MethodEntry) {
1100 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((MethodEntry)m_reference.entry);
1101 m_callsTree.setModel(new DefaultTreeModel(node));
1102 } else if (m_reference.entry instanceof ConstructorEntry) {
1103 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((ConstructorEntry)m_reference.entry);
1104 m_callsTree.setModel(new DefaultTreeModel(node));
1105 }
1106
1107 m_tabs.setSelectedIndex(2);
1108 redraw();
1109 }
1110
1111 private void toggleMapping() {
1112 if (m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
1113 m_controller.removeMapping(m_reference);
1114 } else {
1115 m_controller.markAsDeobfuscated(m_reference);
1116 }
1117 }
1118
1119 private TreePath getPathToRoot(TreeNode node) {
1120 List<TreeNode> nodes = Lists.newArrayList();
1121 TreeNode n = node;
1122 do {
1123 nodes.add(n);
1124 n = n.getParent();
1125 } while (n != null);
1126 Collections.reverse(nodes);
1127 return new TreePath(nodes.toArray());
1128 }
1129
1130 private void close() {
1131 if (!m_controller.isDirty()) {
1132 // everything is saved, we can exit safely
1133 m_frame.dispose();
1134 } else {
1135 // ask to save before closing
1136 String[] options = { "Save and exit", "Discard changes", "Cancel" };
1137 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,
1138 JOptionPane.QUESTION_MESSAGE, null, options, options[2]);
1139 switch (response) {
1140 case JOptionPane.YES_OPTION: // save and exit
1141 if (m_mappingsFileChooser.getSelectedFile() != null || m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
1142 try {
1143 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
1144 m_frame.dispose();
1145 } catch (IOException ex) {
1146 throw new Error(ex);
1147 }
1148 }
1149 break;
1150
1151 case JOptionPane.NO_OPTION:
1152 // don't save, exit
1153 m_frame.dispose();
1154 break;
1155
1156 // cancel means do nothing
1157 }
1158 }
1159 }
1160
1161 private void redraw() {
1162 m_frame.validate();
1163 m_frame.repaint();
1164 }
1165}
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..aa22265
--- /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 Signature getMethodSignature() {
86 return m_behaviorEntry.getSignature();
87 }
88
89 @Override
90 public int hashCode() {
91 return Util.combineHashesOrdered(
92 m_behaviorEntry,
93 Integer.valueOf(m_index).hashCode(),
94 m_name.hashCode()
95 );
96 }
97
98 @Override
99 public boolean equals(Object other) {
100 if (other instanceof ArgumentEntry) {
101 return equals((ArgumentEntry)other);
102 }
103 return false;
104 }
105
106 public boolean equals(ArgumentEntry other) {
107 return m_behaviorEntry.equals(other.m_behaviorEntry)
108 && m_index == other.m_index
109 && m_name.equals(other.m_name);
110 }
111
112 @Override
113 public String toString() {
114 return m_behaviorEntry.toString() + "(" + m_index + ":" + m_name + ")";
115 }
116}
diff --git a/src/cuchaz/enigma/mapping/ArgumentMapping.java b/src/cuchaz/enigma/mapping/ArgumentMapping.java
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..535788f
--- /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 Signature getSignature();
15}
diff --git a/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java b/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java
new file mode 100644
index 0000000..61e501b
--- /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, new Signature(signature));
27 } else if (name.equals("<clinit>")) {
28 return new ConstructorEntry(classEntry);
29 } else {
30 return new MethodEntry(classEntry, name, new Signature(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().toString());
52 }
53
54 public static BehaviorEntry createDeobf(ClassEntry classEntry, MethodMapping methodMapping) {
55 return create(classEntry, methodMapping.getDeobfName(), methodMapping.getObfSignature().toString());
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..e2c3d56
--- /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, Signature obfSignature) {
235 return m_methodsByObf.containsKey(getMethodKey(obfName, obfSignature));
236 }
237
238 public boolean containsDeobfMethod(String deobfName, Signature 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, Signature signature) {
270 return m_methodsByObf.get(getMethodKey(obfName, signature));
271 }
272
273 public MethodMapping getMethodByDeobf(String deobfName, Signature signature) {
274 return m_methodsByDeobf.get(getMethodKey(deobfName, signature));
275 }
276
277 private String getMethodKey(String name, Signature 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, Signature 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, Signature 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, Signature obfMethodSignature, int argumentIndex) {
313 m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)).removeArgumentName(argumentIndex);
314 }
315
316 private MethodMapping createMethodMapping(String obfName, Signature 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/ClassNameReplacer.java b/src/cuchaz/enigma/mapping/ClassNameReplacer.java
new file mode 100644
index 0000000..bf984fd
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ClassNameReplacer.java
@@ -0,0 +1,5 @@
1package cuchaz.enigma.mapping;
2
3public interface ClassNameReplacer {
4 String replace(String className);
5}
diff --git a/src/cuchaz/enigma/mapping/ConstructorEntry.java b/src/cuchaz/enigma/mapping/ConstructorEntry.java
new file mode 100644
index 0000000..5f3760f
--- /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 Signature m_signature;
23
24 public ConstructorEntry(ClassEntry classEntry) {
25 this(classEntry, null);
26 }
27
28 public ConstructorEntry(ClassEntry classEntry, Signature signature) {
29 if (classEntry == null) {
30 throw new IllegalArgumentException("Class cannot be null!");
31 }
32
33 m_classEntry = classEntry;
34 m_signature = signature;
35 }
36
37 public ConstructorEntry(ConstructorEntry other) {
38 m_classEntry = new ClassEntry(other.m_classEntry);
39 m_signature = other.m_signature;
40 }
41
42 public ConstructorEntry(ConstructorEntry other, String newClassName) {
43 m_classEntry = new ClassEntry(newClassName);
44 m_signature = other.m_signature;
45 }
46
47 @Override
48 public ClassEntry getClassEntry() {
49 return m_classEntry;
50 }
51
52 @Override
53 public String getName() {
54 if (isStatic()) {
55 return "<clinit>";
56 }
57 return "<init>";
58 }
59
60 public boolean isStatic() {
61 return m_signature == null;
62 }
63
64 @Override
65 public Signature getSignature() {
66 return m_signature;
67 }
68
69 @Override
70 public String getClassName() {
71 return m_classEntry.getName();
72 }
73
74 @Override
75 public ConstructorEntry cloneToNewClass(ClassEntry classEntry) {
76 return new ConstructorEntry(this, classEntry.getName());
77 }
78
79 @Override
80 public int hashCode() {
81 if (isStatic()) {
82 return Util.combineHashesOrdered(m_classEntry);
83 } else {
84 return Util.combineHashesOrdered(m_classEntry, m_signature);
85 }
86 }
87
88 @Override
89 public boolean equals(Object other) {
90 if (other instanceof ConstructorEntry) {
91 return equals((ConstructorEntry)other);
92 }
93 return false;
94 }
95
96 public boolean equals(ConstructorEntry other) {
97 if (isStatic() != other.isStatic()) {
98 return false;
99 }
100
101 if (isStatic()) {
102 return m_classEntry.equals(other.m_classEntry);
103 } else {
104 return m_classEntry.equals(other.m_classEntry) && m_signature.equals(other.m_signature);
105 }
106 }
107
108 @Override
109 public String toString() {
110 if (isStatic()) {
111 return m_classEntry.getName() + "." + getName();
112 } else {
113 return m_classEntry.getName() + "." + getName() + m_signature;
114 }
115 }
116}
diff --git a/src/cuchaz/enigma/mapping/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..0c446c4
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/JavassistUtil.java
@@ -0,0 +1,83 @@
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 javassist.expr.ConstructorCall;
10import javassist.expr.FieldAccess;
11import javassist.expr.MethodCall;
12import javassist.expr.NewExpr;
13
14public class JavassistUtil {
15
16 public static ClassEntry getClassEntry(CtClass c) {
17 return new ClassEntry(Descriptor.toJvmName(c.getName()));
18 }
19
20 public static ClassEntry getSuperclassEntry(CtClass c) {
21 return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
22 }
23
24 public static MethodEntry getMethodEntry(CtMethod method) {
25 return new MethodEntry(
26 getClassEntry(method.getDeclaringClass()),
27 method.getName(),
28 new Signature(method.getMethodInfo().getDescriptor())
29 );
30 }
31
32 public static MethodEntry getMethodEntry(MethodCall call) {
33 return new MethodEntry(
34 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
35 call.getMethodName(),
36 new Signature(call.getSignature())
37 );
38 }
39
40 public static ConstructorEntry getConstructorEntry(CtConstructor constructor) {
41 return new ConstructorEntry(
42 getClassEntry(constructor.getDeclaringClass()),
43 new Signature(constructor.getMethodInfo().getDescriptor())
44 );
45 }
46
47 public static ConstructorEntry getConstructorEntry(ConstructorCall call) {
48 return new ConstructorEntry(
49 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
50 new Signature(call.getSignature())
51 );
52 }
53
54 public static ConstructorEntry getConstructorEntry(NewExpr call) {
55 return new ConstructorEntry(
56 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
57 new Signature(call.getSignature())
58 );
59 }
60
61 public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) {
62 if (behavior instanceof CtMethod) {
63 return getMethodEntry((CtMethod)behavior);
64 } else if (behavior instanceof CtConstructor) {
65 return getConstructorEntry((CtConstructor)behavior);
66 }
67 throw new Error("behavior is neither Method nor Constructor!");
68 }
69
70 public static FieldEntry getFieldEntry(CtField field) {
71 return new FieldEntry(
72 getClassEntry(field.getDeclaringClass()),
73 field.getName()
74 );
75 }
76
77 public static FieldEntry getFieldEntry(FieldAccess call) {
78 return new FieldEntry(
79 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
80 call.getFieldName()
81 );
82 }
83}
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..57d8001
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Mappings.java
@@ -0,0 +1,188 @@
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.Collection;
16import java.util.Map;
17import java.util.Set;
18
19import com.google.common.collect.Maps;
20import com.google.common.collect.Sets;
21
22import cuchaz.enigma.analysis.TranslationIndex;
23
24public class Mappings implements Serializable {
25
26 private static final long serialVersionUID = 4649790259460259026L;
27
28 protected Map<String,ClassMapping> m_classesByObf;
29 protected Map<String,ClassMapping> m_classesByDeobf;
30
31 public Mappings() {
32 m_classesByObf = Maps.newHashMap();
33 m_classesByDeobf = Maps.newHashMap();
34 }
35
36 public Mappings(Iterable<ClassMapping> classes) {
37 this();
38
39 for (ClassMapping classMapping : classes) {
40 m_classesByObf.put(classMapping.getObfName(), classMapping);
41 if (classMapping.getDeobfName() != null) {
42 m_classesByDeobf.put(classMapping.getDeobfName(), classMapping);
43 }
44 }
45 }
46
47 public Collection<ClassMapping> classes() {
48 assert (m_classesByObf.size() >= m_classesByDeobf.size());
49 return m_classesByObf.values();
50 }
51
52 public void addClassMapping(ClassMapping classMapping) {
53 if (m_classesByObf.containsKey(classMapping.getObfName())) {
54 throw new Error("Already have mapping for " + classMapping.getObfName());
55 }
56 boolean obfWasAdded = m_classesByObf.put(classMapping.getObfName(), classMapping) == null;
57 assert (obfWasAdded);
58 if (classMapping.getDeobfName() != null) {
59 if (m_classesByDeobf.containsKey(classMapping.getDeobfName())) {
60 throw new Error("Already have mapping for " + classMapping.getDeobfName());
61 }
62 boolean deobfWasAdded = m_classesByDeobf.put(classMapping.getDeobfName(), classMapping) == null;
63 assert (deobfWasAdded);
64 }
65 }
66
67 public void removeClassMapping(ClassMapping classMapping) {
68 boolean obfWasRemoved = m_classesByObf.remove(classMapping.getObfName()) != null;
69 assert (obfWasRemoved);
70 if (classMapping.getDeobfName() != null) {
71 boolean deobfWasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
72 assert (deobfWasRemoved);
73 }
74 }
75
76 public ClassMapping getClassByObf(ClassEntry entry) {
77 return getClassByObf(entry.getName());
78 }
79
80 public ClassMapping getClassByObf(String obfName) {
81 return m_classesByObf.get(obfName);
82 }
83
84 public ClassMapping getClassByDeobf(ClassEntry entry) {
85 return getClassByDeobf(entry.getName());
86 }
87
88 public ClassMapping getClassByDeobf(String deobfName) {
89 return m_classesByDeobf.get(deobfName);
90 }
91
92 public Translator getTranslator(TranslationDirection direction, TranslationIndex index) {
93 switch (direction) {
94 case Deobfuscating:
95
96 return new Translator(direction, m_classesByObf, index);
97
98 case Obfuscating:
99
100 // fill in the missing deobf class entries with obf entries
101 Map<String,ClassMapping> classes = Maps.newHashMap();
102 for (ClassMapping classMapping : classes()) {
103 if (classMapping.getDeobfName() != null) {
104 classes.put(classMapping.getDeobfName(), classMapping);
105 } else {
106 classes.put(classMapping.getObfName(), classMapping);
107 }
108 }
109
110 // translate the translation index
111 // NOTE: this isn't actually recursive
112 TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.Deobfuscating, index));
113
114 return new Translator(direction, classes, deobfIndex);
115
116 default:
117 throw new Error("Invalid translation direction!");
118 }
119 }
120
121 @Override
122 public String toString() {
123 StringBuilder buf = new StringBuilder();
124 for (ClassMapping classMapping : m_classesByObf.values()) {
125 buf.append(classMapping.toString());
126 buf.append("\n");
127 }
128 return buf.toString();
129 }
130
131 public void renameObfClass(String oldObfName, String newObfName) {
132 for (ClassMapping classMapping : new ArrayList<ClassMapping>(classes())) {
133 if (classMapping.renameObfClass(oldObfName, newObfName)) {
134 boolean wasRemoved = m_classesByObf.remove(oldObfName) != null;
135 assert (wasRemoved);
136 boolean wasAdded = m_classesByObf.put(newObfName, classMapping) == null;
137 assert (wasAdded);
138 }
139 }
140 }
141
142 public Set<String> getAllObfClassNames() {
143 final Set<String> classNames = Sets.newHashSet();
144 for (ClassMapping classMapping : classes()) {
145
146 // add the class name
147 classNames.add(classMapping.getObfName());
148
149 // add classes from method signatures
150 for (MethodMapping methodMapping : classMapping.methods()) {
151 for (Type type : methodMapping.getObfSignature().types()) {
152 if (type.hasClass()) {
153 classNames.add(type.getClassEntry().getClassName());
154 }
155 }
156 }
157 }
158 return classNames;
159 }
160
161 public boolean containsDeobfClass(String deobfName) {
162 return m_classesByDeobf.containsKey(deobfName);
163 }
164
165 public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName) {
166 ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
167 if (classMapping != null) {
168 return classMapping.containsDeobfField(deobfName);
169 }
170 return false;
171 }
172
173 public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, Signature deobfSignature) {
174 ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
175 if (classMapping != null) {
176 return classMapping.containsDeobfMethod(deobfName, deobfSignature);
177 }
178 return false;
179 }
180
181 public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) {
182 ClassMapping classMapping = m_classesByObf.get(obfBehaviorEntry.getClassName());
183 if (classMapping != null) {
184 return classMapping.containsArgument(obfBehaviorEntry, name);
185 }
186 return false;
187 }
188}
diff --git a/src/cuchaz/enigma/mapping/MappingsReader.java b/src/cuchaz/enigma/mapping/MappingsReader.java
new file mode 100644
index 0000000..adf460e
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingsReader.java
@@ -0,0 +1,175 @@
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;
21
22public class MappingsReader {
23
24 public Mappings read(Reader in) throws IOException, MappingParseException {
25 return read(new BufferedReader(in));
26 }
27
28 public Mappings read(BufferedReader in) throws IOException, MappingParseException {
29 Mappings mappings = new Mappings();
30 Deque<Object> mappingStack = Queues.newArrayDeque();
31
32 int lineNumber = 0;
33 String line = null;
34 while ( (line = in.readLine()) != null) {
35 lineNumber++;
36
37 // strip comments
38 int commentPos = line.indexOf('#');
39 if (commentPos >= 0) {
40 line = line.substring(0, commentPos);
41 }
42
43 // skip blank lines
44 if (line.trim().length() <= 0) {
45 continue;
46 }
47
48 // get the indent of this line
49 int indent = 0;
50 for (int i = 0; i < line.length(); i++) {
51 if (line.charAt(i) != '\t') {
52 break;
53 }
54 indent++;
55 }
56
57 // handle stack pops
58 while (indent < mappingStack.size()) {
59 mappingStack.pop();
60 }
61
62 String[] parts = line.trim().split("\\s");
63 try {
64 // read the first token
65 String token = parts[0];
66
67 if (token.equalsIgnoreCase("CLASS")) {
68 ClassMapping classMapping;
69 if (indent == 0) {
70 // outer class
71 classMapping = readClass(parts, false);
72 mappings.addClassMapping(classMapping);
73 } else if (indent == 1) {
74 // inner class
75 if (! (mappingStack.getFirst() instanceof ClassMapping)) {
76 throw new MappingParseException(lineNumber, "Unexpected CLASS entry here!");
77 }
78
79 classMapping = readClass(parts, true);
80 ((ClassMapping)mappingStack.getFirst()).addInnerClassMapping(classMapping);
81 } else {
82 throw new MappingParseException(lineNumber, "Unexpected CLASS entry nesting!");
83 }
84 mappingStack.push(classMapping);
85 } else if (token.equalsIgnoreCase("FIELD")) {
86 if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof ClassMapping)) {
87 throw new MappingParseException(lineNumber, "Unexpected FIELD entry here!");
88 }
89 ((ClassMapping)mappingStack.getFirst()).addFieldMapping(readField(parts));
90 } else if (token.equalsIgnoreCase("METHOD")) {
91 if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof ClassMapping)) {
92 throw new MappingParseException(lineNumber, "Unexpected METHOD entry here!");
93 }
94 MethodMapping methodMapping = readMethod(parts);
95 ((ClassMapping)mappingStack.getFirst()).addMethodMapping(methodMapping);
96 mappingStack.push(methodMapping);
97 } else if (token.equalsIgnoreCase("ARG")) {
98 if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof MethodMapping)) {
99 throw new MappingParseException(lineNumber, "Unexpected ARG entry here!");
100 }
101 ((MethodMapping)mappingStack.getFirst()).addArgumentMapping(readArgument(parts));
102 }
103 } catch (ArrayIndexOutOfBoundsException | NumberFormatException ex) {
104 throw new MappingParseException(lineNumber, "Malformed line!");
105 }
106 }
107
108 return mappings;
109 }
110
111 private ArgumentMapping readArgument(String[] parts) {
112 return new ArgumentMapping(Integer.parseInt(parts[1]), parts[2]);
113 }
114
115 private ClassMapping readClass(String[] parts, boolean makeSimple) {
116 if (parts.length == 2) {
117 String obfName = processName(parts[1], makeSimple);
118 return new ClassMapping(obfName);
119 } else {
120 String obfName = processName(parts[1], makeSimple);
121 String deobfName = processName(parts[2], makeSimple);
122 return new ClassMapping(obfName, deobfName);
123 }
124 }
125
126 private String processName(String name, boolean makeSimple) {
127 if (makeSimple) {
128 return new ClassEntry(name).getSimpleName();
129 } else {
130 return moveClassOutOfDefaultPackage(name, Constants.NonePackage);
131 }
132 }
133
134 private String moveClassOutOfDefaultPackage(String className, String newPackageName) {
135 ClassEntry classEntry = new ClassEntry(className);
136 if (classEntry.isInDefaultPackage()) {
137 return newPackageName + "/" + classEntry.getName();
138 }
139 return className;
140 }
141
142 private FieldMapping readField(String[] parts) {
143 return new FieldMapping(parts[1], parts[2]);
144 }
145
146 private MethodMapping readMethod(String[] parts) {
147 if (parts.length == 3) {
148 String obfName = parts[1];
149 Signature obfSignature = moveSignatureOutOfDefaultPackage(new Signature(parts[2]), Constants.NonePackage);
150 return new MethodMapping(obfName, obfSignature);
151 } else {
152 String obfName = parts[1];
153 String deobfName = parts[2];
154 Signature obfSignature = moveSignatureOutOfDefaultPackage(new Signature(parts[3]), Constants.NonePackage);
155 if (obfName.equals(deobfName)) {
156 return new MethodMapping(obfName, obfSignature);
157 } else {
158 return new MethodMapping(obfName, obfSignature, deobfName);
159 }
160 }
161 }
162
163 private Signature moveSignatureOutOfDefaultPackage(Signature signature, final String newPackageName) {
164 return new Signature(signature, new ClassNameReplacer() {
165 @Override
166 public String replace(String className) {
167 ClassEntry classEntry = new ClassEntry(className);
168 if (classEntry.isInDefaultPackage()) {
169 return newPackageName + "/" + className;
170 }
171 return null;
172 }
173 });
174 }
175}
diff --git a/src/cuchaz/enigma/mapping/MappingsRenamer.java b/src/cuchaz/enigma/mapping/MappingsRenamer.java
new file mode 100644
index 0000000..0a41c2b
--- /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 Signature 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..057e02b
--- /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 Signature m_signature;
24
25 public MethodEntry(ClassEntry classEntry, String name, Signature signature) {
26 if (classEntry == null) {
27 throw new IllegalArgumentException("Class cannot be null!");
28 }
29 if (name == null) {
30 throw new IllegalArgumentException("Method name cannot be null!");
31 }
32 if (signature == null) {
33 throw new IllegalArgumentException("Method signature cannot be null!");
34 }
35 if (name.startsWith("<")) {
36 throw new IllegalArgumentException("Don't use MethodEntry for a constructor!");
37 }
38
39 m_classEntry = classEntry;
40 m_name = name;
41 m_signature = signature;
42 }
43
44 public MethodEntry(MethodEntry other) {
45 m_classEntry = new ClassEntry(other.m_classEntry);
46 m_name = other.m_name;
47 m_signature = other.m_signature;
48 }
49
50 public MethodEntry(MethodEntry other, String newClassName) {
51 m_classEntry = new ClassEntry(newClassName);
52 m_name = other.m_name;
53 m_signature = other.m_signature;
54 }
55
56 @Override
57 public ClassEntry getClassEntry() {
58 return m_classEntry;
59 }
60
61 @Override
62 public String getName() {
63 return m_name;
64 }
65
66 @Override
67 public Signature getSignature() {
68 return m_signature;
69 }
70
71 @Override
72 public String getClassName() {
73 return m_classEntry.getName();
74 }
75
76 @Override
77 public MethodEntry cloneToNewClass(ClassEntry classEntry) {
78 return new MethodEntry(this, classEntry.getName());
79 }
80
81 @Override
82 public int hashCode() {
83 return Util.combineHashesOrdered(m_classEntry, m_name, m_signature);
84 }
85
86 @Override
87 public boolean equals(Object other) {
88 if (other instanceof MethodEntry) {
89 return equals((MethodEntry)other);
90 }
91 return false;
92 }
93
94 public boolean equals(MethodEntry other) {
95 return m_classEntry.equals(other.m_classEntry)
96 && m_name.equals(other.m_name)
97 && m_signature.equals(other.m_signature);
98 }
99
100 @Override
101 public String toString() {
102 return m_classEntry.getName() + "." + m_name + m_signature;
103 }
104}
diff --git a/src/cuchaz/enigma/mapping/MethodMapping.java b/src/cuchaz/enigma/mapping/MethodMapping.java
new file mode 100644
index 0000000..1704428
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MethodMapping.java
@@ -0,0 +1,161 @@
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
17public class MethodMapping implements Serializable, Comparable<MethodMapping> {
18
19 private static final long serialVersionUID = -4409570216084263978L;
20
21 private String m_obfName;
22 private String m_deobfName;
23 private Signature m_obfSignature;
24 private Map<Integer,ArgumentMapping> m_arguments;
25
26 public MethodMapping(String obfName, Signature obfSignature) {
27 this(obfName, obfSignature, null);
28 }
29
30 public MethodMapping(String obfName, Signature obfSignature, String deobfName) {
31 if (obfName == null) {
32 throw new IllegalArgumentException("obf name cannot be null!");
33 }
34 if (obfSignature == null) {
35 throw new IllegalArgumentException("obf signature cannot be null!");
36 }
37 m_obfName = obfName;
38 m_deobfName = NameValidator.validateMethodName(deobfName);
39 m_obfSignature = obfSignature;
40 m_arguments = new TreeMap<Integer,ArgumentMapping>();
41 }
42
43 public String getObfName() {
44 return m_obfName;
45 }
46
47 public String getDeobfName() {
48 return m_deobfName;
49 }
50
51 public void setDeobfName(String val) {
52 m_deobfName = NameValidator.validateMethodName(val);
53 }
54
55 public Signature getObfSignature() {
56 return m_obfSignature;
57 }
58
59 public Iterable<ArgumentMapping> arguments() {
60 return m_arguments.values();
61 }
62
63 public boolean isConstructor() {
64 return m_obfName.startsWith("<");
65 }
66
67 public void addArgumentMapping(ArgumentMapping argumentMapping) {
68 boolean wasAdded = m_arguments.put(argumentMapping.getIndex(), argumentMapping) == null;
69 assert (wasAdded);
70 }
71
72 public String getObfArgumentName(int index) {
73 ArgumentMapping argumentMapping = m_arguments.get(index);
74 if (argumentMapping != null) {
75 return argumentMapping.getName();
76 }
77
78 return null;
79 }
80
81 public String getDeobfArgumentName(int index) {
82 ArgumentMapping argumentMapping = m_arguments.get(index);
83 if (argumentMapping != null) {
84 return argumentMapping.getName();
85 }
86
87 return null;
88 }
89
90 public void setArgumentName(int index, String name) {
91 ArgumentMapping argumentMapping = m_arguments.get(index);
92 if (argumentMapping == null) {
93 argumentMapping = new ArgumentMapping(index, name);
94 boolean wasAdded = m_arguments.put(index, argumentMapping) == null;
95 assert (wasAdded);
96 } else {
97 argumentMapping.setName(name);
98 }
99 }
100
101 public void removeArgumentName(int index) {
102 boolean wasRemoved = m_arguments.remove(index) != null;
103 assert (wasRemoved);
104 }
105
106 @Override
107 public String toString() {
108 StringBuilder buf = new StringBuilder();
109 buf.append("\t");
110 buf.append(m_obfName);
111 buf.append(" <-> ");
112 buf.append(m_deobfName);
113 buf.append("\n");
114 buf.append("\t");
115 buf.append(m_obfSignature);
116 buf.append("\n");
117 buf.append("\tArguments:\n");
118 for (ArgumentMapping argumentMapping : m_arguments.values()) {
119 buf.append("\t\t");
120 buf.append(argumentMapping.getIndex());
121 buf.append(" -> ");
122 buf.append(argumentMapping.getName());
123 buf.append("\n");
124 }
125 return buf.toString();
126 }
127
128 @Override
129 public int compareTo(MethodMapping other) {
130 return (m_obfName + m_obfSignature).compareTo(other.m_obfName + other.m_obfSignature);
131 }
132
133 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
134
135 // rename obf classes in the signature
136 Signature newSignature = new Signature(m_obfSignature, new ClassNameReplacer() {
137 @Override
138 public String replace(String className) {
139 if (className.equals(oldObfClassName)) {
140 return newObfClassName;
141 }
142 return null;
143 }
144 });
145
146 if (!newSignature.equals(m_obfSignature)) {
147 m_obfSignature = newSignature;
148 return true;
149 }
150 return false;
151 }
152
153 public boolean containsArgument(String name) {
154 for (ArgumentMapping argumentMapping : m_arguments.values()) {
155 if (argumentMapping.getName().equals(name)) {
156 return true;
157 }
158 }
159 return false;
160 }
161}
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/Signature.java b/src/cuchaz/enigma/mapping/Signature.java
new file mode 100644
index 0000000..ff7f807
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Signature.java
@@ -0,0 +1,109 @@
1package cuchaz.enigma.mapping;
2
3import java.util.List;
4
5import com.beust.jcommander.internal.Lists;
6
7import cuchaz.enigma.Util;
8
9public class Signature {
10
11 private List<Type> m_argumentTypes;
12 private Type m_returnType;
13
14 public Signature(String signature) {
15 try {
16 m_argumentTypes = Lists.newArrayList();
17 int i=0;
18 while (i<signature.length()) {
19 char c = signature.charAt(i);
20 if (c == '(') {
21 assert(m_argumentTypes.isEmpty());
22 assert(m_returnType == null);
23 i++;
24 } else if (c == ')') {
25 i++;
26 break;
27 } else {
28 String type = Type.parseFirst(signature.substring(i));
29 m_argumentTypes.add(new Type(type));
30 i += type.length();
31 }
32 }
33 m_returnType = new Type(Type.parseFirst(signature.substring(i)));
34 } catch (Exception ex) {
35 throw new IllegalArgumentException("Unable to parse signature: " + signature, ex);
36 }
37 }
38
39 public Signature(Signature other, ClassNameReplacer replacer) {
40 m_argumentTypes = Lists.newArrayList(other.m_argumentTypes);
41 for (int i=0; i<m_argumentTypes.size(); i++) {
42 m_argumentTypes.set(i, new Type(m_argumentTypes.get(i), replacer));
43 }
44 m_returnType = new Type(other.m_returnType, replacer);
45 }
46
47 public List<Type> getArgumentTypes() {
48 return m_argumentTypes;
49 }
50
51 public Type getReturnType() {
52 return m_returnType;
53 }
54
55 @Override
56 public String toString() {
57 StringBuilder buf = new StringBuilder();
58 buf.append("(");
59 for (Type type : m_argumentTypes) {
60 buf.append(type.toString());
61 }
62 buf.append(")");
63 buf.append(m_returnType.toString());
64 return buf.toString();
65 }
66
67 public Iterable<Type> types() {
68 List<Type> types = Lists.newArrayList();
69 types.addAll(m_argumentTypes);
70 types.add(m_returnType);
71 return types;
72 }
73
74 public Iterable<ClassEntry> classes() {
75 List<ClassEntry> out = Lists.newArrayList();
76 for (Type type : types()) {
77 if (type.isClass()) {
78 out.add(type.getClassEntry());
79 }
80 }
81 return out;
82 }
83
84 @Override
85 public boolean equals(Object other) {
86 if (other instanceof Signature) {
87 return equals((Signature)other);
88 }
89 return false;
90 }
91
92 public boolean equals(Signature other) {
93 return m_argumentTypes.equals(other.m_argumentTypes) && m_returnType.equals(other.m_returnType);
94 }
95
96 @Override
97 public int hashCode() {
98 return Util.combineHashesOrdered(m_argumentTypes.hashCode(), m_returnType.hashCode());
99 }
100
101 public boolean hasClass(ClassEntry classEntry) {
102 for (Type type : types()) {
103 if (type.hasClass() && type.getClassEntry().equals(classEntry)) {
104 return true;
105 }
106 }
107 return false;
108 }
109}
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..5eba18c
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Translator.java
@@ -0,0 +1,239 @@
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;
18
19public class Translator {
20
21 private TranslationDirection m_direction;
22 private Map<String,ClassMapping> m_classes;
23 private TranslationIndex m_index;
24
25 public Translator() {
26 m_direction = null;
27 m_classes = Maps.newHashMap();
28 }
29
30 public Translator(TranslationDirection direction, Map<String,ClassMapping> classes, TranslationIndex index) {
31 m_direction = direction;
32 m_classes = classes;
33 m_index = index;
34 }
35
36 @SuppressWarnings("unchecked")
37 public <T extends Entry> T translateEntry(T entry) {
38 if (entry instanceof ClassEntry) {
39 return (T)translateEntry((ClassEntry)entry);
40 } else if (entry instanceof FieldEntry) {
41 return (T)translateEntry((FieldEntry)entry);
42 } else if (entry instanceof MethodEntry) {
43 return (T)translateEntry((MethodEntry)entry);
44 } else if (entry instanceof ConstructorEntry) {
45 return (T)translateEntry((ConstructorEntry)entry);
46 } else if (entry instanceof ArgumentEntry) {
47 return (T)translateEntry((ArgumentEntry)entry);
48 } else {
49 throw new Error("Unknown entry type: " + entry.getClass().getName());
50 }
51 }
52
53 public String translateClass(String className) {
54 return translate(new ClassEntry(className));
55 }
56
57 public String translate(ClassEntry in) {
58 ClassMapping classMapping = m_classes.get(in.getOuterClassName());
59 if (classMapping != null) {
60 if (in.isInnerClass()) {
61 // translate the inner class
62 String translatedInnerClassName = m_direction.choose(
63 classMapping.getDeobfInnerClassName(in.getInnerClassName()),
64 classMapping.getObfInnerClassName(in.getInnerClassName())
65 );
66 if (translatedInnerClassName != null) {
67 // try to translate the outer name
68 String translatedOuterClassName = m_direction.choose(classMapping.getDeobfName(), classMapping.getObfName());
69 if (translatedOuterClassName != null) {
70 return translatedOuterClassName + "$" + translatedInnerClassName;
71 } else {
72 return in.getOuterClassName() + "$" + translatedInnerClassName;
73 }
74 }
75 } else {
76 // just return outer
77 return m_direction.choose(classMapping.getDeobfName(), classMapping.getObfName());
78 }
79 }
80 return null;
81 }
82
83 public ClassEntry translateEntry(ClassEntry in) {
84
85 // can we translate the inner class?
86 String name = translate(in);
87 if (name != null) {
88 return new ClassEntry(name);
89 }
90
91 if (in.isInnerClass()) {
92
93 // guess not. just translate the outer class name then
94 String outerClassName = translate(in.getOuterClassEntry());
95 if (outerClassName != null) {
96 return new ClassEntry(outerClassName + "$" + in.getInnerClassName());
97 }
98 }
99
100 return in;
101 }
102
103 public String translate(FieldEntry in) {
104
105 // resolve the class entry
106 ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in);
107 if (resolvedClassEntry != null) {
108
109 // look for the class
110 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
111 if (classMapping != null) {
112
113 // look for the field
114 String translatedName = m_direction.choose(
115 classMapping.getDeobfFieldName(in.getName()),
116 classMapping.getObfFieldName(in.getName())
117 );
118 if (translatedName != null) {
119 return translatedName;
120 }
121 }
122 }
123 return null;
124 }
125
126 public FieldEntry translateEntry(FieldEntry in) {
127 String name = translate(in);
128 if (name == null) {
129 name = in.getName();
130 }
131 return new FieldEntry(translateEntry(in.getClassEntry()), name);
132 }
133
134 public String translate(MethodEntry in) {
135
136 // resolve the class entry
137 ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in);
138 if (resolvedClassEntry != null) {
139
140 // look for class
141 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
142 if (classMapping != null) {
143
144 // look for the method
145 MethodMapping methodMapping = m_direction.choose(
146 classMapping.getMethodByObf(in.getName(), in.getSignature()),
147 classMapping.getMethodByDeobf(in.getName(), translateSignature(in.getSignature()))
148 );
149 if (methodMapping != null) {
150 return m_direction.choose(methodMapping.getDeobfName(), methodMapping.getObfName());
151 }
152 }
153 }
154 return null;
155 }
156
157 public MethodEntry translateEntry(MethodEntry in) {
158 String name = translate(in);
159 if (name == null) {
160 name = in.getName();
161 }
162 return new MethodEntry(translateEntry(in.getClassEntry()), name, translateSignature(in.getSignature()));
163 }
164
165 public ConstructorEntry translateEntry(ConstructorEntry in) {
166 if (in.isStatic()) {
167 return new ConstructorEntry(translateEntry(in.getClassEntry()));
168 } else {
169 return new ConstructorEntry(translateEntry(in.getClassEntry()), translateSignature(in.getSignature()));
170 }
171 }
172
173 public BehaviorEntry translateEntry(BehaviorEntry in) {
174 if (in instanceof MethodEntry) {
175 return translateEntry((MethodEntry)in);
176 } else if (in instanceof ConstructorEntry) {
177 return translateEntry((ConstructorEntry)in);
178 }
179 throw new Error("Wrong entry type!");
180 }
181
182 public String translate(ArgumentEntry in) {
183
184 // look for the class
185 ClassMapping classMapping = findClassMapping(in.getClassEntry());
186 if (classMapping != null) {
187
188 // look for the method
189 MethodMapping methodMapping = m_direction.choose(
190 classMapping.getMethodByObf(in.getMethodName(), in.getMethodSignature()),
191 classMapping.getMethodByDeobf(in.getMethodName(), translateSignature(in.getMethodSignature()))
192 );
193 if (methodMapping != null) {
194 return m_direction.choose(
195 methodMapping.getDeobfArgumentName(in.getIndex()),
196 methodMapping.getObfArgumentName(in.getIndex())
197 );
198 }
199 }
200 return null;
201 }
202
203 public ArgumentEntry translateEntry(ArgumentEntry in) {
204 String name = translate(in);
205 if (name == null) {
206 name = in.getName();
207 }
208 return new ArgumentEntry(translateEntry(in.getBehaviorEntry()), in.getIndex(), name);
209 }
210
211 public Type translateType(Type type) {
212 return new Type(type, new ClassNameReplacer() {
213 @Override
214 public String replace(String className) {
215 return translateClass(className);
216 }
217 });
218 }
219
220 public Signature translateSignature(Signature signature) {
221 return new Signature(signature, new ClassNameReplacer() {
222 @Override
223 public String replace(String className) {
224 return translateClass(className);
225 }
226 });
227 }
228
229 private ClassMapping findClassMapping(ClassEntry classEntry) {
230 ClassMapping classMapping = m_classes.get(classEntry.getOuterClassName());
231 if (classMapping != null && classEntry.isInnerClass()) {
232 classMapping = m_direction.choose(
233 classMapping.getInnerClassByObf(classEntry.getInnerClassName()),
234 classMapping.getInnerClassByDeobfThenObf(classEntry.getInnerClassName())
235 );
236 }
237 return classMapping;
238 }
239}
diff --git a/src/cuchaz/enigma/mapping/Type.java b/src/cuchaz/enigma/mapping/Type.java
new file mode 100644
index 0000000..9f5d52f
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Type.java
@@ -0,0 +1,218 @@
1package cuchaz.enigma.mapping;
2
3import java.util.Map;
4
5import com.google.common.collect.Maps;
6
7public class Type {
8
9 public enum Primitive {
10 Byte('B'),
11 Character('C'),
12 Short('S'),
13 Integer('I'),
14 Long('J'),
15 Float('F'),
16 Double('D'),
17 Boolean('Z');
18
19 private static final Map<Character,Primitive> m_lookup;
20
21 static {
22 m_lookup = Maps.newTreeMap();
23 for (Primitive val : values()) {
24 m_lookup.put(val.getCode(), val);
25 }
26 }
27
28 public static Primitive get(char code) {
29 return m_lookup.get(code);
30 }
31
32 private char m_code;
33
34 private Primitive(char code) {
35 m_code = code;
36 }
37
38 public char getCode() {
39 return m_code;
40 }
41 }
42
43 public static String parseFirst(String in) {
44
45 if (in == null || in.length() <= 0) {
46 throw new IllegalArgumentException("No type to parse, input is empty!");
47 }
48
49 // read one type from the input
50
51 char c = in.charAt(0);
52
53 // first check for void
54 if (c == 'V') {
55 return "V";
56 }
57
58 // then check for primitives
59 Primitive primitive = Primitive.get(c);
60 if (primitive != null) {
61 return in.substring(0, 1);
62 }
63
64 // then check for classes
65 if (c == 'L') {
66 return readClass(in);
67 }
68
69 // then check for arrays
70 int dim = countArrayDimension(in);
71 if (dim > 0) {
72 String arrayType = Type.parseFirst(in.substring(dim));
73 return in.substring(0, dim + arrayType.length());
74 }
75
76 throw new IllegalArgumentException("don't know how to parse: " + in);
77 }
78
79 private String m_name;
80
81 public Type(String name) {
82 m_name = name;
83 }
84
85 public Type(ClassEntry classEntry) {
86 m_name = "L" + classEntry.getClassName() + ";";
87 }
88
89 public Type(Type type, ClassNameReplacer replacer) {
90 m_name = type.m_name;
91 if (type.isClass()) {
92 String replacedName = replacer.replace(type.getClassEntry().getClassName());
93 if (replacedName != null) {
94 m_name = "L" + replacedName + ";";
95 }
96 } else if (type.isArray() && type.hasClass()) {
97 String replacedName = replacer.replace(type.getClassEntry().getClassName());
98 if (replacedName != null) {
99 m_name = Type.getArrayPrefix(type.getArrayDimension()) + "L" + replacedName + ";";
100 }
101 }
102 }
103
104 @Override
105 public String toString() {
106 return m_name;
107 }
108
109 public boolean isVoid() {
110 return m_name.length() == 1 && m_name.charAt(0) == 'V';
111 }
112
113 public boolean isPrimitive() {
114 return m_name.length() == 1 && Primitive.get(m_name.charAt(0)) != null;
115 }
116
117 public Primitive getPrimitive() {
118 if (!isPrimitive()) {
119 throw new IllegalStateException("not a primitive");
120 }
121 return Primitive.get(m_name.charAt(0));
122 }
123
124 public boolean isClass() {
125 return m_name.charAt(0) == 'L' && m_name.charAt(m_name.length() - 1) == ';';
126 }
127
128 public ClassEntry getClassEntry() {
129 if (isClass()) {
130 String name = m_name.substring(1, m_name.length() - 1);
131
132 int pos = name.indexOf('<');
133 if (pos >= 0) {
134 // remove the parameters from the class name
135 name = name.substring(0, pos);
136 }
137
138 return new ClassEntry(name);
139
140 } else if (isArray() && getArrayType().isClass()) {
141 return getArrayType().getClassEntry();
142 } else {
143 throw new IllegalStateException("type doesn't have a class");
144 }
145 }
146
147 public boolean isArray() {
148 return m_name.charAt(0) == '[';
149 }
150
151 public int getArrayDimension() {
152 if (!isArray()) {
153 throw new IllegalStateException("not an array");
154 }
155 return countArrayDimension(m_name);
156 }
157
158 public Type getArrayType() {
159 if (!isArray()) {
160 throw new IllegalStateException("not an array");
161 }
162 return new Type(m_name.substring(getArrayDimension(), m_name.length()));
163 }
164
165 private static String getArrayPrefix(int dimension) {
166 StringBuilder buf = new StringBuilder();
167 for (int i=0; i<dimension; i++) {
168 buf.append("[");
169 }
170 return buf.toString();
171 }
172
173 public boolean hasClass() {
174 return isClass() || (isArray() && getArrayType().hasClass());
175 }
176
177 @Override
178 public boolean equals(Object other) {
179 if (other instanceof Type) {
180 return equals((Type)other);
181 }
182 return false;
183 }
184
185 public boolean equals(Type other) {
186 return m_name.equals(other.m_name);
187 }
188
189 public int hashCode() {
190 return m_name.hashCode();
191 }
192
193 private static int countArrayDimension(String in) {
194 int i=0;
195 for(; i < in.length() && in.charAt(i) == '['; i++);
196 return i;
197 }
198
199 private static String readClass(String in) {
200 // read all the characters in the buffer until we hit a ';'
201 // remember to treat parameters correctly
202 StringBuilder buf = new StringBuilder();
203 int depth = 0;
204 for (int i=0; i<in.length(); i++) {
205 char c = in.charAt(i);
206 buf.append(c);
207
208 if (c == '<') {
209 depth++;
210 } else if (c == '>') {
211 depth--;
212 } else if (depth == 0 && c == ';') {
213 return buf.toString();
214 }
215 }
216 return null;
217 }
218}
diff --git a/test/cuchaz/enigma/EntryFactory.java b/test/cuchaz/enigma/EntryFactory.java
new file mode 100644
index 0000000..fa90779
--- /dev/null
+++ b/test/cuchaz/enigma/EntryFactory.java
@@ -0,0 +1,67 @@
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;
20import cuchaz.enigma.mapping.Signature;
21
22public class EntryFactory {
23
24 public static ClassEntry newClass(String name) {
25 return new ClassEntry(name);
26 }
27
28 public static FieldEntry newField(String className, String fieldName) {
29 return newField(newClass(className), fieldName);
30 }
31
32 public static FieldEntry newField(ClassEntry classEntry, String fieldName) {
33 return new FieldEntry(classEntry, fieldName);
34 }
35
36 public static MethodEntry newMethod(String className, String methodName, String methodSignature) {
37 return newMethod(newClass(className), methodName, methodSignature);
38 }
39
40 public static MethodEntry newMethod(ClassEntry classEntry, String methodName, String methodSignature) {
41 return new MethodEntry(classEntry, methodName, new Signature(methodSignature));
42 }
43
44 public static ConstructorEntry newConstructor(String className, String signature) {
45 return newConstructor(newClass(className), signature);
46 }
47
48 public static ConstructorEntry newConstructor(ClassEntry classEntry, String signature) {
49 return new ConstructorEntry(classEntry, new Signature(signature));
50 }
51
52 public static EntryReference<FieldEntry,BehaviorEntry> newFieldReferenceByMethod(FieldEntry fieldEntry, String callerClassName, String callerName, String callerSignature) {
53 return new EntryReference<FieldEntry,BehaviorEntry>(fieldEntry, "", newMethod(callerClassName, callerName, callerSignature));
54 }
55
56 public static EntryReference<FieldEntry,BehaviorEntry> newFieldReferenceByConstructor(FieldEntry fieldEntry, String callerClassName, String callerSignature) {
57 return new EntryReference<FieldEntry,BehaviorEntry>(fieldEntry, "", newConstructor(callerClassName, callerSignature));
58 }
59
60 public static EntryReference<BehaviorEntry,BehaviorEntry> newBehaviorReferenceByMethod(BehaviorEntry behaviorEntry, String callerClassName, String callerName, String callerSignature) {
61 return new EntryReference<BehaviorEntry,BehaviorEntry>(behaviorEntry, "", newMethod(callerClassName, callerName, callerSignature));
62 }
63
64 public static EntryReference<BehaviorEntry,BehaviorEntry> newBehaviorReferenceByConstructor(BehaviorEntry behaviorEntry, String callerClassName, String callerSignature) {
65 return new EntryReference<BehaviorEntry,BehaviorEntry>(behaviorEntry, "", newConstructor(callerClassName, callerSignature));
66 }
67}
diff --git a/test/cuchaz/enigma/TestDeobfuscator.java b/test/cuchaz/enigma/TestDeobfuscator.java
new file mode 100644
index 0000000..26d492d
--- /dev/null
+++ b/test/cuchaz/enigma/TestDeobfuscator.java
@@ -0,0 +1,58 @@
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()
29 throws IOException {
30 return new Deobfuscator(new JarFile("build/testLoneClass.obf.jar"));
31 }
32
33 @Test
34 public void loadJar()
35 throws Exception {
36 getDeobfuscator();
37 }
38
39 @Test
40 public void getClasses()
41 throws Exception {
42 Deobfuscator deobfuscator = getDeobfuscator();
43 List<ClassEntry> obfClasses = Lists.newArrayList();
44 List<ClassEntry> deobfClasses = Lists.newArrayList();
45 deobfuscator.getSeparatedClasses(obfClasses, deobfClasses);
46 assertEquals(1, obfClasses.size());
47 assertEquals("none/a", obfClasses.get(0).getName());
48 assertEquals(1, deobfClasses.size());
49 assertEquals("cuchaz/enigma/inputs/Keep", deobfClasses.get(0).getName());
50 }
51
52 @Test
53 public void decompileClass()
54 throws Exception {
55 Deobfuscator deobfuscator = getDeobfuscator();
56 deobfuscator.getSource(deobfuscator.getSourceTree("none/a"));
57 }
58}
diff --git a/test/cuchaz/enigma/TestInnerClasses.java b/test/cuchaz/enigma/TestInnerClasses.java
new file mode 100644
index 0000000..2e16a33
--- /dev/null
+++ b/test/cuchaz/enigma/TestInnerClasses.java
@@ -0,0 +1,90 @@
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()
40 throws Exception {
41 m_index = new JarIndex();
42 JarFile jar = new JarFile("build/testInnerClasses.obf.jar");
43 m_index.indexJar(jar, true);
44 m_deobfuscator = new Deobfuscator(jar);
45 }
46
47 @Test
48 public void simple() {
49 assertThat(m_index.getOuterClass(SimpleInner), is(SimpleOuter));
50 assertThat(m_index.getInnerClasses(SimpleOuter), containsInAnyOrder(SimpleInner));
51 assertThat(m_index.isAnonymousClass(SimpleInner), is(false));
52 decompile(SimpleOuter);
53 }
54
55 @Test
56 public void anonymous() {
57 assertThat(m_index.getOuterClass(AnonymousInner), is(AnonymousOuter));
58 assertThat(m_index.getInnerClasses(AnonymousOuter), containsInAnyOrder(AnonymousInner));
59 assertThat(m_index.isAnonymousClass(AnonymousInner), is(true));
60 decompile(AnonymousOuter);
61 }
62
63 @Test
64 public void constructorArgs() {
65 assertThat(m_index.getOuterClass(ConstructorArgsInner), is(ConstructorArgsOuter));
66 assertThat(m_index.getInnerClasses(ConstructorArgsOuter), containsInAnyOrder(ConstructorArgsInner));
67 assertThat(m_index.isAnonymousClass(ConstructorArgsInner), is(false));
68 decompile(ConstructorArgsOuter);
69 }
70
71 @Test
72 public void anonymousWithScopeArgs() {
73 assertThat(m_index.getOuterClass(AnonymousWithScopeArgsInner), is(AnonymousWithScopeArgsOuter));
74 assertThat(m_index.getInnerClasses(AnonymousWithScopeArgsOuter), containsInAnyOrder(AnonymousWithScopeArgsInner));
75 assertThat(m_index.isAnonymousClass(AnonymousWithScopeArgsInner), is(true));
76 decompile(AnonymousWithScopeArgsOuter);
77 }
78
79 @Test
80 public void anonymousWithOuterAccess() {
81 assertThat(m_index.getOuterClass(AnonymousWithOuterAccessInner), is(AnonymousWithOuterAccessOuter));
82 assertThat(m_index.getInnerClasses(AnonymousWithOuterAccessOuter), containsInAnyOrder(AnonymousWithOuterAccessInner));
83 assertThat(m_index.isAnonymousClass(AnonymousWithOuterAccessInner), is(true));
84 decompile(AnonymousWithOuterAccessOuter);
85 }
86
87 private void decompile(String name) {
88 m_deobfuscator.getSourceTree(name);
89 }
90}
diff --git a/test/cuchaz/enigma/TestJarIndexConstructorReferences.java b/test/cuchaz/enigma/TestJarIndexConstructorReferences.java
new file mode 100644
index 0000000..22812fe
--- /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;
27
28public class TestJarIndexConstructorReferences {
29
30 private JarIndex m_index;
31
32 private ClassEntry m_baseClass = newClass("none/a");
33 private ClassEntry m_subClass = newClass("none/d");
34 private ClassEntry m_subsubClass = newClass("none/e");
35 private ClassEntry m_defaultClass = newClass("none/c");
36 private ClassEntry m_callerClass = newClass("none/b");
37
38 public TestJarIndexConstructorReferences()
39 throws Exception {
40 File jarFile = new File("build/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 = newConstructor(m_baseClass, "()V");
54 Collection<EntryReference<BehaviorEntry,BehaviorEntry>> references = m_index.getBehaviorReferences(source);
55 assertThat(references, containsInAnyOrder(
56 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "a", "()V"),
57 newBehaviorReferenceByConstructor(source, m_subClass.getName(), "()V"),
58 newBehaviorReferenceByConstructor(source, m_subClass.getName(), "(III)V")
59 ));
60 }
61
62 @Test
63 @SuppressWarnings("unchecked")
64 public void baseInt() {
65 BehaviorEntry source = newConstructor(m_baseClass, "(I)V");
66 assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder(
67 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "b", "()V")
68 ));
69 }
70
71 @Test
72 @SuppressWarnings("unchecked")
73 public void subDefault() {
74 BehaviorEntry source = newConstructor(m_subClass, "()V");
75 assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder(
76 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "c", "()V"),
77 newBehaviorReferenceByConstructor(source, m_subClass.getName(), "(I)V")
78 ));
79 }
80
81 @Test
82 @SuppressWarnings("unchecked")
83 public void subInt() {
84 BehaviorEntry source = newConstructor(m_subClass, "(I)V");
85 assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder(
86 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "d", "()V"),
87 newBehaviorReferenceByConstructor(source, m_subClass.getName(), "(II)V"),
88 newBehaviorReferenceByConstructor(source, m_subsubClass.getName(), "(I)V")
89 ));
90 }
91
92 @Test
93 @SuppressWarnings("unchecked")
94 public void subIntInt() {
95 BehaviorEntry source = newConstructor(m_subClass, "(II)V");
96 assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder(
97 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "e", "()V")
98 ));
99 }
100
101 @Test
102 public void subIntIntInt() {
103 BehaviorEntry source = newConstructor(m_subClass, "(III)V");
104 assertThat(m_index.getBehaviorReferences(source), is(empty()));
105 }
106
107 @Test
108 @SuppressWarnings("unchecked")
109 public void subsubInt() {
110 BehaviorEntry source = newConstructor(m_subsubClass, "(I)V");
111 assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder(
112 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "f", "()V")
113 ));
114 }
115
116 @Test
117 @SuppressWarnings("unchecked")
118 public void defaultConstructable() {
119 BehaviorEntry source = newConstructor(m_defaultClass, "()V");
120 assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder(
121 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "g", "()V")
122 ));
123 }
124}
diff --git a/test/cuchaz/enigma/TestJarIndexInheritanceTree.java b/test/cuchaz/enigma/TestJarIndexInheritanceTree.java
new file mode 100644
index 0000000..1d6e5a5
--- /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.FieldEntry;
30import cuchaz.enigma.mapping.MethodEntry;
31
32public class TestJarIndexInheritanceTree {
33
34 private JarIndex m_index;
35
36 private ClassEntry m_baseClass = newClass("none/a");
37 private ClassEntry m_subClassA = newClass("none/b");
38 private ClassEntry m_subClassAA = newClass("none/d");
39 private ClassEntry m_subClassB = newClass("none/c");
40 private FieldEntry m_nameField = new FieldEntry(m_baseClass, "a");
41 private FieldEntry m_numThingsField = new FieldEntry(m_subClassB, "a");
42
43 public TestJarIndexInheritanceTree()
44 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(newMethod(m_baseClass, "a", "()Ljava/lang/String;"));
102 assertThat(entries, containsInAnyOrder(
103 newMethod(m_baseClass, "a", "()Ljava/lang/String;"),
104 newMethod(m_subClassAA, "a", "()Ljava/lang/String;")
105 ));
106 entries = m_index.getRelatedMethodImplementations(newMethod(m_subClassAA, "a", "()Ljava/lang/String;"));
107 assertThat(entries, containsInAnyOrder(
108 newMethod(m_baseClass, "a", "()Ljava/lang/String;"),
109 newMethod(m_subClassAA, "a", "()Ljava/lang/String;")
110 ));
111
112 // doBaseThings()
113 entries = m_index.getRelatedMethodImplementations(newMethod(m_baseClass, "a", "()V"));
114 assertThat(entries, containsInAnyOrder(
115 newMethod(m_baseClass, "a", "()V"),
116 newMethod(m_subClassAA, "a", "()V"),
117 newMethod(m_subClassB, "a", "()V")
118 ));
119 entries = m_index.getRelatedMethodImplementations(newMethod(m_subClassAA, "a", "()V"));
120 assertThat(entries, containsInAnyOrder(
121 newMethod(m_baseClass, "a", "()V"),
122 newMethod(m_subClassAA, "a", "()V"),
123 newMethod(m_subClassB, "a", "()V")
124 ));
125 entries = m_index.getRelatedMethodImplementations(newMethod(m_subClassB, "a", "()V"));
126 assertThat(entries, containsInAnyOrder(
127 newMethod(m_baseClass, "a", "()V"),
128 newMethod(m_subClassAA, "a", "()V"),
129 newMethod(m_subClassB, "a", "()V")
130 ));
131
132 // doBThings
133 entries = m_index.getRelatedMethodImplementations(newMethod(m_subClassB, "b", "()V"));
134 assertThat(entries, containsInAnyOrder(newMethod(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 = newConstructor(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 = newConstructor(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 = newMethod(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 = newMethod(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(newMethod(m_baseClass, "a", "()Ljava/lang/String;")), is(true));
211 assertThat(m_index.containsObfBehavior(newMethod(m_subClassA, "a", "()Ljava/lang/String;")), is(false));
212 assertThat(m_index.containsObfBehavior(newMethod(m_subClassAA, "a", "()Ljava/lang/String;")), is(true));
213 assertThat(m_index.containsObfBehavior(newMethod(m_subClassB, "a", "()Ljava/lang/String;")), is(false));
214
215 // doBaseThings()
216 assertThat(m_index.containsObfBehavior(newMethod(m_baseClass, "a", "()V")), is(true));
217 assertThat(m_index.containsObfBehavior(newMethod(m_subClassA, "a", "()V")), is(false));
218 assertThat(m_index.containsObfBehavior(newMethod(m_subClassAA, "a", "()V")), is(true));
219 assertThat(m_index.containsObfBehavior(newMethod(m_subClassB, "a", "()V")), is(true));
220
221 // doBThings()
222 assertThat(m_index.containsObfBehavior(newMethod(m_baseClass, "b", "()V")), is(false));
223 assertThat(m_index.containsObfBehavior(newMethod(m_subClassA, "b", "()V")), is(false));
224 assertThat(m_index.containsObfBehavior(newMethod(m_subClassAA, "b", "()V")), is(false));
225 assertThat(m_index.containsObfBehavior(newMethod(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..c6a9e55
--- /dev/null
+++ b/test/cuchaz/enigma/TestJarIndexLoneClass.java
@@ -0,0 +1,165 @@
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()
42 throws Exception {
43 m_index = new JarIndex();
44 m_index.indexJar(new JarFile("build/testLoneClass.obf.jar"), false);
45 }
46
47 @Test
48 public void obfEntries() {
49 assertThat(m_index.getObfClassEntries(), containsInAnyOrder(
50 newClass("cuchaz/enigma/inputs/Keep"),
51 newClass("none/a")
52 ));
53 }
54
55 @Test
56 public void translationIndex() {
57 assertThat(m_index.getTranslationIndex().getSuperclass(new ClassEntry("none/a")), is(nullValue()));
58 assertThat(m_index.getTranslationIndex().getSuperclass(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(nullValue()));
59 assertThat(m_index.getTranslationIndex().getAncestry(new ClassEntry("none/a")), is(empty()));
60 assertThat(m_index.getTranslationIndex().getAncestry(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(empty()));
61 assertThat(m_index.getTranslationIndex().getSubclass(new ClassEntry("none/a")), is(empty()));
62 assertThat(m_index.getTranslationIndex().getSubclass(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(empty()));
63 }
64
65 @Test
66 public void access() {
67 assertThat(m_index.getAccess(newField("none/a", "a")), is(Access.Private));
68 assertThat(m_index.getAccess(newMethod("none/a", "a", "()Ljava/lang/String;")), is(Access.Public));
69 assertThat(m_index.getAccess(newField("none/a", "b")), is(nullValue()));
70 }
71
72 @Test
73 public void classInheritance() {
74 ClassInheritanceTreeNode node = m_index.getClassInheritance(new Translator(), newClass("none/a"));
75 assertThat(node, is(not(nullValue())));
76 assertThat(node.getObfClassName(), is("none/a"));
77 assertThat(node.getChildCount(), is(0));
78 }
79
80 @Test
81 public void methodInheritance() {
82 MethodEntry source = newMethod("none/a", "a", "()Ljava/lang/String;");
83 MethodInheritanceTreeNode node = m_index.getMethodInheritance(new Translator(), source);
84 assertThat(node, is(not(nullValue())));
85 assertThat(node.getMethodEntry(), is(source));
86 assertThat(node.getChildCount(), is(0));
87 }
88
89 @Test
90 public void classImplementations() {
91 ClassImplementationsTreeNode node = m_index.getClassImplementations(new Translator(), newClass("none/a"));
92 assertThat(node, is(nullValue()));
93 }
94
95 @Test
96 public void methodImplementations() {
97 MethodEntry source = newMethod("none/a", "a", "()Ljava/lang/String;");
98 MethodImplementationsTreeNode node = m_index.getMethodImplementations(new Translator(), source);
99 assertThat(node, is(nullValue()));
100 }
101
102 @Test
103 public void relatedMethodImplementations() {
104 Set<MethodEntry> entries = m_index.getRelatedMethodImplementations(newMethod("none/a", "a", "()Ljava/lang/String;"));
105 assertThat(entries, containsInAnyOrder(
106 newMethod("none/a", "a", "()Ljava/lang/String;")
107 ));
108 }
109
110 @Test
111 @SuppressWarnings("unchecked")
112 public void fieldReferences() {
113 FieldEntry source = newField("none/a", "a");
114 Collection<EntryReference<FieldEntry,BehaviorEntry>> references = m_index.getFieldReferences(source);
115 assertThat(references, containsInAnyOrder(
116 newFieldReferenceByConstructor(source, "none/a", "(Ljava/lang/String;)V"),
117 newFieldReferenceByMethod(source, "none/a", "a", "()Ljava/lang/String;")
118 ));
119 }
120
121 @Test
122 public void behaviorReferences() {
123 assertThat(m_index.getBehaviorReferences(newMethod("none/a", "a", "()Ljava/lang/String;")), is(empty()));
124 }
125
126 @Test
127 public void innerClasses() {
128 assertThat(m_index.getInnerClasses("none/a"), is(empty()));
129 }
130
131 @Test
132 public void outerClass() {
133 assertThat(m_index.getOuterClass("a"), is(nullValue()));
134 }
135
136 @Test
137 public void isAnonymousClass() {
138 assertThat(m_index.isAnonymousClass("none/a"), is(false));
139 }
140
141 @Test
142 public void interfaces() {
143 assertThat(m_index.getInterfaces("none/a"), is(empty()));
144 }
145
146 @Test
147 public void implementingClasses() {
148 assertThat(m_index.getImplementingClasses("none/a"), is(empty()));
149 }
150
151 @Test
152 public void isInterface() {
153 assertThat(m_index.isInterface("none/a"), is(false));
154 }
155
156 @Test
157 public void contains() {
158 assertThat(m_index.containsObfClass(newClass("none/a")), is(true));
159 assertThat(m_index.containsObfClass(newClass("none/b")), is(false));
160 assertThat(m_index.containsObfField(newField("none/a", "a")), is(true));
161 assertThat(m_index.containsObfField(newField("none/a", "b")), is(false));
162 assertThat(m_index.containsObfBehavior(newMethod("none/a", "a", "()Ljava/lang/String;")), is(true));
163 assertThat(m_index.containsObfBehavior(newMethod("none/a", "b", "()Ljava/lang/String;")), is(false));
164 }
165}
diff --git a/test/cuchaz/enigma/TestSignature.java b/test/cuchaz/enigma/TestSignature.java
new file mode 100644
index 0000000..183a4fd
--- /dev/null
+++ b/test/cuchaz/enigma/TestSignature.java
@@ -0,0 +1,266 @@
1package cuchaz.enigma;
2
3import static org.hamcrest.MatcherAssert.*;
4import static org.hamcrest.Matchers.*;
5
6import org.junit.Test;
7
8import cuchaz.enigma.mapping.ClassNameReplacer;
9import cuchaz.enigma.mapping.Signature;
10import cuchaz.enigma.mapping.Type;
11
12
13public class TestSignature {
14
15 @Test
16 public void easiest() {
17 final Signature sig = new Signature("()V");
18 assertThat(sig.getArgumentTypes(), is(empty()));
19 assertThat(sig.getReturnType(), is(new Type("V")));
20 }
21
22 @Test
23 public void primitives() {
24 {
25 final Signature sig = new Signature("(I)V");
26 assertThat(sig.getArgumentTypes(), contains(
27 new Type("I")
28 ));
29 assertThat(sig.getReturnType(), is(new Type("V")));
30 }
31 {
32 final Signature sig = new Signature("(I)I");
33 assertThat(sig.getArgumentTypes(), contains(
34 new Type("I")
35 ));
36 assertThat(sig.getReturnType(), is(new Type("I")));
37 }
38 {
39 final Signature sig = new Signature("(IBCJ)Z");
40 assertThat(sig.getArgumentTypes(), contains(
41 new Type("I"),
42 new Type("B"),
43 new Type("C"),
44 new Type("J")
45 ));
46 assertThat(sig.getReturnType(), is(new Type("Z")));
47 }
48 }
49
50 @Test
51 public void classes() {
52 {
53 final Signature sig = new Signature("([LFoo;)V");
54 assertThat(sig.getArgumentTypes().size(), is(1));
55 assertThat(sig.getArgumentTypes().get(0), is(new Type("[LFoo;")));
56 assertThat(sig.getReturnType(), is(new Type("V")));
57 }
58 {
59 final Signature sig = new Signature("(LFoo;)LBar;");
60 assertThat(sig.getArgumentTypes(), contains(
61 new Type("LFoo;")
62 ));
63 assertThat(sig.getReturnType(), is(new Type("LBar;")));
64 }
65 {
66 final Signature sig = new Signature("(LFoo;LMoo;LZoo;)LBar;");
67 assertThat(sig.getArgumentTypes(), contains(
68 new Type("LFoo;"),
69 new Type("LMoo;"),
70 new Type("LZoo;")
71 ));
72 assertThat(sig.getReturnType(), is(new Type("LBar;")));
73 }
74 {
75 final Signature sig = new Signature("(LFoo<LParm;>;LMoo<LParm;>;)LBar<LParm;>;");
76 assertThat(sig.getArgumentTypes(), contains(
77 new Type("LFoo<LParm;>;"),
78 new Type("LMoo<LParm;>;")
79 ));
80 assertThat(sig.getReturnType(), is(new Type("LBar<LParm;>;")));
81 }
82 }
83
84 @Test
85 public void arrays() {
86 {
87 final Signature sig = new Signature("([I)V");
88 assertThat(sig.getArgumentTypes(), contains(
89 new Type("[I")
90 ));
91 assertThat(sig.getReturnType(), is(new Type("V")));
92 }
93 {
94 final Signature sig = new Signature("([I)[J");
95 assertThat(sig.getArgumentTypes(), contains(
96 new Type("[I")
97 ));
98 assertThat(sig.getReturnType(), is(new Type("[J")));
99 }
100 {
101 final Signature sig = new Signature("([I[Z[F)[D");
102 assertThat(sig.getArgumentTypes(), contains(
103 new Type("[I"),
104 new Type("[Z"),
105 new Type("[F")
106 ));
107 assertThat(sig.getReturnType(), is(new Type("[D")));
108 }
109 }
110
111 @Test
112 public void mixed() {
113 {
114 final Signature sig = new Signature("(I[JLFoo;)Z");
115 assertThat(sig.getArgumentTypes(), contains(
116 new Type("I"),
117 new Type("[J"),
118 new Type("LFoo;")
119 ));
120 assertThat(sig.getReturnType(), is(new Type("Z")));
121 }
122 {
123 final Signature sig = new Signature("(III)[LFoo;");
124 assertThat(sig.getArgumentTypes(), contains(
125 new Type("I"),
126 new Type("I"),
127 new Type("I")
128 ));
129 assertThat(sig.getReturnType(), is(new Type("[LFoo;")));
130 }
131 }
132
133 @Test
134 public void replaceClasses() {
135 {
136 final Signature oldSig = new Signature("()V");
137 final Signature sig = new Signature(oldSig, new ClassNameReplacer() {
138 @Override
139 public String replace(String val) {
140 return null;
141 }
142 });
143 assertThat(sig.getArgumentTypes(), is(empty()));
144 assertThat(sig.getReturnType(), is(new Type("V")));
145 }
146 {
147 final Signature oldSig = new Signature("(IJLFoo;)V");
148 final Signature sig = new Signature(oldSig, new ClassNameReplacer() {
149 @Override
150 public String replace(String val) {
151 return null;
152 }
153 });
154 assertThat(sig.getArgumentTypes(), contains(
155 new Type("I"),
156 new Type("J"),
157 new Type("LFoo;")
158 ));
159 assertThat(sig.getReturnType(), is(new Type("V")));
160 }
161 {
162 final Signature oldSig = new Signature("(LFoo;LBar;)LMoo;");
163 final Signature sig = new Signature(oldSig, new ClassNameReplacer() {
164 @Override
165 public String replace(String val) {
166 if (val.equals("Foo")) {
167 return "Bar";
168 }
169 return null;
170 }
171 });
172 assertThat(sig.getArgumentTypes(), contains(
173 new Type("LBar;"),
174 new Type("LBar;")
175 ));
176 assertThat(sig.getReturnType(), is(new Type("LMoo;")));
177 }
178 {
179 final Signature oldSig = new Signature("(LFoo;LBar;)LMoo;");
180 final Signature sig = new Signature(oldSig, new ClassNameReplacer() {
181 @Override
182 public String replace(String val) {
183 if (val.equals("Moo")) {
184 return "Cow";
185 }
186 return null;
187 }
188 });
189 assertThat(sig.getArgumentTypes(), contains(
190 new Type("LFoo;"),
191 new Type("LBar;")
192 ));
193 assertThat(sig.getReturnType(), is(new Type("LCow;")));
194 }
195 }
196
197 @Test
198 public void replaceArrayClasses() {
199 {
200 final Signature oldSig = new Signature("([LFoo;)[[[LBar;");
201 final Signature sig = new Signature(oldSig, new ClassNameReplacer() {
202 @Override
203 public String replace(String val) {
204 if (val.equals("Foo")) {
205 return "Food";
206 } else if (val.equals("Bar")) {
207 return "Beer";
208 }
209 return null;
210 }
211 });
212 assertThat(sig.getArgumentTypes(), contains(
213 new Type("[LFood;")
214 ));
215 assertThat(sig.getReturnType(), is(new Type("[[[LBeer;")));
216 }
217 }
218
219 @Test
220 public void equals() {
221
222 // base
223 assertThat(new Signature("()V"), is(new Signature("()V")));
224
225 // arguments
226 assertThat(new Signature("(I)V"), is(new Signature("(I)V")));
227 assertThat(new Signature("(ZIZ)V"), is(new Signature("(ZIZ)V")));
228 assertThat(new Signature("(LFoo;)V"), is(new Signature("(LFoo;)V")));
229 assertThat(new Signature("(LFoo;LBar;)V"), is(new Signature("(LFoo;LBar;)V")));
230 assertThat(new Signature("([I)V"), is(new Signature("([I)V")));
231 assertThat(new Signature("([[D[[[J)V"), is(new Signature("([[D[[[J)V")));
232
233 assertThat(new Signature("()V"), is(not(new Signature("(I)V"))));
234 assertThat(new Signature("(I)V"), is(not(new Signature("()V"))));
235 assertThat(new Signature("(IJ)V"), is(not(new Signature("(JI)V"))));
236 assertThat(new Signature("([[Z)V"), is(not(new Signature("([[LFoo;)V"))));
237 assertThat(new Signature("(LFoo;LBar;)V"), is(not(new Signature("(LFoo;LCow;)V"))));
238 assertThat(new Signature("([LFoo;LBar;)V"), is(not(new Signature("(LFoo;LCow;)V"))));
239
240 // return type
241 assertThat(new Signature("()I"), is(new Signature("()I")));
242 assertThat(new Signature("()Z"), is(new Signature("()Z")));
243 assertThat(new Signature("()[D"), is(new Signature("()[D")));
244 assertThat(new Signature("()[[[Z"), is(new Signature("()[[[Z")));
245 assertThat(new Signature("()LFoo;"), is(new Signature("()LFoo;")));
246 assertThat(new Signature("()[LFoo;"), is(new Signature("()[LFoo;")));
247
248 assertThat(new Signature("()I"), is(not(new Signature("()Z"))));
249 assertThat(new Signature("()Z"), is(not(new Signature("()I"))));
250 assertThat(new Signature("()[D"), is(not(new Signature("()[J"))));
251 assertThat(new Signature("()[[[Z"), is(not(new Signature("()[[Z"))));
252 assertThat(new Signature("()LFoo;"), is(not(new Signature("()LBar;"))));
253 assertThat(new Signature("()[LFoo;"), is(not(new Signature("()[LBar;"))));
254 }
255
256 @Test
257 public void testToString() {
258 assertThat(new Signature("()V").toString(), is("()V"));
259 assertThat(new Signature("(I)V").toString(), is("(I)V"));
260 assertThat(new Signature("(ZIZ)V").toString(), is("(ZIZ)V"));
261 assertThat(new Signature("(LFoo;)V").toString(), is("(LFoo;)V"));
262 assertThat(new Signature("(LFoo;LBar;)V").toString(), is("(LFoo;LBar;)V"));
263 assertThat(new Signature("([I)V").toString(), is("([I)V"));
264 assertThat(new Signature("([[D[[[J)V").toString(), is("([[D[[[J)V"));
265 }
266}
diff --git a/test/cuchaz/enigma/TestSourceIndex.java b/test/cuchaz/enigma/TestSourceIndex.java
new file mode 100644
index 0000000..357acb6
--- /dev/null
+++ b/test/cuchaz/enigma/TestSourceIndex.java
@@ -0,0 +1,50 @@
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()
29 throws Exception {
30 Deobfuscator deobfuscator = new Deobfuscator(new JarFile("input/1.8.jar"));
31
32 // get all classes that aren't inner classes
33 Set<ClassEntry> classEntries = Sets.newHashSet();
34 for (ClassEntry obfClassEntry : deobfuscator.getJarIndex().getObfClassEntries()) {
35 if (!obfClassEntry.isInnerClass()) {
36 classEntries.add(obfClassEntry);
37 }
38 }
39
40 for (ClassEntry obfClassEntry : classEntries) {
41 try {
42 CompilationUnit tree = deobfuscator.getSourceTree(obfClassEntry.getName());
43 String source = deobfuscator.getSource(tree);
44 deobfuscator.getSourceIndex(tree, source);
45 } catch (Throwable t) {
46 throw new Error("Unable to index " + obfClassEntry, t);
47 }
48 }
49 }
50}
diff --git a/test/cuchaz/enigma/TestTokensConstructors.java b/test/cuchaz/enigma/TestTokensConstructors.java
new file mode 100644
index 0000000..6758d2a
--- /dev/null
+++ b/test/cuchaz/enigma/TestTokensConstructors.java
@@ -0,0 +1,136 @@
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()
26 throws Exception {
27 super(new JarFile("build/testConstructors.obf.jar"));
28 }
29
30 @Test
31 public void baseDeclarations() {
32 assertThat(getDeclarationToken(newConstructor("none/a", "()V")), is("a"));
33 assertThat(getDeclarationToken(newConstructor("none/a", "(I)V")), is("a"));
34 }
35
36 @Test
37 public void subDeclarations() {
38 assertThat(getDeclarationToken(newConstructor("none/d", "()V")), is("d"));
39 assertThat(getDeclarationToken(newConstructor("none/d", "(I)V")), is("d"));
40 assertThat(getDeclarationToken(newConstructor("none/d", "(II)V")), is("d"));
41 assertThat(getDeclarationToken(newConstructor("none/d", "(III)V")), is("d"));
42 }
43
44 @Test
45 public void subsubDeclarations() {
46 assertThat(getDeclarationToken(newConstructor("none/e", "(I)V")), is("e"));
47 }
48
49 @Test
50 public void defaultDeclarations() {
51 assertThat(getDeclarationToken(newConstructor("none/c", "()V")), nullValue());
52 }
53
54 @Test
55 public void baseDefaultReferences() {
56 BehaviorEntry source = newConstructor("none/a", "()V");
57 assertThat(
58 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "a", "()V")),
59 containsInAnyOrder("a")
60 );
61 assertThat(
62 getReferenceTokens(newBehaviorReferenceByConstructor(source, "none/d", "()V")),
63 is(empty()) // implicit call, not decompiled to token
64 );
65 assertThat(
66 getReferenceTokens(newBehaviorReferenceByConstructor(source, "none/d", "(III)V")),
67 is(empty()) // implicit call, not decompiled to token
68 );
69 }
70
71 @Test
72 public void baseIntReferences() {
73 BehaviorEntry source = newConstructor("none/a", "(I)V");
74 assertThat(
75 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "b", "()V")),
76 containsInAnyOrder("a")
77 );
78 }
79
80 @Test
81 public void subDefaultReferences() {
82 BehaviorEntry source = newConstructor("none/d", "()V");
83 assertThat(
84 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "c", "()V")),
85 containsInAnyOrder("d")
86 );
87 assertThat(
88 getReferenceTokens(newBehaviorReferenceByConstructor(source, "none/d", "(I)V")),
89 containsInAnyOrder("this")
90 );
91 }
92
93 @Test
94 public void subIntReferences() {
95 BehaviorEntry source = newConstructor("none/d", "(I)V");
96 assertThat(getReferenceTokens(
97 newBehaviorReferenceByMethod(source, "none/b", "d", "()V")),
98 containsInAnyOrder("d")
99 );
100 assertThat(getReferenceTokens(
101 newBehaviorReferenceByConstructor(source, "none/d", "(II)V")),
102 containsInAnyOrder("this")
103 );
104 assertThat(getReferenceTokens(
105 newBehaviorReferenceByConstructor(source, "none/e", "(I)V")),
106 containsInAnyOrder("super")
107 );
108 }
109
110 @Test
111 public void subIntIntReferences() {
112 BehaviorEntry source = newConstructor("none/d", "(II)V");
113 assertThat(
114 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "e", "()V")),
115 containsInAnyOrder("d")
116 );
117 }
118
119 @Test
120 public void subsubIntReferences() {
121 BehaviorEntry source = newConstructor("none/e", "(I)V");
122 assertThat(
123 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "f", "()V")),
124 containsInAnyOrder("e")
125 );
126 }
127
128 @Test
129 public void defaultConstructableReferences() {
130 BehaviorEntry source = newConstructor("none/c", "()V");
131 assertThat(
132 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "g", "()V")),
133 containsInAnyOrder("c")
134 );
135 }
136}
diff --git a/test/cuchaz/enigma/TestTranslator.java b/test/cuchaz/enigma/TestTranslator.java
new file mode 100644
index 0000000..290f6f0
--- /dev/null
+++ b/test/cuchaz/enigma/TestTranslator.java
@@ -0,0 +1,39 @@
1package cuchaz.enigma;
2
3import static cuchaz.enigma.EntryFactory.*;
4import static org.hamcrest.MatcherAssert.*;
5import static org.hamcrest.Matchers.*;
6
7import java.io.InputStream;
8import java.io.InputStreamReader;
9import java.util.jar.JarFile;
10
11import org.junit.Test;
12
13import cuchaz.enigma.mapping.Mappings;
14import cuchaz.enigma.mapping.MappingsReader;
15import cuchaz.enigma.mapping.TranslationDirection;
16import cuchaz.enigma.mapping.Translator;
17
18
19public class TestTranslator {
20
21 private Deobfuscator m_deobfuscator;
22 private Mappings m_mappings;
23
24 public TestTranslator()
25 throws Exception {
26 m_deobfuscator = new Deobfuscator(new JarFile("build/testTranslation.obf.jar"));
27 try (InputStream in = getClass().getResourceAsStream("/cuchaz/enigma/resources/translation.mappings")) {
28 m_mappings = new MappingsReader().read(new InputStreamReader(in));
29 m_deobfuscator.setMappings(m_mappings);
30 }
31 }
32
33 @Test
34 public void deobfuscatingTranslations()
35 throws Exception {
36 Translator translator = m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating);
37 assertThat(translator.translateEntry(newClass("none/a")), is(newClass("deobf/A")));
38 }
39}
diff --git a/test/cuchaz/enigma/TestType.java b/test/cuchaz/enigma/TestType.java
new file mode 100644
index 0000000..7c3cebe
--- /dev/null
+++ b/test/cuchaz/enigma/TestType.java
@@ -0,0 +1,229 @@
1package cuchaz.enigma;
2
3import org.junit.Test;
4
5import static org.hamcrest.MatcherAssert.*;
6import static org.hamcrest.Matchers.*;
7
8import cuchaz.enigma.mapping.Type;
9
10import static cuchaz.enigma.EntryFactory.*;
11
12
13public class TestType {
14
15 @Test
16 public void isVoid() {
17 assertThat(new Type("V").isVoid(), is(true));
18 assertThat(new Type("Z").isVoid(), is(false));
19 assertThat(new Type("B").isVoid(), is(false));
20 assertThat(new Type("C").isVoid(), is(false));
21 assertThat(new Type("I").isVoid(), is(false));
22 assertThat(new Type("J").isVoid(), is(false));
23 assertThat(new Type("F").isVoid(), is(false));
24 assertThat(new Type("D").isVoid(), is(false));
25 assertThat(new Type("LFoo;").isVoid(), is(false));
26 assertThat(new Type("[I").isVoid(), is(false));
27 }
28
29 @Test
30 public void isPrimitive() {
31 assertThat(new Type("V").isPrimitive(), is(false));
32 assertThat(new Type("Z").isPrimitive(), is(true));
33 assertThat(new Type("B").isPrimitive(), is(true));
34 assertThat(new Type("C").isPrimitive(), is(true));
35 assertThat(new Type("I").isPrimitive(), is(true));
36 assertThat(new Type("J").isPrimitive(), is(true));
37 assertThat(new Type("F").isPrimitive(), is(true));
38 assertThat(new Type("D").isPrimitive(), is(true));
39 assertThat(new Type("LFoo;").isPrimitive(), is(false));
40 assertThat(new Type("[I").isPrimitive(), is(false));
41 }
42
43 @Test
44 public void getPrimitive() {
45 assertThat(new Type("Z").getPrimitive(), is(Type.Primitive.Boolean));
46 assertThat(new Type("B").getPrimitive(), is(Type.Primitive.Byte));
47 assertThat(new Type("C").getPrimitive(), is(Type.Primitive.Character));
48 assertThat(new Type("I").getPrimitive(), is(Type.Primitive.Integer));
49 assertThat(new Type("J").getPrimitive(), is(Type.Primitive.Long));
50 assertThat(new Type("F").getPrimitive(), is(Type.Primitive.Float));
51 assertThat(new Type("D").getPrimitive(), is(Type.Primitive.Double));
52 }
53
54 @Test
55 public void isClass() {
56 assertThat(new Type("V").isClass(), is(false));
57 assertThat(new Type("Z").isClass(), is(false));
58 assertThat(new Type("B").isClass(), is(false));
59 assertThat(new Type("C").isClass(), is(false));
60 assertThat(new Type("I").isClass(), is(false));
61 assertThat(new Type("J").isClass(), is(false));
62 assertThat(new Type("F").isClass(), is(false));
63 assertThat(new Type("D").isClass(), is(false));
64 assertThat(new Type("LFoo;").isClass(), is(true));
65 assertThat(new Type("[I").isClass(), is(false));
66 }
67
68 @Test
69 public void getClassEntry() {
70 assertThat(new Type("LFoo;").getClassEntry(), is(newClass("Foo")));
71 assertThat(new Type("LFoo<Ljava/lang/String;>;").getClassEntry(), is(newClass("Foo")));
72 }
73
74 @Test
75 public void getArrayClassEntry() {
76 assertThat(new Type("[LFoo;").getClassEntry(), is(newClass("Foo")));
77 assertThat(new Type("[[[LFoo<Ljava/lang/String;>;").getClassEntry(), is(newClass("Foo")));
78 }
79
80 @Test
81 public void isArray() {
82 assertThat(new Type("V").isArray(), is(false));
83 assertThat(new Type("Z").isArray(), is(false));
84 assertThat(new Type("B").isArray(), is(false));
85 assertThat(new Type("C").isArray(), is(false));
86 assertThat(new Type("I").isArray(), is(false));
87 assertThat(new Type("J").isArray(), is(false));
88 assertThat(new Type("F").isArray(), is(false));
89 assertThat(new Type("D").isArray(), is(false));
90 assertThat(new Type("LFoo;").isArray(), is(false));
91 assertThat(new Type("[I").isArray(), is(true));
92 }
93
94 @Test
95 public void getArrayDimension() {
96 assertThat(new Type("[I").getArrayDimension(), is(1));
97 assertThat(new Type("[[I").getArrayDimension(), is(2));
98 assertThat(new Type("[[[I").getArrayDimension(), is(3));
99 }
100
101 @Test
102 public void getArrayType() {
103 assertThat(new Type("[I").getArrayType(), is(new Type("I")));
104 assertThat(new Type("[[I").getArrayType(), is(new Type("I")));
105 assertThat(new Type("[[[I").getArrayType(), is(new Type("I")));
106 assertThat(new Type("[Ljava/lang/String;").getArrayType(), is(new Type("Ljava/lang/String;")));
107 }
108
109 @Test
110 public void hasClass() {
111 assertThat(new Type("LFoo;").hasClass(), is(true));
112 assertThat(new Type("LCow<LCheese;>;").hasClass(), is(true));
113 assertThat(new Type("[LBar;").hasClass(), is(true));
114 assertThat(new Type("[[[LCat;").hasClass(), is(true));
115
116 assertThat(new Type("V").hasClass(), is(false));
117 assertThat(new Type("[I").hasClass(), is(false));
118 assertThat(new Type("[[[I").hasClass(), is(false));
119 assertThat(new Type("Z").hasClass(), is(false));
120 }
121
122 @Test
123 public void parseVoid() {
124 final String answer = "V";
125 assertThat(Type.parseFirst("V"), is(answer));
126 assertThat(Type.parseFirst("VVV"), is(answer));
127 assertThat(Type.parseFirst("VIJ"), is(answer));
128 assertThat(Type.parseFirst("V[I"), is(answer));
129 assertThat(Type.parseFirst("VLFoo;"), is(answer));
130 assertThat(Type.parseFirst("V[LFoo;"), is(answer));
131 }
132
133 @Test
134 public void parsePrimitive() {
135 final String answer = "I";
136 assertThat(Type.parseFirst("I"), is(answer));
137 assertThat(Type.parseFirst("III"), is(answer));
138 assertThat(Type.parseFirst("IJZ"), is(answer));
139 assertThat(Type.parseFirst("I[I"), is(answer));
140 assertThat(Type.parseFirst("ILFoo;"), is(answer));
141 assertThat(Type.parseFirst("I[LFoo;"), is(answer));
142 }
143
144 @Test
145 public void parseClass() {
146 {
147 final String answer = "LFoo;";
148 assertThat(Type.parseFirst("LFoo;"), is(answer));
149 assertThat(Type.parseFirst("LFoo;I"), is(answer));
150 assertThat(Type.parseFirst("LFoo;JZ"), is(answer));
151 assertThat(Type.parseFirst("LFoo;[I"), is(answer));
152 assertThat(Type.parseFirst("LFoo;LFoo;"), is(answer));
153 assertThat(Type.parseFirst("LFoo;[LFoo;"), is(answer));
154 }
155 {
156 final String answer = "LFoo<LFoo;>;";
157 assertThat(Type.parseFirst("LFoo<LFoo;>;"), is(answer));
158 assertThat(Type.parseFirst("LFoo<LFoo;>;I"), is(answer));
159 assertThat(Type.parseFirst("LFoo<LFoo;>;JZ"), is(answer));
160 assertThat(Type.parseFirst("LFoo<LFoo;>;[I"), is(answer));
161 assertThat(Type.parseFirst("LFoo<LFoo;>;LFoo;"), is(answer));
162 assertThat(Type.parseFirst("LFoo<LFoo;>;[LFoo;"), is(answer));
163 }
164 {
165 final String answer = "LFoo<LFoo;,LBar;>;";
166 assertThat(Type.parseFirst("LFoo<LFoo;,LBar;>;"), is(answer));
167 assertThat(Type.parseFirst("LFoo<LFoo;,LBar;>;I"), is(answer));
168 assertThat(Type.parseFirst("LFoo<LFoo;,LBar;>;JZ"), is(answer));
169 assertThat(Type.parseFirst("LFoo<LFoo;,LBar;>;[I"), is(answer));
170 assertThat(Type.parseFirst("LFoo<LFoo;,LBar;>;LFoo;"), is(answer));
171 assertThat(Type.parseFirst("LFoo<LFoo;,LBar;>;[LFoo;"), is(answer));
172 }
173 }
174
175 @Test
176 public void parseArray() {
177 {
178 final String answer = "[I";
179 assertThat(Type.parseFirst("[I"), is(answer));
180 assertThat(Type.parseFirst("[III"), is(answer));
181 assertThat(Type.parseFirst("[IJZ"), is(answer));
182 assertThat(Type.parseFirst("[I[I"), is(answer));
183 assertThat(Type.parseFirst("[ILFoo;"), is(answer));
184 }
185 {
186 final String answer = "[[I";
187 assertThat(Type.parseFirst("[[I"), is(answer));
188 assertThat(Type.parseFirst("[[III"), is(answer));
189 assertThat(Type.parseFirst("[[IJZ"), is(answer));
190 assertThat(Type.parseFirst("[[I[I"), is(answer));
191 assertThat(Type.parseFirst("[[ILFoo;"), is(answer));
192 }
193 {
194 final String answer = "[LFoo;";
195 assertThat(Type.parseFirst("[LFoo;"), is(answer));
196 assertThat(Type.parseFirst("[LFoo;II"), is(answer));
197 assertThat(Type.parseFirst("[LFoo;JZ"), is(answer));
198 assertThat(Type.parseFirst("[LFoo;[I"), is(answer));
199 assertThat(Type.parseFirst("[LFoo;LFoo;"), is(answer));
200 }
201 }
202
203 @Test
204 public void equals() {
205 assertThat(new Type("V"), is(new Type("V")));
206 assertThat(new Type("Z"), is(new Type("Z")));
207 assertThat(new Type("B"), is(new Type("B")));
208 assertThat(new Type("C"), is(new Type("C")));
209 assertThat(new Type("I"), is(new Type("I")));
210 assertThat(new Type("J"), is(new Type("J")));
211 assertThat(new Type("F"), is(new Type("F")));
212 assertThat(new Type("D"), is(new Type("D")));
213 assertThat(new Type("LFoo;"), is(new Type("LFoo;")));
214 assertThat(new Type("[I"), is(new Type("[I")));
215 assertThat(new Type("[[[I"), is(new Type("[[[I")));
216 assertThat(new Type("[LFoo;"), is(new Type("[LFoo;")));
217 assertThat(new Type("LFoo<LBar;>;"), is(new Type("LFoo<LBar;>;")));
218
219 assertThat(new Type("V"), is(not(new Type("I"))));
220 assertThat(new Type("I"), is(not(new Type("J"))));
221 assertThat(new Type("I"), is(not(new Type("LBar;"))));
222 assertThat(new Type("I"), is(not(new Type("[I"))));
223 assertThat(new Type("LFoo;"), is(not(new Type("LBar;"))));
224 assertThat(new Type("LFoo<LBar;>;"), is(not(new Type("LFoo<LCow;>;"))));
225 assertThat(new Type("[I"), is(not(new Type("[Z"))));
226 assertThat(new Type("[[[I"), is(not(new Type("[I"))));
227 assertThat(new Type("[LFoo;"), is(not(new Type("[LBar;"))));
228 }
229}
diff --git a/test/cuchaz/enigma/TokenChecker.java b/test/cuchaz/enigma/TokenChecker.java
new file mode 100644
index 0000000..a72c2fc
--- /dev/null
+++ b/test/cuchaz/enigma/TokenChecker.java
@@ -0,0 +1,65 @@
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)
31 throws IOException {
32 m_deobfuscator = new Deobfuscator(jarFile);
33 }
34
35 protected String getDeclarationToken(Entry entry) {
36 // decompile the class
37 CompilationUnit tree = m_deobfuscator.getSourceTree(entry.getClassName());
38 // DEBUG
39 // tree.acceptVisitor( new TreeDumpVisitor( new File( "tree." + entry.getClassName().replace( '/', '.' ) + ".txt" ) ), null );
40 String source = m_deobfuscator.getSource(tree);
41 SourceIndex index = m_deobfuscator.getSourceIndex(tree, source);
42
43 // get the token value
44 Token token = index.getDeclarationToken(entry);
45 if (token == null) {
46 return null;
47 }
48 return source.substring(token.start, token.end);
49 }
50
51 @SuppressWarnings("unchecked")
52 protected Collection<String> getReferenceTokens(EntryReference<? extends Entry,? extends Entry> reference) {
53 // decompile the class
54 CompilationUnit tree = m_deobfuscator.getSourceTree(reference.context.getClassName());
55 String source = m_deobfuscator.getSource(tree);
56 SourceIndex index = m_deobfuscator.getSourceIndex(tree, source);
57
58 // get the token values
59 List<String> values = Lists.newArrayList();
60 for (Token token : index.getReferenceTokens((EntryReference<Entry,Entry>)reference)) {
61 values.add(source.substring(token.start, token.end));
62 }
63 return values;
64 }
65}
diff --git a/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}
diff --git a/test/cuchaz/enigma/inputs/translation/A.java b/test/cuchaz/enigma/inputs/translation/A.java
new file mode 100644
index 0000000..b8aaf11
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/translation/A.java
@@ -0,0 +1,8 @@
1package cuchaz.enigma.inputs.translation;
2
3public class A {
4
5 public int one;
6 public float two;
7 public String three;
8}
diff --git a/test/cuchaz/enigma/resources/translation.mappings b/test/cuchaz/enigma/resources/translation.mappings
new file mode 100644
index 0000000..70755bf
--- /dev/null
+++ b/test/cuchaz/enigma/resources/translation.mappings
@@ -0,0 +1,4 @@
1CLASS none/a deobf/A
2 FIELD a one
3 FIELD b two
4 FIELD c three \ No newline at end of file