summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Michael Smith2015-05-21 23:30:00 +0100
committerGravatar Michael Smith2015-05-21 23:30:00 +0100
commite3f452250e51b7271f3989c7dfd12e4422934942 (patch)
tree5aa482f9a6e21eb318a3e23e7d8274d77c73faf6
downloadenigma-e3f452250e51b7271f3989c7dfd12e4422934942.tar.gz
enigma-e3f452250e51b7271f3989c7dfd12e4422934942.tar.xz
enigma-e3f452250e51b7271f3989c7dfd12e4422934942.zip
Support Gradle alongside SSJB
This makes builds faster, simpler and better automated but still keeps Cuchaz happy. :)
-rw-r--r--.classpath10
-rw-r--r--.hgignore13
-rw-r--r--.project17
-rw-r--r--.settings/org.eclipse.jdt.core.prefs13
-rw-r--r--build.gradle176
-rw-r--r--build.py131
-rw-r--r--conf/about.html6
-rw-r--r--license.APL2.txt55
-rw-r--r--license.LGPL3.txt165
-rw-r--r--proguard-build.conf8
-rw-r--r--proguard-test.conf9
-rw-r--r--readme.txt33
-rw-r--r--settings.gradle1
-rw-r--r--src/cuchaz/enigma/CommandMain.java186
-rw-r--r--src/cuchaz/enigma/Constants.java20
-rw-r--r--src/cuchaz/enigma/ConvertMain.java322
-rw-r--r--src/cuchaz/enigma/Deobfuscator.java548
-rw-r--r--src/cuchaz/enigma/ExceptionIgnorer.java34
-rw-r--r--src/cuchaz/enigma/Main.java51
-rw-r--r--src/cuchaz/enigma/MainFormatConverter.java130
-rw-r--r--src/cuchaz/enigma/TranslatingTypeLoader.java249
-rw-r--r--src/cuchaz/enigma/Util.java104
-rw-r--r--src/cuchaz/enigma/analysis/Access.java43
-rw-r--r--src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java93
-rw-r--r--src/cuchaz/enigma/analysis/BridgeMarker.java43
-rw-r--r--src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java80
-rw-r--r--src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java85
-rw-r--r--src/cuchaz/enigma/analysis/EntryReference.java126
-rw-r--r--src/cuchaz/enigma/analysis/EntryRenamer.java192
-rw-r--r--src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java81
-rw-r--r--src/cuchaz/enigma/analysis/JarClassIterator.java137
-rw-r--r--src/cuchaz/enigma/analysis/JarIndex.java837
-rw-r--r--src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java101
-rw-r--r--src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java114
-rw-r--r--src/cuchaz/enigma/analysis/ReferenceTreeNode.java18
-rw-r--r--src/cuchaz/enigma/analysis/RelatedMethodChecker.java106
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndex.java184
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java150
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java112
-rw-r--r--src/cuchaz/enigma/analysis/SourceIndexVisitor.java452
-rw-r--r--src/cuchaz/enigma/analysis/Token.java56
-rw-r--r--src/cuchaz/enigma/analysis/TranslationIndex.java298
-rw-r--r--src/cuchaz/enigma/analysis/TreeDumpVisitor.java512
-rw-r--r--src/cuchaz/enigma/bytecode/CheckCastIterator.java127
-rw-r--r--src/cuchaz/enigma/bytecode/ClassProtectifier.java51
-rw-r--r--src/cuchaz/enigma/bytecode/ClassPublifier.java51
-rw-r--r--src/cuchaz/enigma/bytecode/ClassRenamer.java544
-rw-r--r--src/cuchaz/enigma/bytecode/ClassTranslator.java157
-rw-r--r--src/cuchaz/enigma/bytecode/ConstPoolEditor.java263
-rw-r--r--src/cuchaz/enigma/bytecode/InfoType.java317
-rw-r--r--src/cuchaz/enigma/bytecode/InnerClassWriter.java132
-rw-r--r--src/cuchaz/enigma/bytecode/LocalVariableRenamer.java123
-rw-r--r--src/cuchaz/enigma/bytecode/MethodParameterWriter.java70
-rw-r--r--src/cuchaz/enigma/bytecode/MethodParametersAttribute.java86
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java55
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java156
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java74
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java74
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java74
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java55
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java74
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java55
-rw-r--r--src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java28
-rw-r--r--src/cuchaz/enigma/convert/ClassForest.java60
-rw-r--r--src/cuchaz/enigma/convert/ClassIdentifier.java54
-rw-r--r--src/cuchaz/enigma/convert/ClassIdentity.java468
-rw-r--r--src/cuchaz/enigma/convert/ClassMatch.java88
-rw-r--r--src/cuchaz/enigma/convert/ClassMatches.java163
-rw-r--r--src/cuchaz/enigma/convert/ClassMatching.java155
-rw-r--r--src/cuchaz/enigma/convert/ClassNamer.java66
-rw-r--r--src/cuchaz/enigma/convert/FieldMatches.java155
-rw-r--r--src/cuchaz/enigma/convert/MappingsConverter.java559
-rw-r--r--src/cuchaz/enigma/convert/MatchesReader.java113
-rw-r--r--src/cuchaz/enigma/convert/MatchesWriter.java121
-rw-r--r--src/cuchaz/enigma/convert/MemberMatches.java159
-rw-r--r--src/cuchaz/enigma/gui/AboutDialog.java86
-rw-r--r--src/cuchaz/enigma/gui/BoxHighlightPainter.java64
-rw-r--r--src/cuchaz/enigma/gui/BrowserCaret.java45
-rw-r--r--src/cuchaz/enigma/gui/ClassListCellRenderer.java36
-rw-r--r--src/cuchaz/enigma/gui/ClassMatchingGui.java589
-rw-r--r--src/cuchaz/enigma/gui/ClassSelector.java293
-rw-r--r--src/cuchaz/enigma/gui/ClassSelectorClassNode.java50
-rw-r--r--src/cuchaz/enigma/gui/ClassSelectorPackageNode.java45
-rw-r--r--src/cuchaz/enigma/gui/CodeReader.java222
-rw-r--r--src/cuchaz/enigma/gui/CrashDialog.java101
-rw-r--r--src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java21
-rw-r--r--src/cuchaz/enigma/gui/Gui.java1122
-rw-r--r--src/cuchaz/enigma/gui/GuiController.java358
-rw-r--r--src/cuchaz/enigma/gui/GuiTricks.java56
-rw-r--r--src/cuchaz/enigma/gui/MemberMatchingGui.java499
-rw-r--r--src/cuchaz/enigma/gui/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/ScoredClassEntry.java30
-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.java49
-rw-r--r--src/cuchaz/enigma/mapping/BehaviorEntry.java15
-rw-r--r--src/cuchaz/enigma/mapping/ClassEntry.java172
-rw-r--r--src/cuchaz/enigma/mapping/ClassMapping.java460
-rw-r--r--src/cuchaz/enigma/mapping/ClassNameReplacer.java15
-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/EntryFactory.java166
-rw-r--r--src/cuchaz/enigma/mapping/EntryPair.java22
-rw-r--r--src/cuchaz/enigma/mapping/FieldEntry.java99
-rw-r--r--src/cuchaz/enigma/mapping/FieldMapping.java89
-rw-r--r--src/cuchaz/enigma/mapping/IllegalNameException.java44
-rw-r--r--src/cuchaz/enigma/mapping/MappingParseException.java29
-rw-r--r--src/cuchaz/enigma/mapping/Mappings.java216
-rw-r--r--src/cuchaz/enigma/mapping/MappingsChecker.java107
-rw-r--r--src/cuchaz/enigma/mapping/MappingsReader.java134
-rw-r--r--src/cuchaz/enigma/mapping/MappingsRenamer.java237
-rw-r--r--src/cuchaz/enigma/mapping/MappingsWriter.java88
-rw-r--r--src/cuchaz/enigma/mapping/MemberMapping.java17
-rw-r--r--src/cuchaz/enigma/mapping/MethodEntry.java104
-rw-r--r--src/cuchaz/enigma/mapping/MethodMapping.java191
-rw-r--r--src/cuchaz/enigma/mapping/NameValidator.java80
-rw-r--r--src/cuchaz/enigma/mapping/ProcyonEntryFactory.java55
-rw-r--r--src/cuchaz/enigma/mapping/Signature.java117
-rw-r--r--src/cuchaz/enigma/mapping/SignatureUpdater.java94
-rw-r--r--src/cuchaz/enigma/mapping/TranslationDirection.java29
-rw-r--r--src/cuchaz/enigma/mapping/Translator.java289
-rw-r--r--src/cuchaz/enigma/mapping/Type.java247
-rw-r--r--test/cuchaz/enigma/TestDeobfed.java95
-rw-r--r--test/cuchaz/enigma/TestDeobfuscator.java57
-rw-r--r--test/cuchaz/enigma/TestEntryFactory.java67
-rw-r--r--test/cuchaz/enigma/TestInnerClasses.java132
-rw-r--r--test/cuchaz/enigma/TestJarIndexConstructorReferences.java124
-rw-r--r--test/cuchaz/enigma/TestJarIndexInheritanceTree.java228
-rw-r--r--test/cuchaz/enigma/TestJarIndexLoneClass.java164
-rw-r--r--test/cuchaz/enigma/TestSignature.java268
-rw-r--r--test/cuchaz/enigma/TestSourceIndex.java67
-rw-r--r--test/cuchaz/enigma/TestTokensConstructors.java136
-rw-r--r--test/cuchaz/enigma/TestTranslator.java170
-rw-r--r--test/cuchaz/enigma/TestType.java243
-rw-r--r--test/cuchaz/enigma/TokenChecker.java65
-rw-r--r--test/cuchaz/enigma/inputs/Keep.java17
-rw-r--r--test/cuchaz/enigma/inputs/constructors/BaseClass.java25
-rw-r--r--test/cuchaz/enigma/inputs/constructors/Caller.java57
-rw-r--r--test/cuchaz/enigma/inputs/constructors/DefaultConstructable.java15
-rw-r--r--test/cuchaz/enigma/inputs/constructors/SubClass.java38
-rw-r--r--test/cuchaz/enigma/inputs/constructors/SubSubClass.java21
-rw-r--r--test/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java31
-rw-r--r--test/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java21
-rw-r--r--test/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java40
-rw-r--r--test/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java34
-rw-r--r--test/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java24
-rw-r--r--test/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java23
-rw-r--r--test/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java30
-rw-r--r--test/cuchaz/enigma/inputs/innerClasses/D_Simple.java18
-rw-r--r--test/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java31
-rw-r--r--test/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java30
-rw-r--r--test/cuchaz/enigma/inputs/loneClass/LoneClass.java24
-rw-r--r--test/cuchaz/enigma/inputs/translation/A_Basic.java32
-rw-r--r--test/cuchaz/enigma/inputs/translation/B_BaseClass.java25
-rw-r--r--test/cuchaz/enigma/inputs/translation/C_SubClass.java27
-rw-r--r--test/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java28
-rw-r--r--test/cuchaz/enigma/inputs/translation/E_Bridges.java32
-rw-r--r--test/cuchaz/enigma/inputs/translation/F_ObjectMethods.java29
-rw-r--r--test/cuchaz/enigma/inputs/translation/G_OuterClass.java36
-rw-r--r--test/cuchaz/enigma/inputs/translation/H_NamelessClass.java38
-rw-r--r--test/cuchaz/enigma/inputs/translation/I_Generics.java35
-rw-r--r--test/cuchaz/enigma/resources/translation.mappings41
167 files changed, 20750 insertions, 0 deletions
diff --git a/.classpath b/.classpath
new file mode 100644
index 00000000..389aeb52
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<classpath>
3 <classpathentry kind="src" path="src"/>
4 <classpathentry kind="src" path="conf"/>
5 <classpathentry kind="src" path="test"/>
6 <classpathentry kind="lib" path="lib/deps.jar"/>
7 <classpathentry kind="lib" path="lib/test-deps.jar"/>
8 <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/java-7-openjdk-amd64"/>
9 <classpathentry kind="output" path="bin"/>
10</classpath>
diff --git a/.hgignore b/.hgignore
new file mode 100644
index 00000000..d691e2d7
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,13 @@
1
2syntax: glob
3bin
4lib
5out
6build
7*.iml
8.idea
9.gradle
10data
11input
12ivy
13*.pyc \ No newline at end of file
diff --git a/.project b/.project
new file mode 100644
index 00000000..08dff6c0
--- /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 00000000..b5d234f2
--- /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.gradle b/build.gradle
new file mode 100644
index 00000000..48ad4272
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,176 @@
1// Do it this way so people with older Gradle can hopefully still build.
2buildscript {
3 repositories { jcenter() }
4
5 dependencies {
6 classpath 'com.github.jengelman.gradle.plugins:shadow:1.2.1'
7 }
8}
9
10apply plugin: 'java'
11apply plugin: 'eclipse'
12apply plugin: 'com.github.johnrengelman.shadow'
13apply plugin: 'maven'
14
15group = 'cuchaz'
16version = '0.10.4b'
17
18sourceCompatibility = 1.7
19targetCompatibility = 1.7
20
21// Custom source layout
22sourceSets {
23 main {
24 java { srcDir 'src' }
25 resources { srcDir 'conf' }
26 }
27 test {
28 java { srcDir 'test' }
29 resources { srcDir 'test' }
30 }
31}
32
33repositories {
34 mavenLocal()
35 mavenCentral()
36
37 maven {
38 name "Cuchaz Custom Repository"
39 url 'http://maven.cuchazinteractive.com'
40 }
41}
42
43configurations {
44 proGuard // used to download ProGuard
45 application // used for JSyntaxPane so it's not in the library POM
46
47 compile.extendsFrom application
48}
49
50dependencies {
51 compile 'com.google.guava:guava:17.+'
52 compile 'org.javassist:javassist:3.+'
53 compile 'org.bitbucket.mstrobel:procyon-decompiler:0.5.28-enigma'
54 application 'de.sciss:syntaxpane:1.1.+'
55
56 testCompile 'junit:junit:4.+'
57 testCompile 'org.hamcrest:hamcrest-all:1.+'
58
59 proGuard 'net.sf.proguard:proguard-base:5.+'
60}
61
62// For each set of test inputs, create an output jar and obfuscate it.
63file('test/cuchaz/enigma/inputs').listFiles().each {theFile ->
64 if (theFile.directory) {
65 task("${theFile.name}TestJar", type: Jar, dependsOn: testClasses) {
66 from ('build/classes/test') {
67 include 'cuchaz/enigma/inputs/$theFile.name/**/*.class'
68 include 'cuchaz/enigma/inputs/Keep.class'
69 }
70
71 archiveName = theFile.name + '.jar'
72 destinationDir = file('build/test-inputs')
73 }
74
75 task ("${theFile.name}TestObf", type: JavaExec,
76 dependsOn: "${theFile.name}TestJar") {
77 main 'proguard.ProGuard'
78 classpath configurations.proGuard
79
80 args '@proguard-test.conf', '-injars', file('build/test-inputs/' +
81 "${theFile.name}.jar"), '-outjars', file('build/test-obf/' +
82 "${theFile.name}.jar")
83 }
84
85 test.dependsOn "${theFile.name}TestObf"
86 }
87}
88
89// We also semi-deobfuscate translation.jar to then test it... yeah... oh well.
90task ('deobfTranslationInput', type: JavaExec, dependsOn: 'translationTestObf')
91{
92 classpath sourceSets.main.runtimeClasspath
93 main 'cuchaz.enigma.CommandMain'
94 args 'deobfuscate', file('build/test-obf/translation.jar'),
95 file('build/test-deobf/translation.jar')
96}
97test.dependsOn 'deobfTranslationInput'
98
99test {
100 // Since the Minecraft test is really long (like 10 minutes D:) we turn it
101 // off by default.
102 if (!System.getProperty('enableExtremelySlowMinecraftTest', '')
103 .equalsIgnoreCase('true')) {
104 exclude 'cuchaz/enigma/TestSourceIndex.class'
105 }
106
107 // Allow people to specify a custom path to their Minecraft directory.
108 // (Example: `gradle build -Denigma.test.minecraftdir=./`)
109 systemProperties = [
110 'enigma.test.minecraftdir': System.getProperty('test.minecraftdir')
111 ]
112}
113
114// Set the main class.
115jar.manifest.attributes 'Main-Class': 'cuchaz.enigma.Main'
116
117// Make the "fat" application jar. This is useful to just throw in a classpath
118// for tests, though it includes some slightly useless stuff.
119shadowJar {
120 append 'license.LGPL3.txt'
121 append 'license.APL2.txt'
122 append 'readme.txt'
123
124 exclude 'META-INF/maven/**'
125}
126
127// Now make the deployable application jar with extra classes stripped out using
128// ProGuard (don't use JavaExec because we want to redirect output to a file).
129task('thinJar', dependsOn: shadowJar) << {
130 javaexec {
131 main 'proguard.ProGuard'
132 classpath configurations.proGuard
133
134 args '@proguard-build.conf', '-injars', shadowJar.archivePath,
135 // well this works...
136 '-outjars', file("build/libs/$project.name-${version}-thin.jar")
137
138 // Cut down on console spam
139 standardOutput new File(buildDir, 'proguard.log').newOutputStream();
140 }
141 println 'Saved ProGuard output to build/proguard.log'
142}
143
144// Create a library jar, containing only the deobfuscation code, for use at
145// runtime. This will be deployed to Maven Local with a POM, and can be uploaded
146// to a remote server manually (for now anyway).
147task('libJar', type: Jar, dependsOn: classes) {
148 baseName = 'enigma-lib'
149
150 from("$buildDir/classes/main") {
151 exclude 'cuchaz/enigma/gui/**'
152 exclude 'cuchaz/enigma/convert/**'
153
154 // Main classes + inner classes (keep CommandMain)
155 exclude 'cuchaz/enigma/Main.class'
156 exclude 'cuchaz/enigma/Main.class'
157 exclude 'cuchaz/enigma/ConvertMain*.class'
158 }
159}
160artifacts.archives libJar { name 'enigma-lib' }
161
162// Here we need a bit of hackery to remove the default Maven deployment and just
163// deploy enigma-lib. Works, but not ideal :(
164configurations.archives {
165 artifacts.iterator().with {
166 while(it.hasNext()) {
167 if (it.next().name == 'enigma') {
168 it.remove()
169 }
170 }
171 }
172}
173
174// And finally, make the build generate / install the jars.
175assemble.dependsOn thinJar
176build.dependsOn install \ No newline at end of file
diff --git a/build.py b/build.py
new file mode 100644
index 00000000..40da3271
--- /dev/null
+++ b/build.py
@@ -0,0 +1,131 @@
1
2import os
3import sys
4
5# settings
6PathSsjb = "../ssjb"
7Author = "Cuchaz"
8Version = "0.10.4b"
9
10DirBin = "bin"
11DirLib = "lib"
12DirBuild = "build"
13PathLocalMavenRepo = "../maven"
14
15
16# import ssjb
17sys.path.insert(0, PathSsjb)
18import ssjb
19import ssjb.ivy
20
21
22ArtifactStandalone = ssjb.ivy.Dep("cuchaz:enigma:%s" % Version)
23ArtifactLib = ssjb.ivy.Dep("cuchaz:enigma-lib:%s" % Version)
24
25# dependencies
26ExtraRepos = [
27 "http://maven.cuchazinteractive.com"
28]
29LibDeps = [
30 ssjb.ivy.Dep("com.google.guava:guava:17.0"),
31 ssjb.ivy.Dep("org.javassist:javassist:3.19.0-GA"),
32 ssjb.ivy.Dep("org.bitbucket.mstrobel:procyon-decompiler:0.5.28-enigma")
33]
34StandaloneDeps = LibDeps + [
35 ssjb.ivy.Dep("de.sciss:syntaxpane:1.1.4")
36]
37ProguardDep = ssjb.ivy.Dep("net.sf.proguard:proguard-base:5.1")
38TestDeps = [
39 ssjb.ivy.Dep("junit:junit:4.12"),
40 ssjb.ivy.Dep("org.hamcrest:hamcrest-all:1.3")
41]
42
43# functions
44
45def buildTestJar(name, glob):
46
47 pathJar = os.path.join(DirBuild, "test-inputs/%s.jar" % name)
48 pathObfJar = os.path.join(DirBuild, "test-obf/%s.jar" % name)
49
50 # build the unobf jar
51 with ssjb.file.TempDir("tmp") as dirTemp:
52 ssjb.file.copyTree(dirTemp, DirBin, ssjb.file.find(DirBin, "cuchaz/enigma/inputs/Keep.class"))
53 ssjb.file.copyTree(dirTemp, DirBin, ssjb.file.find(DirBin, glob))
54 ssjb.jar.makeJar(pathJar, dirTemp)
55
56 # build the obf jar
57 ssjb.callJavaJar(
58 os.path.join(DirLib, "proguard.jar"),
59 ["@proguard-test.conf", "-injars", pathJar, "-outjars", pathObfJar]
60 )
61
62def buildDeobfTestJar(outPath, inPath):
63 ssjb.callJava(
64 [DirBin, os.path.join(DirLib, "deps.jar")],
65 "cuchaz.enigma.CommandMain",
66 ["deobfuscate", inPath, outPath]
67 )
68
69def applyReadme(dirTemp):
70 ssjb.file.copy(dirTemp, "license.APL2.txt")
71 ssjb.file.copy(dirTemp, "license.LGPL3.txt")
72 ssjb.file.copy(dirTemp, "readme.txt")
73
74def buildStandaloneJar(dirOut):
75 with ssjb.file.TempDir(os.path.join(dirOut, "tmp")) as dirTemp:
76 ssjb.file.copyTree(dirTemp, DirBin, ssjb.file.find(DirBin))
77 for path in ssjb.ivy.getJarPaths(StandaloneDeps, ExtraRepos):
78 ssjb.jar.unpackJar(dirTemp, path)
79 ssjb.file.delete(os.path.join(dirTemp, "LICENSE.txt"))
80 ssjb.file.delete(os.path.join(dirTemp, "META-INF/maven"))
81 applyReadme(dirTemp)
82 manifest = ssjb.jar.buildManifest(
83 ArtifactStandalone.artifactId,
84 ArtifactStandalone.version,
85 Author,
86 "cuchaz.enigma.Main"
87 )
88 pathJar = os.path.join(DirBuild, "%s.jar" % ArtifactStandalone.getName())
89 ssjb.jar.makeJar(pathJar, dirTemp, manifest=manifest)
90 ssjb.ivy.deployJarToLocalMavenRepo(PathLocalMavenRepo, pathJar, ArtifactStandalone)
91
92def buildLibJar(dirOut):
93 with ssjb.file.TempDir(os.path.join(dirOut, "tmp")) as dirTemp:
94 ssjb.file.copyTree(dirTemp, DirBin, ssjb.file.find(DirBin))
95 applyReadme(dirTemp)
96 pathJar = os.path.join(DirBuild, "%s.jar" % ArtifactLib.getName())
97 ssjb.jar.makeJar(pathJar, dirTemp)
98 ssjb.ivy.deployJarToLocalMavenRepo(PathLocalMavenRepo, pathJar, ArtifactLib, deps=LibDeps)
99
100
101# tasks
102
103def taskGetDeps():
104 ssjb.file.mkdir(DirLib)
105 ssjb.ivy.makeLibsJar(os.path.join(DirLib, "deps.jar"), StandaloneDeps, extraRepos=ExtraRepos)
106 ssjb.ivy.makeLibsJar(os.path.join(DirLib, "test-deps.jar"), TestDeps)
107 ssjb.ivy.makeJar(os.path.join(DirLib, "proguard.jar"), ProguardDep)
108
109def taskBuildTestJars():
110 buildTestJar("loneClass", "cuchaz/enigma/inputs/loneClass/*.class")
111 buildTestJar("constructors", "cuchaz/enigma/inputs/constructors/*.class")
112 buildTestJar("inheritanceTree", "cuchaz/enigma/inputs/inheritanceTree/*.class")
113 buildTestJar("innerClasses", "cuchaz/enigma/inputs/innerClasses/*.class")
114 taskBuildTranslationTestJar()
115
116def taskBuildTranslationTestJar():
117 buildTestJar("translation", "cuchaz/enigma/inputs/translation/*.class")
118 buildDeobfTestJar(os.path.join(DirBuild, "test-deobf/translation.jar"), os.path.join(DirBuild, "test-obf/translation.jar"))
119
120def taskBuild():
121 ssjb.file.delete(DirBuild)
122 ssjb.file.mkdir(DirBuild)
123 buildStandaloneJar(DirBuild)
124 buildLibJar(DirBuild)
125
126ssjb.registerTask("getDeps", taskGetDeps)
127ssjb.registerTask("buildTestJars", taskBuildTestJars)
128ssjb.registerTask("buildTranslationTestJar", taskBuildTranslationTestJar)
129ssjb.registerTask("build", taskBuild)
130ssjb.run()
131
diff --git a/conf/about.html b/conf/about.html
new file mode 100644
index 00000000..b75c1bf0
--- /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 00000000..a453e432
--- /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.LGPL3.txt b/license.LGPL3.txt
new file mode 100644
index 00000000..65c5ca88
--- /dev/null
+++ b/license.LGPL3.txt
@@ -0,0 +1,165 @@
1 GNU LESSER 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
9 This version of the GNU Lesser General Public License incorporates
10the terms and conditions of version 3 of the GNU General Public
11License, supplemented by the additional permissions listed below.
12
13 0. Additional Definitions.
14
15 As used herein, "this License" refers to version 3 of the GNU Lesser
16General Public License, and the "GNU GPL" refers to version 3 of the GNU
17General Public License.
18
19 "The Library" refers to a covered work governed by this License,
20other than an Application or a Combined Work as defined below.
21
22 An "Application" is any work that makes use of an interface provided
23by the Library, but which is not otherwise based on the Library.
24Defining a subclass of a class defined by the Library is deemed a mode
25of using an interface provided by the Library.
26
27 A "Combined Work" is a work produced by combining or linking an
28Application with the Library. The particular version of the Library
29with which the Combined Work was made is also called the "Linked
30Version".
31
32 The "Minimal Corresponding Source" for a Combined Work means the
33Corresponding Source for the Combined Work, excluding any source code
34for portions of the Combined Work that, considered in isolation, are
35based on the Application, and not on the Linked Version.
36
37 The "Corresponding Application Code" for a Combined Work means the
38object code and/or source code for the Application, including any data
39and utility programs needed for reproducing the Combined Work from the
40Application, but excluding the System Libraries of the Combined Work.
41
42 1. Exception to Section 3 of the GNU GPL.
43
44 You may convey a covered work under sections 3 and 4 of this License
45without being bound by section 3 of the GNU GPL.
46
47 2. Conveying Modified Versions.
48
49 If you modify a copy of the Library, and, in your modifications, a
50facility refers to a function or data to be supplied by an Application
51that uses the facility (other than as an argument passed when the
52facility is invoked), then you may convey a copy of the modified
53version:
54
55 a) under this License, provided that you make a good faith effort to
56 ensure that, in the event an Application does not supply the
57 function or data, the facility still operates, and performs
58 whatever part of its purpose remains meaningful, or
59
60 b) under the GNU GPL, with none of the additional permissions of
61 this License applicable to that copy.
62
63 3. Object Code Incorporating Material from Library Header Files.
64
65 The object code form of an Application may incorporate material from
66a header file that is part of the Library. You may convey such object
67code under terms of your choice, provided that, if the incorporated
68material is not limited to numerical parameters, data structure
69layouts and accessors, or small macros, inline functions and templates
70(ten or fewer lines in length), you do both of the following:
71
72 a) Give prominent notice with each copy of the object code that the
73 Library is used in it and that the Library and its use are
74 covered by this License.
75
76 b) Accompany the object code with a copy of the GNU GPL and this license
77 document.
78
79 4. Combined Works.
80
81 You may convey a Combined Work under terms of your choice that,
82taken together, effectively do not restrict modification of the
83portions of the Library contained in the Combined Work and reverse
84engineering for debugging such modifications, if you also do each of
85the following:
86
87 a) Give prominent notice with each copy of the Combined Work that
88 the Library is used in it and that the Library and its use are
89 covered by this License.
90
91 b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 document.
93
94 c) For a Combined Work that displays copyright notices during
95 execution, include the copyright notice for the Library among
96 these notices, as well as a reference directing the user to the
97 copies of the GNU GPL and this license document.
98
99 d) Do one of the following:
100
101 0) Convey the Minimal Corresponding Source under the terms of this
102 License, and the Corresponding Application Code in a form
103 suitable for, and under terms that permit, the user to
104 recombine or relink the Application with a modified version of
105 the Linked Version to produce a modified Combined Work, in the
106 manner specified by section 6 of the GNU GPL for conveying
107 Corresponding Source.
108
109 1) Use a suitable shared library mechanism for linking with the
110 Library. A suitable mechanism is one that (a) uses at run time
111 a copy of the Library already present on the user's computer
112 system, and (b) will operate properly with a modified version
113 of the Library that is interface-compatible with the Linked
114 Version.
115
116 e) Provide Installation Information, but only if you would otherwise
117 be required to provide such information under section 6 of the
118 GNU GPL, and only to the extent that such information is
119 necessary to install and execute a modified version of the
120 Combined Work produced by recombining or relinking the
121 Application with a modified version of the Linked Version. (If
122 you use option 4d0, the Installation Information must accompany
123 the Minimal Corresponding Source and Corresponding Application
124 Code. If you use option 4d1, you must provide the Installation
125 Information in the manner specified by section 6 of the GNU GPL
126 for conveying Corresponding Source.)
127
128 5. Combined Libraries.
129
130 You may place library facilities that are a work based on the
131Library side by side in a single library together with other library
132facilities that are not Applications and are not covered by this
133License, and convey such a combined library under terms of your
134choice, if you do both of the following:
135
136 a) Accompany the combined library with a copy of the same work based
137 on the Library, uncombined with any other library facilities,
138 conveyed under the terms of this License.
139
140 b) Give prominent notice with the combined library that part of it
141 is a work based on the Library, and explaining where to find the
142 accompanying uncombined form of the same work.
143
144 6. Revised Versions of the GNU Lesser General Public License.
145
146 The Free Software Foundation may publish revised and/or new versions
147of the GNU Lesser General Public License from time to time. Such new
148versions will be similar in spirit to the present version, but may
149differ in detail to address new problems or concerns.
150
151 Each version is given a distinguishing version number. If the
152Library as you received it specifies that a certain numbered version
153of the GNU Lesser General Public License "or any later version"
154applies to it, you have the option of following the terms and
155conditions either of that published version or of any later version
156published by the Free Software Foundation. If the Library as you
157received it does not specify a version number of the GNU Lesser
158General Public License, you may choose any version of the GNU Lesser
159General Public License ever published by the Free Software Foundation.
160
161 If the Library as you received it specifies that a proxy can decide
162whether future versions of the GNU Lesser General Public License shall
163apply, that proxy's public statement of acceptance of any version is
164permanent authorization for you to choose that version for the
165Library.
diff --git a/proguard-build.conf b/proguard-build.conf
new file mode 100644
index 00000000..195b84db
--- /dev/null
+++ b/proguard-build.conf
@@ -0,0 +1,8 @@
1-libraryjars <java.home>/lib/rt.jar
2-dontoptimize
3-dontobfuscate
4-dontwarn
5-keep class cuchaz.enigma.Main { static void main(java.lang.String[]); }
6-keep class cuchaz.enigma.CommandMain { static void main(java.lang.String[]); }
7-keep class cuchaz.enigma.ConvertMain { static void main(java.lang.String[]); }
8-keep class de.sciss.syntaxpane.** { *; } \ No newline at end of file
diff --git a/proguard-test.conf b/proguard-test.conf
new file mode 100644
index 00000000..0d3d60e9
--- /dev/null
+++ b/proguard-test.conf
@@ -0,0 +1,9 @@
1-libraryjars <java.home>/lib/rt.jar
2-overloadaggressively
3-repackageclasses
4-allowaccessmodification
5-dontoptimize
6-dontshrink
7-keepparameternames
8-keepattributes
9-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 00000000..47277d91
--- /dev/null
+++ b/readme.txt
@@ -0,0 +1,33 @@
1
2Enigma v0.10.4 beta
3A tool for deobfuscation of Java bytecode
4
5Copyright Jeff Martin, 2015
6
7
8LICENSE
9
10Enigma is distributed under the GNU Lesser 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 Lesser 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
29
30
31OPEN SOURCE
32
33https://bitbucket.org/cuchaz/enigma
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 00000000..8fa1712a
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
rootProject.name = 'enigma' \ No newline at end of file
diff --git a/src/cuchaz/enigma/CommandMain.java b/src/cuchaz/enigma/CommandMain.java
new file mode 100644
index 00000000..540cfb95
--- /dev/null
+++ b/src/cuchaz/enigma/CommandMain.java
@@ -0,0 +1,186 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.io.FileReader;
15import java.util.jar.JarFile;
16
17import cuchaz.enigma.Deobfuscator.ProgressListener;
18import cuchaz.enigma.mapping.Mappings;
19import cuchaz.enigma.mapping.MappingsReader;
20
21public class CommandMain {
22
23 public static class ConsoleProgressListener implements ProgressListener {
24
25 private static final int ReportTime = 5000; // 5s
26
27 private int m_totalWork;
28 private long m_startTime;
29 private long m_lastReportTime;
30
31 @Override
32 public void init(int totalWork, String title) {
33 m_totalWork = totalWork;
34 m_startTime = System.currentTimeMillis();
35 m_lastReportTime = m_startTime;
36 System.out.println(title);
37 }
38
39 @Override
40 public void onProgress(int numDone, String message) {
41
42 long now = System.currentTimeMillis();
43 boolean isLastUpdate = numDone == m_totalWork;
44 boolean shouldReport = isLastUpdate || now - m_lastReportTime > ReportTime;
45
46 if (shouldReport) {
47 int percent = numDone*100/m_totalWork;
48 System.out.println(String.format("\tProgress: %3d%%", percent));
49 m_lastReportTime = now;
50 }
51 if (isLastUpdate) {
52 double elapsedSeconds = (now - m_startTime)/1000;
53 System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds));
54 }
55 }
56 }
57
58 public static void main(String[] args)
59 throws Exception {
60
61 try {
62
63 // process the command
64 String command = getArg(args, 0, "command", true);
65 if (command.equalsIgnoreCase("deobfuscate")) {
66 deobfuscate(args);
67 } else if (command.equalsIgnoreCase("decompile")) {
68 decompile(args);
69 } else if (command.equalsIgnoreCase("protectify")) {
70 protectify(args);
71 } else if (command.equalsIgnoreCase("publify")) {
72 publify(args);
73 } else {
74 throw new IllegalArgumentException("Command not recognized: " + command);
75 }
76 } catch (IllegalArgumentException ex) {
77 System.out.println(ex.getMessage());
78 printHelp();
79 }
80 }
81
82 private static void printHelp() {
83 System.out.println(String.format("%s - %s", Constants.Name, Constants.Version));
84 System.out.println("Usage:");
85 System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain <command>");
86 System.out.println("\twhere <command> is one of:");
87 System.out.println("\t\tdeobfuscate <in jar> <out jar> [<mappings file>]");
88 System.out.println("\t\tdecompile <in jar> <out folder> [<mappings file>]");
89 System.out.println("\t\tprotectify <in jar> <out jar>");
90 }
91
92 private static void decompile(String[] args)
93 throws Exception {
94 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
95 File fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true));
96 File fileMappings = getReadableFile(getArg(args, 3, "mappings file", false));
97 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn));
98 deobfuscator.writeSources(fileJarOut, new ConsoleProgressListener());
99 }
100
101 private static void deobfuscate(String[] args)
102 throws Exception {
103 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
104 File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true));
105 File fileMappings = getReadableFile(getArg(args, 3, "mappings file", false));
106 Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn));
107 deobfuscator.writeJar(fileJarOut, new ConsoleProgressListener());
108 }
109
110 private static void protectify(String[] args)
111 throws Exception {
112 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
113 File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true));
114 Deobfuscator deobfuscator = getDeobfuscator(null, new JarFile(fileJarIn));
115 deobfuscator.protectifyJar(fileJarOut, new ConsoleProgressListener());
116 }
117
118 private static void publify(String[] args)
119 throws Exception {
120 File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true));
121 File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true));
122 Deobfuscator deobfuscator = getDeobfuscator(null, new JarFile(fileJarIn));
123 deobfuscator.publifyJar(fileJarOut, new ConsoleProgressListener());
124 }
125
126 private static Deobfuscator getDeobfuscator(File fileMappings, JarFile jar)
127 throws Exception {
128 System.out.println("Reading jar...");
129 Deobfuscator deobfuscator = new Deobfuscator(jar);
130 if (fileMappings != null) {
131 System.out.println("Reading mappings...");
132 Mappings mappings = new MappingsReader().read(new FileReader(fileMappings));
133 deobfuscator.setMappings(mappings);
134 }
135 return deobfuscator;
136 }
137
138 private static String getArg(String[] args, int i, String name, boolean required) {
139 if (i >= args.length) {
140 if (required) {
141 throw new IllegalArgumentException(name + " is required");
142 } else {
143 return null;
144 }
145 }
146 return args[i];
147 }
148
149 private static File getWritableFile(String path) {
150 if (path == null) {
151 return null;
152 }
153 File file = new File(path).getAbsoluteFile();
154 File dir = file.getParentFile();
155 if (dir == null) {
156 throw new IllegalArgumentException("Cannot write to folder: " + dir);
157 }
158 // quick fix to avoid stupid stuff in Gradle code
159 if (!dir.isDirectory()) {
160 dir.mkdirs();
161 }
162 return file;
163 }
164
165 private static File getWritableFolder(String path) {
166 if (path == null) {
167 return null;
168 }
169 File dir = new File(path).getAbsoluteFile();
170 if (!dir.exists()) {
171 throw new IllegalArgumentException("Cannot write to folder: " + dir);
172 }
173 return dir;
174 }
175
176 private static File getReadableFile(String path) {
177 if (path == null) {
178 return null;
179 }
180 File file = new File(path).getAbsoluteFile();
181 if (!file.exists()) {
182 throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath());
183 }
184 return file;
185 }
186}
diff --git a/src/cuchaz/enigma/Constants.java b/src/cuchaz/enigma/Constants.java
new file mode 100644
index 00000000..951fa8f3
--- /dev/null
+++ b/src/cuchaz/enigma/Constants.java
@@ -0,0 +1,20 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13public class Constants {
14 public static final String Name = "Enigma";
15 public static final String Version = "0.10.4 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/ConvertMain.java b/src/cuchaz/enigma/ConvertMain.java
new file mode 100644
index 00000000..17bd2f80
--- /dev/null
+++ b/src/cuchaz/enigma/ConvertMain.java
@@ -0,0 +1,322 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.io.FileReader;
15import java.io.FileWriter;
16import java.io.IOException;
17import java.util.jar.JarFile;
18
19import cuchaz.enigma.convert.ClassMatches;
20import cuchaz.enigma.convert.MappingsConverter;
21import cuchaz.enigma.convert.MatchesReader;
22import cuchaz.enigma.convert.MatchesWriter;
23import cuchaz.enigma.convert.MemberMatches;
24import cuchaz.enigma.gui.ClassMatchingGui;
25import cuchaz.enigma.gui.MemberMatchingGui;
26import cuchaz.enigma.mapping.BehaviorEntry;
27import cuchaz.enigma.mapping.ClassEntry;
28import cuchaz.enigma.mapping.ClassMapping;
29import cuchaz.enigma.mapping.FieldEntry;
30import cuchaz.enigma.mapping.FieldMapping;
31import cuchaz.enigma.mapping.MappingParseException;
32import cuchaz.enigma.mapping.Mappings;
33import cuchaz.enigma.mapping.MappingsChecker;
34import cuchaz.enigma.mapping.MappingsReader;
35import cuchaz.enigma.mapping.MappingsWriter;
36import cuchaz.enigma.mapping.MethodMapping;
37
38
39public class ConvertMain {
40
41 public static void main(String[] args)
42 throws IOException, MappingParseException {
43
44 // init files
45 File home = new File(System.getProperty("user.home"));
46 JarFile sourceJar = new JarFile(new File(home, ".minecraft/versions/1.8/1.8.jar"));
47 JarFile destJar = new JarFile(new File(home, ".minecraft/versions/1.8.3/1.8.3.jar"));
48 File inMappingsFile = new File("../Enigma Mappings/1.8.mappings");
49 File outMappingsFile = new File("../Enigma Mappings/1.8.3.mappings");
50 Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile));
51 File classMatchesFile = new File(inMappingsFile.getName() + ".class.matches");
52 File fieldMatchesFile = new File(inMappingsFile.getName() + ".field.matches");
53 File methodMatchesFile = new File(inMappingsFile.getName() + ".method.matches");
54
55 // match classes
56 //computeClassMatches(classMatchesFile, sourceJar, destJar, mappings);
57 //editClasssMatches(classMatchesFile, sourceJar, destJar, mappings);
58 //convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile);
59
60 // match fields
61 //computeFieldMatches(fieldMatchesFile, destJar, outMappingsFile, classMatchesFile);
62 //editFieldMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, fieldMatchesFile);
63 //convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile);
64
65 // match methods/constructors
66 //computeMethodMatches(methodMatchesFile, destJar, outMappingsFile, classMatchesFile);
67 //editMethodMatches(sourceJar, destJar, outMappingsFile, mappings, classMatchesFile, methodMatchesFile);
68 convertMappings(outMappingsFile, sourceJar, destJar, mappings, classMatchesFile, fieldMatchesFile, methodMatchesFile);
69 }
70
71 private static void computeClassMatches(File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings)
72 throws IOException {
73 ClassMatches classMatches = MappingsConverter.computeClassMatches(sourceJar, destJar, mappings);
74 MatchesWriter.writeClasses(classMatches, classMatchesFile);
75 System.out.println("Wrote:\n\t" + classMatchesFile.getAbsolutePath());
76 }
77
78 private static void editClasssMatches(final File classMatchesFile, JarFile sourceJar, JarFile destJar, Mappings mappings)
79 throws IOException {
80 System.out.println("Reading class matches...");
81 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
82 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
83 deobfuscators.source.setMappings(mappings);
84 System.out.println("Starting GUI...");
85 new ClassMatchingGui(classMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new ClassMatchingGui.SaveListener() {
86 @Override
87 public void save(ClassMatches matches) {
88 try {
89 MatchesWriter.writeClasses(matches, classMatchesFile);
90 } catch (IOException ex) {
91 throw new Error(ex);
92 }
93 }
94 });
95 }
96
97 private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile)
98 throws IOException {
99 System.out.println("Reading class matches...");
100 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
101 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
102 deobfuscators.source.setMappings(mappings);
103
104 Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
105
106 try (FileWriter out = new FileWriter(outMappingsFile)) {
107 new MappingsWriter().write(out, newMappings);
108 }
109 System.out.println("Write converted mappings to: " + outMappingsFile.getAbsolutePath());
110 }
111
112 private static void computeFieldMatches(File memberMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile)
113 throws IOException, MappingParseException {
114
115 System.out.println("Reading class matches...");
116 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
117 System.out.println("Reading mappings...");
118 Mappings destMappings = new MappingsReader().read(new FileReader(destMappingsFile));
119 System.out.println("Indexing dest jar...");
120 Deobfuscator destDeobfuscator = new Deobfuscator(destJar);
121
122 System.out.println("Writing matches...");
123
124 // get the matched and unmatched mappings
125 MemberMatches<FieldEntry> fieldMatches = MappingsConverter.computeMemberMatches(
126 destDeobfuscator,
127 destMappings,
128 classMatches,
129 MappingsConverter.getFieldDoer()
130 );
131
132 MatchesWriter.writeMembers(fieldMatches, memberMatchesFile);
133 System.out.println("Wrote:\n\t" + memberMatchesFile.getAbsolutePath());
134 }
135
136 private static void editFieldMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File fieldMatchesFile)
137 throws IOException, MappingParseException {
138
139 System.out.println("Reading matches...");
140 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
141 MemberMatches<FieldEntry> fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
142
143 // prep deobfuscators
144 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
145 deobfuscators.source.setMappings(sourceMappings);
146 Mappings destMappings = new MappingsReader().read(new FileReader(destMappingsFile));
147 MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
148 checker.dropBrokenMappings(destMappings);
149 deobfuscators.dest.setMappings(destMappings);
150
151 new MemberMatchingGui<FieldEntry>(classMatches, fieldMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new MemberMatchingGui.SaveListener<FieldEntry>() {
152 @Override
153 public void save(MemberMatches<FieldEntry> matches) {
154 try {
155 MatchesWriter.writeMembers(matches, fieldMatchesFile);
156 } catch (IOException ex) {
157 throw new Error(ex);
158 }
159 }
160 });
161 }
162
163 private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile)
164 throws IOException {
165
166 System.out.println("Reading matches...");
167 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
168 MemberMatches<FieldEntry> fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
169
170 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
171 deobfuscators.source.setMappings(mappings);
172
173 // apply matches
174 Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
175 MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer());
176
177 // write out the converted mappings
178 try (FileWriter out = new FileWriter(outMappingsFile)) {
179 new MappingsWriter().write(out, newMappings);
180 }
181 System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
182 }
183
184
185 private static void computeMethodMatches(File methodMatchesFile, JarFile destJar, File destMappingsFile, File classMatchesFile)
186 throws IOException, MappingParseException {
187
188 System.out.println("Reading class matches...");
189 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
190 System.out.println("Reading mappings...");
191 Mappings destMappings = new MappingsReader().read(new FileReader(destMappingsFile));
192 System.out.println("Indexing dest jar...");
193 Deobfuscator destDeobfuscator = new Deobfuscator(destJar);
194
195 System.out.println("Writing method matches...");
196
197 // get the matched and unmatched mappings
198 MemberMatches<BehaviorEntry> methodMatches = MappingsConverter.computeMemberMatches(
199 destDeobfuscator,
200 destMappings,
201 classMatches,
202 MappingsConverter.getMethodDoer()
203 );
204
205 MatchesWriter.writeMembers(methodMatches, methodMatchesFile);
206 System.out.println("Wrote:\n\t" + methodMatchesFile.getAbsolutePath());
207 }
208
209 private static void editMethodMatches(JarFile sourceJar, JarFile destJar, File destMappingsFile, Mappings sourceMappings, File classMatchesFile, final File methodMatchesFile)
210 throws IOException, MappingParseException {
211
212 System.out.println("Reading matches...");
213 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
214 MemberMatches<BehaviorEntry> methodMatches = MatchesReader.readMembers(methodMatchesFile);
215
216 // prep deobfuscators
217 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
218 deobfuscators.source.setMappings(sourceMappings);
219 Mappings destMappings = new MappingsReader().read(new FileReader(destMappingsFile));
220 MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
221 checker.dropBrokenMappings(destMappings);
222 deobfuscators.dest.setMappings(destMappings);
223
224 new MemberMatchingGui<BehaviorEntry>(classMatches, methodMatches, deobfuscators.source, deobfuscators.dest).setSaveListener(new MemberMatchingGui.SaveListener<BehaviorEntry>() {
225 @Override
226 public void save(MemberMatches<BehaviorEntry> matches) {
227 try {
228 MatchesWriter.writeMembers(matches, methodMatchesFile);
229 } catch (IOException ex) {
230 throw new Error(ex);
231 }
232 }
233 });
234 }
235
236 private static void convertMappings(File outMappingsFile, JarFile sourceJar, JarFile destJar, Mappings mappings, File classMatchesFile, File fieldMatchesFile, File methodMatchesFile)
237 throws IOException {
238
239 System.out.println("Reading matches...");
240 ClassMatches classMatches = MatchesReader.readClasses(classMatchesFile);
241 MemberMatches<FieldEntry> fieldMatches = MatchesReader.readMembers(fieldMatchesFile);
242 MemberMatches<BehaviorEntry> methodMatches = MatchesReader.readMembers(methodMatchesFile);
243
244 Deobfuscators deobfuscators = new Deobfuscators(sourceJar, destJar);
245 deobfuscators.source.setMappings(mappings);
246
247 // apply matches
248 Mappings newMappings = MappingsConverter.newMappings(classMatches, mappings, deobfuscators.source, deobfuscators.dest);
249 MappingsConverter.applyMemberMatches(newMappings, classMatches, fieldMatches, MappingsConverter.getFieldDoer());
250 MappingsConverter.applyMemberMatches(newMappings, classMatches, methodMatches, MappingsConverter.getMethodDoer());
251
252 // check the final mappings
253 MappingsChecker checker = new MappingsChecker(deobfuscators.dest.getJarIndex());
254 checker.dropBrokenMappings(newMappings);
255
256 for (java.util.Map.Entry<ClassEntry,ClassMapping> mapping : checker.getDroppedClassMappings().entrySet()) {
257 System.out.println("WARNING: Broken class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
258 }
259 for (java.util.Map.Entry<ClassEntry,ClassMapping> mapping : checker.getDroppedInnerClassMappings().entrySet()) {
260 System.out.println("WARNING: Broken inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
261 }
262 for (java.util.Map.Entry<FieldEntry,FieldMapping> mapping : checker.getDroppedFieldMappings().entrySet()) {
263 System.out.println("WARNING: Broken field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
264 }
265 for (java.util.Map.Entry<BehaviorEntry,MethodMapping> mapping : checker.getDroppedMethodMappings().entrySet()) {
266 System.out.println("WARNING: Broken behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ")");
267 }
268
269 // write out the converted mappings
270 try (FileWriter out = new FileWriter(outMappingsFile)) {
271 new MappingsWriter().write(out, newMappings);
272 }
273 System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath());
274 }
275
276 private static class Deobfuscators {
277
278 public Deobfuscator source;
279 public Deobfuscator dest;
280
281 public Deobfuscators(JarFile sourceJar, JarFile destJar) {
282 System.out.println("Indexing source jar...");
283 IndexerThread sourceIndexer = new IndexerThread(sourceJar);
284 sourceIndexer.start();
285 System.out.println("Indexing dest jar...");
286 IndexerThread destIndexer = new IndexerThread(destJar);
287 destIndexer.start();
288 sourceIndexer.joinOrBail();
289 destIndexer.joinOrBail();
290 source = sourceIndexer.deobfuscator;
291 dest = destIndexer.deobfuscator;
292 }
293 }
294
295 private static class IndexerThread extends Thread {
296
297 private JarFile m_jarFile;
298 public Deobfuscator deobfuscator;
299
300 public IndexerThread(JarFile jarFile) {
301 m_jarFile = jarFile;
302 deobfuscator = null;
303 }
304
305 public void joinOrBail() {
306 try {
307 join();
308 } catch (InterruptedException ex) {
309 throw new Error(ex);
310 }
311 }
312
313 @Override
314 public void run() {
315 try {
316 deobfuscator = new Deobfuscator(m_jarFile);
317 } catch (IOException ex) {
318 throw new Error(ex);
319 }
320 }
321 }
322}
diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java
new file mode 100644
index 00000000..08a974aa
--- /dev/null
+++ b/src/cuchaz/enigma/Deobfuscator.java
@@ -0,0 +1,548 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.io.FileOutputStream;
15import java.io.FileWriter;
16import java.io.IOException;
17import java.io.StringWriter;
18import java.util.List;
19import java.util.Map;
20import java.util.Set;
21import java.util.jar.JarEntry;
22import java.util.jar.JarFile;
23import java.util.jar.JarOutputStream;
24
25import javassist.CtClass;
26import javassist.bytecode.Descriptor;
27
28import com.google.common.collect.Maps;
29import com.google.common.collect.Sets;
30import com.strobel.assembler.metadata.MetadataSystem;
31import com.strobel.assembler.metadata.TypeDefinition;
32import com.strobel.assembler.metadata.TypeReference;
33import com.strobel.decompiler.DecompilerContext;
34import com.strobel.decompiler.DecompilerSettings;
35import com.strobel.decompiler.PlainTextOutput;
36import com.strobel.decompiler.languages.java.JavaOutputVisitor;
37import com.strobel.decompiler.languages.java.ast.AstBuilder;
38import com.strobel.decompiler.languages.java.ast.CompilationUnit;
39import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor;
40
41import cuchaz.enigma.analysis.EntryReference;
42import cuchaz.enigma.analysis.JarClassIterator;
43import cuchaz.enigma.analysis.JarIndex;
44import cuchaz.enigma.analysis.SourceIndex;
45import cuchaz.enigma.analysis.SourceIndexVisitor;
46import cuchaz.enigma.analysis.Token;
47import cuchaz.enigma.bytecode.ClassProtectifier;
48import cuchaz.enigma.bytecode.ClassPublifier;
49import cuchaz.enigma.mapping.ArgumentEntry;
50import cuchaz.enigma.mapping.BehaviorEntry;
51import cuchaz.enigma.mapping.ClassEntry;
52import cuchaz.enigma.mapping.ClassMapping;
53import cuchaz.enigma.mapping.ConstructorEntry;
54import cuchaz.enigma.mapping.Entry;
55import cuchaz.enigma.mapping.FieldEntry;
56import cuchaz.enigma.mapping.FieldMapping;
57import cuchaz.enigma.mapping.Mappings;
58import cuchaz.enigma.mapping.MappingsChecker;
59import cuchaz.enigma.mapping.MappingsRenamer;
60import cuchaz.enigma.mapping.MethodEntry;
61import cuchaz.enigma.mapping.MethodMapping;
62import cuchaz.enigma.mapping.TranslationDirection;
63import cuchaz.enigma.mapping.Translator;
64
65public class Deobfuscator {
66
67 public interface ProgressListener {
68 void init(int totalWork, String title);
69 void onProgress(int numDone, String message);
70 }
71
72 private JarFile m_jar;
73 private DecompilerSettings m_settings;
74 private JarIndex m_jarIndex;
75 private Mappings m_mappings;
76 private MappingsRenamer m_renamer;
77 private Map<TranslationDirection,Translator> m_translatorCache;
78
79 public Deobfuscator(JarFile jar) throws IOException {
80 m_jar = jar;
81
82 // build the jar index
83 m_jarIndex = new JarIndex();
84 m_jarIndex.indexJar(m_jar, true);
85
86 // config the decompiler
87 m_settings = DecompilerSettings.javaDefaults();
88 m_settings.setMergeVariables(true);
89 m_settings.setForceExplicitImports(true);
90 m_settings.setForceExplicitTypeArguments(true);
91 m_settings.setShowDebugLineNumbers(true);
92 // DEBUG
93 //m_settings.setShowSyntheticMembers(true);
94
95 // init defaults
96 m_translatorCache = Maps.newTreeMap();
97
98 // init mappings
99 setMappings(new Mappings());
100 }
101
102 public JarFile getJar() {
103 return m_jar;
104 }
105
106 public String getJarName() {
107 return m_jar.getName();
108 }
109
110 public JarIndex getJarIndex() {
111 return m_jarIndex;
112 }
113
114 public Mappings getMappings() {
115 return m_mappings;
116 }
117
118 public void setMappings(Mappings val) {
119 setMappings(val, true);
120 }
121
122 public void setMappings(Mappings val, boolean warnAboutDrops) {
123 if (val == null) {
124 val = new Mappings();
125 }
126
127 // drop mappings that don't match the jar
128 MappingsChecker checker = new MappingsChecker(m_jarIndex);
129 checker.dropBrokenMappings(val);
130 if (warnAboutDrops) {
131 for (java.util.Map.Entry<ClassEntry,ClassMapping> mapping : checker.getDroppedClassMappings().entrySet()) {
132 System.out.println("WARNING: Couldn't find class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
133 }
134 for (java.util.Map.Entry<ClassEntry,ClassMapping> mapping : checker.getDroppedInnerClassMappings().entrySet()) {
135 System.out.println("WARNING: Couldn't find inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
136 }
137 for (java.util.Map.Entry<FieldEntry,FieldMapping> mapping : checker.getDroppedFieldMappings().entrySet()) {
138 System.out.println("WARNING: Couldn't find field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
139 }
140 for (java.util.Map.Entry<BehaviorEntry,MethodMapping> mapping : checker.getDroppedMethodMappings().entrySet()) {
141 System.out.println("WARNING: Couldn't find behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped.");
142 }
143 }
144
145 // check for related method inconsistencies
146 if (checker.getRelatedMethodChecker().hasProblems()) {
147 throw new Error("Related methods are inconsistent! Need to fix the mappings manually.\n" + checker.getRelatedMethodChecker().getReport());
148 }
149
150 m_mappings = val;
151 m_renamer = new MappingsRenamer(m_jarIndex, val);
152 m_translatorCache.clear();
153 }
154
155 public Translator getTranslator(TranslationDirection direction) {
156 Translator translator = m_translatorCache.get(direction);
157 if (translator == null) {
158 translator = m_mappings.getTranslator(direction, m_jarIndex.getTranslationIndex());
159 m_translatorCache.put(direction, translator);
160 }
161 return translator;
162 }
163
164 public void getSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) {
165 for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) {
166 // skip inner classes
167 if (obfClassEntry.isInnerClass()) {
168 continue;
169 }
170
171 // separate the classes
172 ClassEntry deobfClassEntry = deobfuscateEntry(obfClassEntry);
173 if (!deobfClassEntry.equals(obfClassEntry)) {
174 // if the class has a mapping, clearly it's deobfuscated
175 deobfClasses.add(deobfClassEntry);
176 } else if (!obfClassEntry.getPackageName().equals(Constants.NonePackage)) {
177 // also call it deobufscated if it's not in the none package
178 deobfClasses.add(obfClassEntry);
179 } else {
180 // otherwise, assume it's still obfuscated
181 obfClasses.add(obfClassEntry);
182 }
183 }
184 }
185
186 public CompilationUnit getSourceTree(String className) {
187
188 // we don't know if this class name is obfuscated or deobfuscated
189 // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out
190 // the decompiler only sees classes after deobfuscation, so we need to load it by the deobfuscated name if there is one
191
192 // first, assume class name is deobf
193 String deobfClassName = className;
194
195 // if it wasn't actually deobf, then we can find a mapping for it and get the deobf name
196 ClassMapping classMapping = m_mappings.getClassByObf(className);
197 if (classMapping != null && classMapping.getDeobfName() != null) {
198 deobfClassName = classMapping.getDeobfName();
199 }
200
201 // set the type loader
202 TranslatingTypeLoader loader = new TranslatingTypeLoader(
203 m_jar,
204 m_jarIndex,
205 getTranslator(TranslationDirection.Obfuscating),
206 getTranslator(TranslationDirection.Deobfuscating)
207 );
208 m_settings.setTypeLoader(loader);
209
210 // see if procyon can find the type
211 TypeReference type = new MetadataSystem(loader).lookupType(deobfClassName);
212 if (type == null) {
213 throw new Error(String.format("Unable to find type: %s (deobf: %s)\nTried class names: %s",
214 className, deobfClassName, loader.getClassNamesToTry(deobfClassName)
215 ));
216 }
217 TypeDefinition resolvedType = type.resolve();
218
219 // decompile it!
220 DecompilerContext context = new DecompilerContext();
221 context.setCurrentType(resolvedType);
222 context.setSettings(m_settings);
223 AstBuilder builder = new AstBuilder(context);
224 builder.addType(resolvedType);
225 builder.runTransformations(null);
226 return builder.getCompilationUnit();
227 }
228
229 public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source) {
230 return getSourceIndex(sourceTree, source, null);
231 }
232
233 public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source, Boolean ignoreBadTokens) {
234
235 // build the source index
236 SourceIndex index;
237 if (ignoreBadTokens != null) {
238 index = new SourceIndex(source, ignoreBadTokens);
239 } else {
240 index = new SourceIndex(source);
241 }
242 sourceTree.acceptVisitor(new SourceIndexVisitor(), index);
243
244 // DEBUG
245 // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null );
246
247 // resolve all the classes in the source references
248 for (Token token : index.referenceTokens()) {
249 EntryReference<Entry,Entry> deobfReference = index.getDeobfReference(token);
250
251 // get the obfuscated entry
252 Entry obfEntry = obfuscateEntry(deobfReference.entry);
253
254 // try to resolve the class
255 ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(obfEntry);
256 if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getClassEntry())) {
257 // change the class of the entry
258 obfEntry = obfEntry.cloneToNewClass(resolvedObfClassEntry);
259
260 // save the new deobfuscated reference
261 deobfReference.entry = deobfuscateEntry(obfEntry);
262 index.replaceDeobfReference(token, deobfReference);
263 }
264
265 // DEBUG
266 // System.out.println( token + " -> " + reference + " -> " + index.getReferenceToken( reference ) );
267 }
268
269 return index;
270 }
271
272 public String getSource(CompilationUnit sourceTree) {
273 // render the AST into source
274 StringWriter buf = new StringWriter();
275 sourceTree.acceptVisitor(new InsertParenthesesVisitor(), null);
276 sourceTree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(buf), m_settings), null);
277 return buf.toString();
278 }
279
280 public void writeSources(File dirOut, ProgressListener progress) throws IOException {
281 // get the classes to decompile
282 Set<ClassEntry> classEntries = Sets.newHashSet();
283 for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) {
284 // skip inner classes
285 if (obfClassEntry.isInnerClass()) {
286 continue;
287 }
288
289 classEntries.add(obfClassEntry);
290 }
291
292 if (progress != null) {
293 progress.init(classEntries.size(), "Decompiling classes...");
294 }
295
296 // DEOBFUSCATE ALL THE THINGS!! @_@
297 int i = 0;
298 for (ClassEntry obfClassEntry : classEntries) {
299 ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry));
300 if (progress != null) {
301 progress.onProgress(i++, deobfClassEntry.toString());
302 }
303
304 try {
305 // get the source
306 String source = getSource(getSourceTree(obfClassEntry.getName()));
307
308 // write the file
309 File file = new File(dirOut, deobfClassEntry.getName().replace('.', '/') + ".java");
310 file.getParentFile().mkdirs();
311 try (FileWriter out = new FileWriter(file)) {
312 out.write(source);
313 }
314 } catch (Throwable t) {
315 throw new Error("Unable to deobfuscate class " + deobfClassEntry.toString() + " (" + obfClassEntry.toString() + ")", t);
316 }
317 }
318 if (progress != null) {
319 progress.onProgress(i, "Done!");
320 }
321 }
322
323 public void writeJar(File out, ProgressListener progress) {
324 final TranslatingTypeLoader loader = new TranslatingTypeLoader(
325 m_jar,
326 m_jarIndex,
327 getTranslator(TranslationDirection.Obfuscating),
328 getTranslator(TranslationDirection.Deobfuscating)
329 );
330 transformJar(out, progress, new ClassTransformer() {
331
332 @Override
333 public CtClass transform(CtClass c) throws Exception {
334 return loader.transformClass(c);
335 }
336 });
337 }
338
339 public void protectifyJar(File out, ProgressListener progress) {
340 transformJar(out, progress, new ClassTransformer() {
341
342 @Override
343 public CtClass transform(CtClass c) throws Exception {
344 return ClassProtectifier.protectify(c);
345 }
346 });
347 }
348
349 public void publifyJar(File out, ProgressListener progress) {
350 transformJar(out, progress, new ClassTransformer() {
351
352 @Override
353 public CtClass transform(CtClass c) throws Exception {
354 return ClassPublifier.publify(c);
355 }
356 });
357 }
358
359 private interface ClassTransformer {
360 public CtClass transform(CtClass c) throws Exception;
361 }
362 private void transformJar(File out, ProgressListener progress, ClassTransformer transformer) {
363 try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) {
364 if (progress != null) {
365 progress.init(JarClassIterator.getClassEntries(m_jar).size(), "Transforming classes...");
366 }
367
368 int i = 0;
369 for (CtClass c : JarClassIterator.classes(m_jar)) {
370 if (progress != null) {
371 progress.onProgress(i++, c.getName());
372 }
373
374 try {
375 c = transformer.transform(c);
376 outJar.putNextEntry(new JarEntry(c.getName().replace('.', '/') + ".class"));
377 outJar.write(c.toBytecode());
378 outJar.closeEntry();
379 } catch (Throwable t) {
380 throw new Error("Unable to transform class " + c.getName(), t);
381 }
382 }
383 if (progress != null) {
384 progress.onProgress(i, "Done!");
385 }
386
387 outJar.close();
388 } catch (IOException ex) {
389 throw new Error("Unable to write to Jar file!");
390 }
391 }
392
393 public <T extends Entry> T obfuscateEntry(T deobfEntry) {
394 if (deobfEntry == null) {
395 return null;
396 }
397 return getTranslator(TranslationDirection.Obfuscating).translateEntry(deobfEntry);
398 }
399
400 public <T extends Entry> T deobfuscateEntry(T obfEntry) {
401 if (obfEntry == null) {
402 return null;
403 }
404 return getTranslator(TranslationDirection.Deobfuscating).translateEntry(obfEntry);
405 }
406
407 public <E extends Entry,C extends Entry> EntryReference<E,C> obfuscateReference(EntryReference<E,C> deobfReference) {
408 if (deobfReference == null) {
409 return null;
410 }
411 return new EntryReference<E,C>(
412 obfuscateEntry(deobfReference.entry),
413 obfuscateEntry(deobfReference.context),
414 deobfReference
415 );
416 }
417
418 public <E extends Entry,C extends Entry> EntryReference<E,C> deobfuscateReference(EntryReference<E,C> obfReference) {
419 if (obfReference == null) {
420 return null;
421 }
422 return new EntryReference<E,C>(
423 deobfuscateEntry(obfReference.entry),
424 deobfuscateEntry(obfReference.context),
425 obfReference
426 );
427 }
428
429 public boolean isObfuscatedIdentifier(Entry obfEntry) {
430
431 if (obfEntry instanceof MethodEntry) {
432
433 // HACKHACK: Object methods are not obfuscated identifiers
434 MethodEntry obfMethodEntry = (MethodEntry)obfEntry;
435 String name = obfMethodEntry.getName();
436 String sig = obfMethodEntry.getSignature().toString();
437 if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) {
438 return false;
439 } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) {
440 return false;
441 } else if (name.equals("finalize") && sig.equals("()V")) {
442 return false;
443 } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) {
444 return false;
445 } else if (name.equals("hashCode") && sig.equals("()I")) {
446 return false;
447 } else if (name.equals("notify") && sig.equals("()V")) {
448 return false;
449 } else if (name.equals("notifyAll") && sig.equals("()V")) {
450 return false;
451 } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) {
452 return false;
453 } else if (name.equals("wait") && sig.equals("()V")) {
454 return false;
455 } else if (name.equals("wait") && sig.equals("(J)V")) {
456 return false;
457 } else if (name.equals("wait") && sig.equals("(JI)V")) {
458 return false;
459 }
460 }
461
462 return m_jarIndex.containsObfEntry(obfEntry);
463 }
464
465 public boolean isRenameable(EntryReference<Entry,Entry> obfReference) {
466 return obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry());
467 }
468
469 // NOTE: these methods are a bit messy... oh well
470
471 public boolean hasDeobfuscatedName(Entry obfEntry) {
472 Translator translator = getTranslator(TranslationDirection.Deobfuscating);
473 if (obfEntry instanceof ClassEntry) {
474 ClassEntry obfClass = (ClassEntry)obfEntry;
475 List<ClassMapping> mappingChain = m_mappings.getClassMappingChain(obfClass);
476 ClassMapping classMapping = mappingChain.get(mappingChain.size() - 1);
477 return classMapping != null && classMapping.getDeobfName() != null;
478 } else if (obfEntry instanceof FieldEntry) {
479 return translator.translate((FieldEntry)obfEntry) != null;
480 } else if (obfEntry instanceof MethodEntry) {
481 return translator.translate((MethodEntry)obfEntry) != null;
482 } else if (obfEntry instanceof ConstructorEntry) {
483 // constructors have no names
484 return false;
485 } else if (obfEntry instanceof ArgumentEntry) {
486 return translator.translate((ArgumentEntry)obfEntry) != null;
487 } else {
488 throw new Error("Unknown entry type: " + obfEntry.getClass().getName());
489 }
490 }
491
492 public void rename(Entry obfEntry, String newName) {
493 if (obfEntry instanceof ClassEntry) {
494 m_renamer.setClassName((ClassEntry)obfEntry, Descriptor.toJvmName(newName));
495 } else if (obfEntry instanceof FieldEntry) {
496 m_renamer.setFieldName((FieldEntry)obfEntry, newName);
497 } else if (obfEntry instanceof MethodEntry) {
498 m_renamer.setMethodTreeName((MethodEntry)obfEntry, newName);
499 } else if (obfEntry instanceof ConstructorEntry) {
500 throw new IllegalArgumentException("Cannot rename constructors");
501 } else if (obfEntry instanceof ArgumentEntry) {
502 m_renamer.setArgumentName((ArgumentEntry)obfEntry, newName);
503 } else {
504 throw new Error("Unknown entry type: " + obfEntry.getClass().getName());
505 }
506
507 // clear caches
508 m_translatorCache.clear();
509 }
510
511 public void removeMapping(Entry obfEntry) {
512 if (obfEntry instanceof ClassEntry) {
513 m_renamer.removeClassMapping((ClassEntry)obfEntry);
514 } else if (obfEntry instanceof FieldEntry) {
515 m_renamer.removeFieldMapping((FieldEntry)obfEntry);
516 } else if (obfEntry instanceof MethodEntry) {
517 m_renamer.removeMethodTreeMapping((MethodEntry)obfEntry);
518 } else if (obfEntry instanceof ConstructorEntry) {
519 throw new IllegalArgumentException("Cannot rename constructors");
520 } else if (obfEntry instanceof ArgumentEntry) {
521 m_renamer.removeArgumentMapping((ArgumentEntry)obfEntry);
522 } else {
523 throw new Error("Unknown entry type: " + obfEntry);
524 }
525
526 // clear caches
527 m_translatorCache.clear();
528 }
529
530 public void markAsDeobfuscated(Entry obfEntry) {
531 if (obfEntry instanceof ClassEntry) {
532 m_renamer.markClassAsDeobfuscated((ClassEntry)obfEntry);
533 } else if (obfEntry instanceof FieldEntry) {
534 m_renamer.markFieldAsDeobfuscated((FieldEntry)obfEntry);
535 } else if (obfEntry instanceof MethodEntry) {
536 m_renamer.markMethodTreeAsDeobfuscated((MethodEntry)obfEntry);
537 } else if (obfEntry instanceof ConstructorEntry) {
538 throw new IllegalArgumentException("Cannot rename constructors");
539 } else if (obfEntry instanceof ArgumentEntry) {
540 m_renamer.markArgumentAsDeobfuscated((ArgumentEntry)obfEntry);
541 } else {
542 throw new Error("Unknown entry type: " + obfEntry);
543 }
544
545 // clear caches
546 m_translatorCache.clear();
547 }
548}
diff --git a/src/cuchaz/enigma/ExceptionIgnorer.java b/src/cuchaz/enigma/ExceptionIgnorer.java
new file mode 100644
index 00000000..d8726d13
--- /dev/null
+++ b/src/cuchaz/enigma/ExceptionIgnorer.java
@@ -0,0 +1,34 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13public class ExceptionIgnorer {
14
15 public static boolean shouldIgnore(Throwable t) {
16
17 // is this that pesky concurrent access bug in the highlight painter system?
18 // (ancient ui code is ancient)
19 if (t instanceof ArrayIndexOutOfBoundsException) {
20 StackTraceElement[] stackTrace = t.getStackTrace();
21 if (stackTrace.length > 1) {
22
23 // does this stack frame match javax.swing.text.DefaultHighlighter.paint*() ?
24 StackTraceElement frame = stackTrace[1];
25 if (frame.getClassName().equals("javax.swing.text.DefaultHighlighter") && frame.getMethodName().startsWith("paint")) {
26 return true;
27 }
28 }
29 }
30
31 return false;
32 }
33
34}
diff --git a/src/cuchaz/enigma/Main.java b/src/cuchaz/enigma/Main.java
new file mode 100644
index 00000000..4842a795
--- /dev/null
+++ b/src/cuchaz/enigma/Main.java
@@ -0,0 +1,51 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.util.jar.JarFile;
15
16import cuchaz.enigma.gui.Gui;
17
18public class Main {
19
20 public static void main(String[] args) throws Exception {
21 Gui gui = new Gui();
22
23 // parse command-line args
24 if (args.length >= 1) {
25 gui.getController().openJar(new JarFile(getFile(args[0])));
26 }
27 if (args.length >= 2) {
28 gui.getController().openMappings(getFile(args[1]));
29 }
30
31 // DEBUG
32 //gui.getController().openDeclaration(new ClassEntry("none/bxq"));
33 }
34
35 private static File getFile(String path) {
36 // expand ~ to the home dir
37 if (path.startsWith("~")) {
38 // get the home dir
39 File dirHome = new File(System.getProperty("user.home"));
40
41 // is the path just ~/ or is it ~user/ ?
42 if (path.startsWith("~/")) {
43 return new File(dirHome, path.substring(2));
44 } else {
45 return new File(dirHome.getParentFile(), path.substring(1));
46 }
47 }
48
49 return new File(path);
50 }
51}
diff --git a/src/cuchaz/enigma/MainFormatConverter.java b/src/cuchaz/enigma/MainFormatConverter.java
new file mode 100644
index 00000000..73ee41f4
--- /dev/null
+++ b/src/cuchaz/enigma/MainFormatConverter.java
@@ -0,0 +1,130 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.io.FileReader;
15import java.io.FileWriter;
16import java.lang.reflect.Field;
17import java.util.Map;
18import java.util.jar.JarFile;
19
20import javassist.CtClass;
21import javassist.CtField;
22
23import com.google.common.collect.Maps;
24
25import cuchaz.enigma.analysis.JarClassIterator;
26import cuchaz.enigma.mapping.ClassEntry;
27import cuchaz.enigma.mapping.ClassMapping;
28import cuchaz.enigma.mapping.ClassNameReplacer;
29import cuchaz.enigma.mapping.FieldEntry;
30import cuchaz.enigma.mapping.FieldMapping;
31import cuchaz.enigma.mapping.EntryFactory;
32import cuchaz.enigma.mapping.Mappings;
33import cuchaz.enigma.mapping.MappingsReader;
34import cuchaz.enigma.mapping.MappingsWriter;
35import cuchaz.enigma.mapping.Type;
36
37public class MainFormatConverter {
38
39 public static void main(String[] args)
40 throws Exception {
41
42 System.out.println("Getting field types from jar...");
43
44 JarFile jar = new JarFile(System.getProperty("user.home") + "/.minecraft/versions/1.8/1.8.jar");
45 Map<String,Type> fieldTypes = Maps.newHashMap();
46 for (CtClass c : JarClassIterator.classes(jar)) {
47 for (CtField field : c.getDeclaredFields()) {
48 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
49 fieldTypes.put(getFieldKey(fieldEntry), moveClasssesOutOfDefaultPackage(fieldEntry.getType()));
50 }
51 }
52
53 System.out.println("Reading mappings...");
54
55 File fileMappings = new File("../Enigma Mappings/1.8.mappings");
56 MappingsReader mappingsReader = new MappingsReader() {
57
58 @Override
59 protected FieldMapping readField(String[] parts) {
60 // assume the void type for now
61 return new FieldMapping(parts[1], new Type("V"), parts[2]);
62 }
63 };
64 Mappings mappings = mappingsReader.read(new FileReader(fileMappings));
65
66 System.out.println("Updating field types...");
67
68 for (ClassMapping classMapping : mappings.classes()) {
69 updateFieldsInClass(fieldTypes, classMapping);
70 }
71
72 System.out.println("Saving mappings...");
73
74 try (FileWriter writer = new FileWriter(fileMappings)) {
75 new MappingsWriter().write(writer, mappings);
76 }
77
78 System.out.println("Done!");
79 }
80
81 private static Type moveClasssesOutOfDefaultPackage(Type type) {
82 return new Type(type, new ClassNameReplacer() {
83 @Override
84 public String replace(String className) {
85 ClassEntry entry = new ClassEntry(className);
86 if (entry.isInDefaultPackage()) {
87 return Constants.NonePackage + "/" + className;
88 }
89 return null;
90 }
91 });
92 }
93
94 private static void updateFieldsInClass(Map<String,Type> fieldTypes, ClassMapping classMapping)
95 throws Exception {
96
97 // update the fields
98 for (FieldMapping fieldMapping : classMapping.fields()) {
99 setFieldType(fieldTypes, classMapping, fieldMapping);
100 }
101
102 // recurse
103 for (ClassMapping innerClassMapping : classMapping.innerClasses()) {
104 updateFieldsInClass(fieldTypes, innerClassMapping);
105 }
106 }
107
108 private static void setFieldType(Map<String,Type> fieldTypes, ClassMapping classMapping, FieldMapping fieldMapping)
109 throws Exception {
110
111 // get the new type
112 Type newType = fieldTypes.get(getFieldKey(classMapping, fieldMapping));
113 if (newType == null) {
114 throw new Error("Can't find type for field: " + getFieldKey(classMapping, fieldMapping));
115 }
116
117 // hack in the new field type
118 Field field = fieldMapping.getClass().getDeclaredField("m_obfType");
119 field.setAccessible(true);
120 field.set(fieldMapping, newType);
121 }
122
123 private static Object getFieldKey(ClassMapping classMapping, FieldMapping fieldMapping) {
124 return classMapping.getObfSimpleName() + "." + fieldMapping.getObfName();
125 }
126
127 private static String getFieldKey(FieldEntry obfFieldEntry) {
128 return obfFieldEntry.getClassEntry().getSimpleName() + "." + obfFieldEntry.getName();
129 }
130}
diff --git a/src/cuchaz/enigma/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java
new file mode 100644
index 00000000..a2185e5c
--- /dev/null
+++ b/src/cuchaz/enigma/TranslatingTypeLoader.java
@@ -0,0 +1,249 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.ByteArrayOutputStream;
14import java.io.IOException;
15import java.io.InputStream;
16import java.util.List;
17import java.util.Map;
18import java.util.jar.JarEntry;
19import java.util.jar.JarFile;
20
21import javassist.ByteArrayClassPath;
22import javassist.CannotCompileException;
23import javassist.ClassPool;
24import javassist.CtClass;
25import javassist.NotFoundException;
26import javassist.bytecode.Descriptor;
27
28import com.google.common.collect.Lists;
29import com.google.common.collect.Maps;
30import com.strobel.assembler.metadata.Buffer;
31import com.strobel.assembler.metadata.ClasspathTypeLoader;
32import com.strobel.assembler.metadata.ITypeLoader;
33
34import cuchaz.enigma.analysis.BridgeMarker;
35import cuchaz.enigma.analysis.JarIndex;
36import cuchaz.enigma.bytecode.ClassRenamer;
37import cuchaz.enigma.bytecode.ClassTranslator;
38import cuchaz.enigma.bytecode.InnerClassWriter;
39import cuchaz.enigma.bytecode.LocalVariableRenamer;
40import cuchaz.enigma.bytecode.MethodParameterWriter;
41import cuchaz.enigma.mapping.ClassEntry;
42import cuchaz.enigma.mapping.Translator;
43
44public class TranslatingTypeLoader implements ITypeLoader {
45
46 private JarFile m_jar;
47 private JarIndex m_jarIndex;
48 private Translator m_obfuscatingTranslator;
49 private Translator m_deobfuscatingTranslator;
50 private Map<String,byte[]> m_cache;
51 private ClasspathTypeLoader m_defaultTypeLoader;
52
53 public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex) {
54 this(jar, jarIndex, new Translator(), new Translator());
55 }
56
57 public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator) {
58 m_jar = jar;
59 m_jarIndex = jarIndex;
60 m_obfuscatingTranslator = obfuscatingTranslator;
61 m_deobfuscatingTranslator = deobfuscatingTranslator;
62 m_cache = Maps.newHashMap();
63 m_defaultTypeLoader = new ClasspathTypeLoader();
64 }
65
66 public void clearCache() {
67 m_cache.clear();
68 }
69
70 @Override
71 public boolean tryLoadType(String className, Buffer out) {
72
73 // check the cache
74 byte[] data;
75 if (m_cache.containsKey(className)) {
76 data = m_cache.get(className);
77 } else {
78 data = loadType(className);
79 m_cache.put(className, data);
80 }
81
82 if (data == null) {
83 // chain to default type loader
84 return m_defaultTypeLoader.tryLoadType(className, out);
85 }
86
87 // send the class to the decompiler
88 out.reset(data.length);
89 System.arraycopy(data, 0, out.array(), out.position(), data.length);
90 out.position(0);
91 return true;
92 }
93
94 public CtClass loadClass(String deobfClassName) {
95
96 byte[] data = loadType(deobfClassName);
97 if (data == null) {
98 return null;
99 }
100
101 // return a javassist handle for the class
102 String javaClassFileName = Descriptor.toJavaName(deobfClassName);
103 ClassPool classPool = new ClassPool();
104 classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, data));
105 try {
106 return classPool.get(javaClassFileName);
107 } catch (NotFoundException ex) {
108 throw new Error(ex);
109 }
110 }
111
112 private byte[] loadType(String className) {
113
114 // NOTE: don't know if class name is obf or deobf
115 ClassEntry classEntry = new ClassEntry(className);
116 ClassEntry obfClassEntry = m_obfuscatingTranslator.translateEntry(classEntry);
117
118 // is this an inner class referenced directly? (ie trying to load b instead of a$b)
119 if (!obfClassEntry.isInnerClass()) {
120 List<ClassEntry> classChain = m_jarIndex.getObfClassChain(obfClassEntry);
121 if (classChain.size() > 1) {
122 System.err.println(String.format("WARNING: no class %s after inner class reconstruction. Try %s",
123 className, obfClassEntry.buildClassEntry(classChain)
124 ));
125 return null;
126 }
127 }
128
129 // is this a class we should even know about?
130 if (!m_jarIndex.containsObfClass(obfClassEntry)) {
131 return null;
132 }
133
134 // DEBUG
135 //System.out.println(String.format("Looking for %s (obf: %s)", classEntry.getName(), obfClassEntry.getName()));
136
137 // find the class in the jar
138 String classInJarName = findClassInJar(obfClassEntry);
139 if (classInJarName == null) {
140 // couldn't find it
141 return null;
142 }
143
144 try {
145 // read the class file into a buffer
146 ByteArrayOutputStream data = new ByteArrayOutputStream();
147 byte[] buf = new byte[1024 * 1024]; // 1 KiB
148 InputStream in = m_jar.getInputStream(m_jar.getJarEntry(classInJarName + ".class"));
149 while (true) {
150 int bytesRead = in.read(buf);
151 if (bytesRead <= 0) {
152 break;
153 }
154 data.write(buf, 0, bytesRead);
155 }
156 data.close();
157 in.close();
158 buf = data.toByteArray();
159
160 // load the javassist handle to the raw class
161 ClassPool classPool = new ClassPool();
162 String classInJarJavaName = Descriptor.toJavaName(classInJarName);
163 classPool.insertClassPath(new ByteArrayClassPath(classInJarJavaName, buf));
164 CtClass c = classPool.get(classInJarJavaName);
165
166 c = transformClass(c);
167
168 // sanity checking
169 assertClassName(c, classEntry);
170
171 // DEBUG
172 //Util.writeClass( c );
173
174 // we have a transformed class!
175 return c.toBytecode();
176 } catch (IOException | NotFoundException | CannotCompileException ex) {
177 throw new Error(ex);
178 }
179 }
180
181 private String findClassInJar(ClassEntry obfClassEntry) {
182
183 // try to find the class in the jar
184 for (String className : getClassNamesToTry(obfClassEntry)) {
185 JarEntry jarEntry = m_jar.getJarEntry(className + ".class");
186 if (jarEntry != null) {
187 return className;
188 }
189 }
190
191 // didn't find it ;_;
192 return null;
193 }
194
195 public List<String> getClassNamesToTry(String className) {
196 return getClassNamesToTry(m_obfuscatingTranslator.translateEntry(new ClassEntry(className)));
197 }
198
199 public List<String> getClassNamesToTry(ClassEntry obfClassEntry) {
200 List<String> classNamesToTry = Lists.newArrayList();
201 classNamesToTry.add(obfClassEntry.getName());
202 if (obfClassEntry.getPackageName().equals(Constants.NonePackage)) {
203 // taking off the none package, if any
204 classNamesToTry.add(obfClassEntry.getSimpleName());
205 }
206 if (obfClassEntry.isInnerClass()) {
207 // try just the inner class name
208 classNamesToTry.add(obfClassEntry.getInnermostClassName());
209 }
210 return classNamesToTry;
211 }
212
213 public CtClass transformClass(CtClass c)
214 throws IOException, NotFoundException, CannotCompileException {
215
216 // we moved a lot of classes out of the default package into the none package
217 // make sure all the class references are consistent
218 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
219
220 // reconstruct inner classes
221 new InnerClassWriter(m_jarIndex).write(c);
222
223 // re-get the javassist handle since we changed class names
224 ClassEntry obfClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
225 String javaClassReconstructedName = Descriptor.toJavaName(obfClassEntry.getName());
226 ClassPool classPool = new ClassPool();
227 classPool.insertClassPath(new ByteArrayClassPath(javaClassReconstructedName, c.toBytecode()));
228 c = classPool.get(javaClassReconstructedName);
229
230 // check that the file is correct after inner class reconstruction (ie cause Javassist to fail fast if something is wrong)
231 assertClassName(c, obfClassEntry);
232
233 // do all kinds of deobfuscating transformations on the class
234 new BridgeMarker(m_jarIndex).markBridges(c);
235 new MethodParameterWriter(m_deobfuscatingTranslator).writeMethodArguments(c);
236 new LocalVariableRenamer(m_deobfuscatingTranslator).rename(c);
237 new ClassTranslator(m_deobfuscatingTranslator).translate(c);
238
239 return c;
240 }
241
242 private void assertClassName(CtClass c, ClassEntry obfClassEntry) {
243 String name1 = Descriptor.toJvmName(c.getName());
244 assert (name1.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name1);
245
246 String name2 = Descriptor.toJvmName(c.getClassFile().getName());
247 assert (name2.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name2);
248 }
249}
diff --git a/src/cuchaz/enigma/Util.java b/src/cuchaz/enigma/Util.java
new file mode 100644
index 00000000..c7e509fa
--- /dev/null
+++ b/src/cuchaz/enigma/Util.java
@@ -0,0 +1,104 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..1c8cfc48
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/Access.java
@@ -0,0 +1,43 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..353a4bf0
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java
@@ -0,0 +1,93 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.Set;
14
15import javax.swing.tree.DefaultMutableTreeNode;
16import javax.swing.tree.TreeNode;
17
18import com.google.common.collect.Sets;
19
20import cuchaz.enigma.mapping.BehaviorEntry;
21import cuchaz.enigma.mapping.Entry;
22import cuchaz.enigma.mapping.Translator;
23
24public class BehaviorReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<BehaviorEntry,BehaviorEntry> {
25
26 private static final long serialVersionUID = -3658163700783307520L;
27
28 private Translator m_deobfuscatingTranslator;
29 private BehaviorEntry m_entry;
30 private EntryReference<BehaviorEntry,BehaviorEntry> m_reference;
31 private Access m_access;
32
33 public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, BehaviorEntry entry) {
34 m_deobfuscatingTranslator = deobfuscatingTranslator;
35 m_entry = entry;
36 m_reference = null;
37 }
38
39 public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<BehaviorEntry,BehaviorEntry> reference, Access access) {
40 m_deobfuscatingTranslator = deobfuscatingTranslator;
41 m_entry = reference.entry;
42 m_reference = reference;
43 m_access = access;
44 }
45
46 @Override
47 public BehaviorEntry getEntry() {
48 return m_entry;
49 }
50
51 @Override
52 public EntryReference<BehaviorEntry,BehaviorEntry> getReference() {
53 return m_reference;
54 }
55
56 @Override
57 public String toString() {
58 if (m_reference != null) {
59 return String.format("%s (%s)", m_deobfuscatingTranslator.translateEntry(m_reference.context), m_access);
60 }
61 return m_deobfuscatingTranslator.translateEntry(m_entry).toString();
62 }
63
64 public void load(JarIndex index, boolean recurse) {
65 // get all the child nodes
66 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(m_entry)) {
67 add(new BehaviorReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_entry)));
68 }
69
70 if (recurse && children != null) {
71 for (Object child : children) {
72 if (child instanceof BehaviorReferenceTreeNode) {
73 BehaviorReferenceTreeNode node = (BehaviorReferenceTreeNode)child;
74
75 // don't recurse into ancestor
76 Set<Entry> ancestors = Sets.newHashSet();
77 TreeNode n = (TreeNode)node;
78 while (n.getParent() != null) {
79 n = n.getParent();
80 if (n instanceof BehaviorReferenceTreeNode) {
81 ancestors.add( ((BehaviorReferenceTreeNode)n).getEntry());
82 }
83 }
84 if (ancestors.contains(node.getEntry())) {
85 continue;
86 }
87
88 node.load(index, true);
89 }
90 }
91 }
92 }
93}
diff --git a/src/cuchaz/enigma/analysis/BridgeMarker.java b/src/cuchaz/enigma/analysis/BridgeMarker.java
new file mode 100644
index 00000000..650b3a78
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/BridgeMarker.java
@@ -0,0 +1,43 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import javassist.CtClass;
14import javassist.CtMethod;
15import javassist.bytecode.AccessFlag;
16import cuchaz.enigma.mapping.EntryFactory;
17import cuchaz.enigma.mapping.MethodEntry;
18
19public class BridgeMarker {
20
21 private JarIndex m_jarIndex;
22
23 public BridgeMarker(JarIndex jarIndex) {
24 m_jarIndex = jarIndex;
25 }
26
27 public void markBridges(CtClass c) {
28
29 for (CtMethod method : c.getDeclaredMethods()) {
30 MethodEntry methodEntry = EntryFactory.getMethodEntry(method);
31
32 // is this a bridge method?
33 MethodEntry bridgedMethodEntry = m_jarIndex.getBridgedMethod(methodEntry);
34 if (bridgedMethodEntry != null) {
35
36 // it's a bridge method! add the bridge flag
37 int flags = method.getMethodInfo().getAccessFlags();
38 flags |= AccessFlag.BRIDGE;
39 method.getMethodInfo().setAccessFlags(flags);
40 }
41 }
42 }
43}
diff --git a/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
new file mode 100644
index 00000000..cc70f51e
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
@@ -0,0 +1,80 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..7542bd9d
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java
@@ -0,0 +1,85 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.List;
14
15import javax.swing.tree.DefaultMutableTreeNode;
16
17import com.google.common.collect.Lists;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.Translator;
21
22public class ClassInheritanceTreeNode extends DefaultMutableTreeNode {
23
24 private static final long serialVersionUID = 4432367405826178490L;
25
26 private Translator m_deobfuscatingTranslator;
27 private String m_obfClassName;
28
29 public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) {
30 m_deobfuscatingTranslator = deobfuscatingTranslator;
31 m_obfClassName = obfClassName;
32 }
33
34 public String getObfClassName() {
35 return m_obfClassName;
36 }
37
38 public String getDeobfClassName() {
39 return m_deobfuscatingTranslator.translateClass(m_obfClassName);
40 }
41
42 @Override
43 public String toString() {
44 String deobfClassName = getDeobfClassName();
45 if (deobfClassName != null) {
46 return deobfClassName;
47 }
48 return m_obfClassName;
49 }
50
51 public void load(TranslationIndex ancestries, boolean recurse) {
52 // get all the child nodes
53 List<ClassInheritanceTreeNode> nodes = Lists.newArrayList();
54 for (ClassEntry subclassEntry : ancestries.getSubclass(new ClassEntry(m_obfClassName))) {
55 nodes.add(new ClassInheritanceTreeNode(m_deobfuscatingTranslator, subclassEntry.getName()));
56 }
57
58 // add them to this node
59 for (ClassInheritanceTreeNode node : nodes) {
60 this.add(node);
61 }
62
63 if (recurse) {
64 for (ClassInheritanceTreeNode node : nodes) {
65 node.load(ancestries, true);
66 }
67 }
68 }
69
70 public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) {
71 // is this the node?
72 if (node.getObfClassName().equals(entry.getName())) {
73 return node;
74 }
75
76 // recurse
77 for (int i = 0; i < node.getChildCount(); i++) {
78 ClassInheritanceTreeNode foundNode = findNode((ClassInheritanceTreeNode)node.getChildAt(i), entry);
79 if (foundNode != null) {
80 return foundNode;
81 }
82 }
83 return null;
84 }
85}
diff --git a/src/cuchaz/enigma/analysis/EntryReference.java b/src/cuchaz/enigma/analysis/EntryReference.java
new file mode 100644
index 00000000..85127239
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/EntryReference.java
@@ -0,0 +1,126 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.Arrays;
14import java.util.List;
15
16import cuchaz.enigma.Util;
17import cuchaz.enigma.mapping.ClassEntry;
18import cuchaz.enigma.mapping.ConstructorEntry;
19import cuchaz.enigma.mapping.Entry;
20
21public class EntryReference<E extends Entry,C extends Entry> {
22
23 private static final List<String> ConstructorNonNames = Arrays.asList("this", "super", "static");
24 public E entry;
25 public C context;
26
27 private boolean m_isNamed;
28
29 public EntryReference(E entry, String sourceName) {
30 this(entry, sourceName, null);
31 }
32
33 public EntryReference(E entry, String sourceName, C context) {
34 if (entry == null) {
35 throw new IllegalArgumentException("Entry cannot be null!");
36 }
37
38 this.entry = entry;
39 this.context = context;
40
41 m_isNamed = sourceName != null && sourceName.length() > 0;
42 if (entry instanceof ConstructorEntry && ConstructorNonNames.contains(sourceName)) {
43 m_isNamed = false;
44 }
45 }
46
47 public EntryReference(E entry, C context, EntryReference<E,C> other) {
48 this.entry = entry;
49 this.context = context;
50 m_isNamed = other.m_isNamed;
51 }
52
53 public ClassEntry getLocationClassEntry() {
54 if (context != null) {
55 return context.getClassEntry();
56 }
57 return entry.getClassEntry();
58 }
59
60 public boolean isNamed() {
61 return m_isNamed;
62 }
63
64 public Entry getNameableEntry() {
65 if (entry instanceof ConstructorEntry) {
66 // renaming a constructor really means renaming the class
67 return entry.getClassEntry();
68 }
69 return entry;
70 }
71
72 public String getNamableName() {
73 if (getNameableEntry() instanceof ClassEntry) {
74 ClassEntry classEntry = (ClassEntry)getNameableEntry();
75 if (classEntry.isInnerClass()) {
76 // make sure we only rename the inner class name
77 return classEntry.getInnermostClassName();
78 }
79 }
80
81 return getNameableEntry().getName();
82 }
83
84 @Override
85 public int hashCode() {
86 if (context != null) {
87 return Util.combineHashesOrdered(entry.hashCode(), context.hashCode());
88 }
89 return entry.hashCode();
90 }
91
92 @Override
93 public boolean equals(Object other) {
94 if (other instanceof EntryReference) {
95 return equals((EntryReference<?,?>)other);
96 }
97 return false;
98 }
99
100 public boolean equals(EntryReference<?,?> other) {
101 // check entry first
102 boolean isEntrySame = entry.equals(other.entry);
103 if (!isEntrySame) {
104 return false;
105 }
106
107 // check caller
108 if (context == null && other.context == null) {
109 return true;
110 } else if (context != null && other.context != null) {
111 return context.equals(other.context);
112 }
113 return false;
114 }
115
116 @Override
117 public String toString() {
118 StringBuilder buf = new StringBuilder();
119 buf.append(entry);
120 if (context != null) {
121 buf.append(" called from ");
122 buf.append(context);
123 }
124 return buf.toString();
125 }
126}
diff --git a/src/cuchaz/enigma/analysis/EntryRenamer.java b/src/cuchaz/enigma/analysis/EntryRenamer.java
new file mode 100644
index 00000000..f748274f
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/EntryRenamer.java
@@ -0,0 +1,192 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.AbstractMap;
14import java.util.List;
15import java.util.Map;
16import java.util.Set;
17
18import com.google.common.collect.Lists;
19import com.google.common.collect.Multimap;
20import com.google.common.collect.Sets;
21
22import cuchaz.enigma.mapping.ArgumentEntry;
23import cuchaz.enigma.mapping.ClassEntry;
24import cuchaz.enigma.mapping.ClassNameReplacer;
25import cuchaz.enigma.mapping.ConstructorEntry;
26import cuchaz.enigma.mapping.Entry;
27import cuchaz.enigma.mapping.FieldEntry;
28import cuchaz.enigma.mapping.MethodEntry;
29import cuchaz.enigma.mapping.Signature;
30import cuchaz.enigma.mapping.Type;
31
32public class EntryRenamer {
33
34 public static <T> void renameClassesInSet(Map<String,String> renames, Set<T> set) {
35 List<T> entries = Lists.newArrayList();
36 for (T val : set) {
37 entries.add(renameClassesInThing(renames, val));
38 }
39 set.clear();
40 set.addAll(entries);
41 }
42
43 public static <Key,Val> void renameClassesInMap(Map<String,String> renames, Map<Key,Val> map) {
44 // for each key/value pair...
45 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
46 for (Map.Entry<Key,Val> entry : map.entrySet()) {
47 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
48 renameClassesInThing(renames, entry.getKey()),
49 renameClassesInThing(renames, entry.getValue())
50 ));
51 }
52 map.clear();
53 for (Map.Entry<Key,Val> entry : entriesToAdd) {
54 map.put(entry.getKey(), entry.getValue());
55 }
56 }
57
58 public static <Key,Val> void renameClassesInMultimap(Map<String,String> renames, Multimap<Key,Val> map) {
59 // for each key/value pair...
60 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
61 for (Map.Entry<Key,Val> entry : map.entries()) {
62 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
63 renameClassesInThing(renames, entry.getKey()),
64 renameClassesInThing(renames, entry.getValue())
65 ));
66 }
67 map.clear();
68 for (Map.Entry<Key,Val> entry : entriesToAdd) {
69 map.put(entry.getKey(), entry.getValue());
70 }
71 }
72
73 public static <Key,Val> void renameMethodsInMultimap(Map<MethodEntry,MethodEntry> renames, Multimap<Key,Val> map) {
74 // for each key/value pair...
75 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
76 for (Map.Entry<Key,Val> entry : map.entries()) {
77 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
78 renameMethodsInThing(renames, entry.getKey()),
79 renameMethodsInThing(renames, entry.getValue())
80 ));
81 }
82 map.clear();
83 for (Map.Entry<Key,Val> entry : entriesToAdd) {
84 map.put(entry.getKey(), entry.getValue());
85 }
86 }
87
88 public static <Key,Val> void renameMethodsInMap(Map<MethodEntry,MethodEntry> renames, Map<Key,Val> map) {
89 // for each key/value pair...
90 Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet();
91 for (Map.Entry<Key,Val> entry : map.entrySet()) {
92 entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>(
93 renameMethodsInThing(renames, entry.getKey()),
94 renameMethodsInThing(renames, entry.getValue())
95 ));
96 }
97 map.clear();
98 for (Map.Entry<Key,Val> entry : entriesToAdd) {
99 map.put(entry.getKey(), entry.getValue());
100 }
101 }
102
103 @SuppressWarnings("unchecked")
104 public static <T> T renameMethodsInThing(Map<MethodEntry,MethodEntry> renames, T thing) {
105 if (thing instanceof MethodEntry) {
106 MethodEntry methodEntry = (MethodEntry)thing;
107 MethodEntry newMethodEntry = renames.get(methodEntry);
108 if (newMethodEntry != null) {
109 return (T)new MethodEntry(
110 methodEntry.getClassEntry(),
111 newMethodEntry.getName(),
112 methodEntry.getSignature()
113 );
114 }
115 return thing;
116 } else if (thing instanceof ArgumentEntry) {
117 ArgumentEntry argumentEntry = (ArgumentEntry)thing;
118 return (T)new ArgumentEntry(
119 renameMethodsInThing(renames, argumentEntry.getBehaviorEntry()),
120 argumentEntry.getIndex(),
121 argumentEntry.getName()
122 );
123 } else if (thing instanceof EntryReference) {
124 EntryReference<Entry,Entry> reference = (EntryReference<Entry,Entry>)thing;
125 reference.entry = renameMethodsInThing(renames, reference.entry);
126 reference.context = renameMethodsInThing(renames, reference.context);
127 return thing;
128 }
129 return thing;
130 }
131
132 @SuppressWarnings("unchecked")
133 public static <T> T renameClassesInThing(final Map<String,String> renames, T thing) {
134 if (thing instanceof String) {
135 String stringEntry = (String)thing;
136 if (renames.containsKey(stringEntry)) {
137 return (T)renames.get(stringEntry);
138 }
139 } else if (thing instanceof ClassEntry) {
140 ClassEntry classEntry = (ClassEntry)thing;
141 return (T)new ClassEntry(renameClassesInThing(renames, classEntry.getClassName()));
142 } else if (thing instanceof FieldEntry) {
143 FieldEntry fieldEntry = (FieldEntry)thing;
144 return (T)new FieldEntry(
145 renameClassesInThing(renames, fieldEntry.getClassEntry()),
146 fieldEntry.getName(),
147 renameClassesInThing(renames, fieldEntry.getType())
148 );
149 } else if (thing instanceof ConstructorEntry) {
150 ConstructorEntry constructorEntry = (ConstructorEntry)thing;
151 return (T)new ConstructorEntry(
152 renameClassesInThing(renames, constructorEntry.getClassEntry()),
153 renameClassesInThing(renames, constructorEntry.getSignature())
154 );
155 } else if (thing instanceof MethodEntry) {
156 MethodEntry methodEntry = (MethodEntry)thing;
157 return (T)new MethodEntry(
158 renameClassesInThing(renames, methodEntry.getClassEntry()),
159 methodEntry.getName(),
160 renameClassesInThing(renames, methodEntry.getSignature())
161 );
162 } else if (thing instanceof ArgumentEntry) {
163 ArgumentEntry argumentEntry = (ArgumentEntry)thing;
164 return (T)new ArgumentEntry(
165 renameClassesInThing(renames, argumentEntry.getBehaviorEntry()),
166 argumentEntry.getIndex(),
167 argumentEntry.getName()
168 );
169 } else if (thing instanceof EntryReference) {
170 EntryReference<Entry,Entry> reference = (EntryReference<Entry,Entry>)thing;
171 reference.entry = renameClassesInThing(renames, reference.entry);
172 reference.context = renameClassesInThing(renames, reference.context);
173 return thing;
174 } else if (thing instanceof Signature) {
175 return (T)new Signature((Signature)thing, new ClassNameReplacer() {
176 @Override
177 public String replace(String className) {
178 return renameClassesInThing(renames, className);
179 }
180 });
181 } else if (thing instanceof Type) {
182 return (T)new Type((Type)thing, new ClassNameReplacer() {
183 @Override
184 public String replace(String className) {
185 return renameClassesInThing(renames, className);
186 }
187 });
188 }
189
190 return thing;
191 }
192}
diff --git a/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
new file mode 100644
index 00000000..4ed8fee2
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
@@ -0,0 +1,81 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..aa58e9ec
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/JarClassIterator.java
@@ -0,0 +1,137 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.io.ByteArrayOutputStream;
14import java.io.IOException;
15import java.io.InputStream;
16import java.util.Enumeration;
17import java.util.Iterator;
18import java.util.List;
19import java.util.jar.JarEntry;
20import java.util.jar.JarFile;
21
22import javassist.ByteArrayClassPath;
23import javassist.ClassPool;
24import javassist.CtClass;
25import javassist.NotFoundException;
26import javassist.bytecode.Descriptor;
27
28import com.google.common.collect.Lists;
29
30import cuchaz.enigma.Constants;
31import cuchaz.enigma.mapping.ClassEntry;
32
33public class JarClassIterator implements Iterator<CtClass> {
34
35 private JarFile m_jar;
36 private Iterator<JarEntry> m_iter;
37
38 public JarClassIterator(JarFile jar) {
39 m_jar = jar;
40
41 // get the jar entries that correspond to classes
42 List<JarEntry> classEntries = Lists.newArrayList();
43 Enumeration<JarEntry> entries = m_jar.entries();
44 while (entries.hasMoreElements()) {
45 JarEntry entry = entries.nextElement();
46
47 // is this a class file?
48 if (entry.getName().endsWith(".class")) {
49 classEntries.add(entry);
50 }
51 }
52 m_iter = classEntries.iterator();
53 }
54
55 @Override
56 public boolean hasNext() {
57 return m_iter.hasNext();
58 }
59
60 @Override
61 public CtClass next() {
62 JarEntry entry = m_iter.next();
63 try {
64 return getClass(m_jar, entry);
65 } catch (IOException | NotFoundException ex) {
66 throw new Error("Unable to load class: " + entry.getName());
67 }
68 }
69
70 @Override
71 public void remove() {
72 throw new UnsupportedOperationException();
73 }
74
75 public static List<ClassEntry> getClassEntries(JarFile jar) {
76 List<ClassEntry> classEntries = Lists.newArrayList();
77 Enumeration<JarEntry> entries = jar.entries();
78 while (entries.hasMoreElements()) {
79 JarEntry entry = entries.nextElement();
80
81 // is this a class file?
82 if (!entry.isDirectory() && entry.getName().endsWith(".class")) {
83 classEntries.add(getClassEntry(entry));
84 }
85 }
86 return classEntries;
87 }
88
89 public static Iterable<CtClass> classes(final JarFile jar) {
90 return new Iterable<CtClass>() {
91 @Override
92 public Iterator<CtClass> iterator() {
93 return new JarClassIterator(jar);
94 }
95 };
96 }
97
98 public static CtClass getClass(JarFile jar, ClassEntry classEntry) {
99 try {
100 return getClass(jar, new JarEntry(classEntry.getName() + ".class"));
101 } catch (IOException | NotFoundException ex) {
102 throw new Error("Unable to load class: " + classEntry.getName());
103 }
104 }
105
106 private static CtClass getClass(JarFile jar, JarEntry entry) throws IOException, NotFoundException {
107 // read the class into a buffer
108 ByteArrayOutputStream bos = new ByteArrayOutputStream();
109 byte[] buf = new byte[Constants.KiB];
110 int totalNumBytesRead = 0;
111 InputStream in = jar.getInputStream(entry);
112 while (in.available() > 0) {
113 int numBytesRead = in.read(buf);
114 if (numBytesRead < 0) {
115 break;
116 }
117 bos.write(buf, 0, numBytesRead);
118
119 // sanity checking
120 totalNumBytesRead += numBytesRead;
121 if (totalNumBytesRead > Constants.MiB) {
122 throw new Error("Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!");
123 }
124 }
125
126 // get a javassist handle for the class
127 String className = Descriptor.toJavaName(getClassEntry(entry).getName());
128 ClassPool classPool = new ClassPool();
129 classPool.appendSystemPath();
130 classPool.insertClassPath(new ByteArrayClassPath(className, bos.toByteArray()));
131 return classPool.get(className);
132 }
133
134 private static ClassEntry getClassEntry(JarEntry entry) {
135 return new ClassEntry(entry.getName().substring(0, entry.getName().length() - ".class".length()));
136 }
137}
diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java
new file mode 100644
index 00000000..5c8ec1c4
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/JarIndex.java
@@ -0,0 +1,837 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.lang.reflect.Modifier;
14import java.util.Collection;
15import java.util.Collections;
16import java.util.HashSet;
17import java.util.List;
18import java.util.Map;
19import java.util.Set;
20import java.util.jar.JarFile;
21
22import javassist.CannotCompileException;
23import javassist.CtBehavior;
24import javassist.CtClass;
25import javassist.CtConstructor;
26import javassist.CtField;
27import javassist.CtMethod;
28import javassist.NotFoundException;
29import javassist.bytecode.AccessFlag;
30import javassist.bytecode.Descriptor;
31import javassist.bytecode.EnclosingMethodAttribute;
32import javassist.bytecode.FieldInfo;
33import javassist.bytecode.InnerClassesAttribute;
34import javassist.expr.ConstructorCall;
35import javassist.expr.ExprEditor;
36import javassist.expr.FieldAccess;
37import javassist.expr.MethodCall;
38import javassist.expr.NewExpr;
39
40import com.google.common.collect.HashMultimap;
41import com.google.common.collect.Lists;
42import com.google.common.collect.Maps;
43import com.google.common.collect.Multimap;
44import com.google.common.collect.Sets;
45
46import cuchaz.enigma.Constants;
47import cuchaz.enigma.bytecode.ClassRenamer;
48import cuchaz.enigma.mapping.ArgumentEntry;
49import cuchaz.enigma.mapping.BehaviorEntry;
50import cuchaz.enigma.mapping.ClassEntry;
51import cuchaz.enigma.mapping.ConstructorEntry;
52import cuchaz.enigma.mapping.Entry;
53import cuchaz.enigma.mapping.EntryFactory;
54import cuchaz.enigma.mapping.FieldEntry;
55import cuchaz.enigma.mapping.MethodEntry;
56import cuchaz.enigma.mapping.Translator;
57
58public class JarIndex {
59
60 private Set<ClassEntry> m_obfClassEntries;
61 private TranslationIndex m_translationIndex;
62 private Map<Entry,Access> m_access;
63 private Multimap<ClassEntry,FieldEntry> m_fields;
64 private Multimap<ClassEntry,BehaviorEntry> m_behaviors;
65 private Multimap<String,MethodEntry> m_methodImplementations;
66 private Multimap<BehaviorEntry,EntryReference<BehaviorEntry,BehaviorEntry>> m_behaviorReferences;
67 private Multimap<FieldEntry,EntryReference<FieldEntry,BehaviorEntry>> m_fieldReferences;
68 private Multimap<ClassEntry,ClassEntry> m_innerClassesByOuter;
69 private Map<ClassEntry,ClassEntry> m_outerClassesByInner;
70 private Map<ClassEntry,BehaviorEntry> m_anonymousClasses;
71 private Map<MethodEntry,MethodEntry> m_bridgedMethods;
72
73 public JarIndex() {
74 m_obfClassEntries = Sets.newHashSet();
75 m_translationIndex = new TranslationIndex();
76 m_access = Maps.newHashMap();
77 m_fields = HashMultimap.create();
78 m_behaviors = HashMultimap.create();
79 m_methodImplementations = HashMultimap.create();
80 m_behaviorReferences = HashMultimap.create();
81 m_fieldReferences = HashMultimap.create();
82 m_innerClassesByOuter = HashMultimap.create();
83 m_outerClassesByInner = Maps.newHashMap();
84 m_anonymousClasses = Maps.newHashMap();
85 m_bridgedMethods = Maps.newHashMap();
86 }
87
88 public void indexJar(JarFile jar, boolean buildInnerClasses) {
89
90 // step 1: read the class names
91 for (ClassEntry classEntry : JarClassIterator.getClassEntries(jar)) {
92 if (classEntry.isInDefaultPackage()) {
93 // move out of default package
94 classEntry = new ClassEntry(Constants.NonePackage + "/" + classEntry.getName());
95 }
96 m_obfClassEntries.add(classEntry);
97 }
98
99 // step 2: index field/method/constructor access
100 for (CtClass c : JarClassIterator.classes(jar)) {
101 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
102 for (CtField field : c.getDeclaredFields()) {
103 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
104 m_access.put(fieldEntry, Access.get(field));
105 m_fields.put(fieldEntry.getClassEntry(), fieldEntry);
106 }
107 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
108 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
109 m_access.put(behaviorEntry, Access.get(behavior));
110 m_behaviors.put(behaviorEntry.getClassEntry(), behaviorEntry);
111 }
112 }
113
114 // step 3: index extends, implements, fields, and methods
115 for (CtClass c : JarClassIterator.classes(jar)) {
116 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
117 m_translationIndex.indexClass(c);
118 String className = Descriptor.toJvmName(c.getName());
119 for (String interfaceName : c.getClassFile().getInterfaces()) {
120 className = Descriptor.toJvmName(className);
121 interfaceName = Descriptor.toJvmName(interfaceName);
122 if (className.equals(interfaceName)) {
123 throw new IllegalArgumentException("Class cannot be its own interface! " + className);
124 }
125 }
126 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
127 indexBehavior(behavior);
128 }
129 }
130
131 // step 4: index field, method, constructor references
132 for (CtClass c : JarClassIterator.classes(jar)) {
133 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
134 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
135 indexBehaviorReferences(behavior);
136 }
137 }
138
139 if (buildInnerClasses) {
140
141 // step 5: index inner classes and anonymous classes
142 for (CtClass c : JarClassIterator.classes(jar)) {
143 ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage);
144 ClassEntry innerClassEntry = EntryFactory.getClassEntry(c);
145 ClassEntry outerClassEntry = findOuterClass(c);
146 if (outerClassEntry != null) {
147 m_innerClassesByOuter.put(outerClassEntry, innerClassEntry);
148 boolean innerWasAdded = m_outerClassesByInner.put(innerClassEntry, outerClassEntry) == null;
149 assert (innerWasAdded);
150
151 BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassEntry);
152 if (enclosingBehavior != null) {
153 m_anonymousClasses.put(innerClassEntry, enclosingBehavior);
154
155 // DEBUG
156 //System.out.println("ANONYMOUS: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
157 } else {
158 // DEBUG
159 //System.out.println("INNER: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName());
160 }
161 }
162 }
163
164 // step 6: update other indices with inner class info
165 Map<String,String> renames = Maps.newHashMap();
166 for (ClassEntry innerClassEntry : m_innerClassesByOuter.values()) {
167 String newName = innerClassEntry.buildClassEntry(getObfClassChain(innerClassEntry)).getName();
168 if (!innerClassEntry.getName().equals(newName)) {
169 // DEBUG
170 //System.out.println("REPLACE: " + innerClassEntry.getName() + " WITH " + newName);
171 renames.put(innerClassEntry.getName(), newName);
172 }
173 }
174 EntryRenamer.renameClassesInSet(renames, m_obfClassEntries);
175 m_translationIndex.renameClasses(renames);
176 EntryRenamer.renameClassesInMultimap(renames, m_methodImplementations);
177 EntryRenamer.renameClassesInMultimap(renames, m_behaviorReferences);
178 EntryRenamer.renameClassesInMultimap(renames, m_fieldReferences);
179 EntryRenamer.renameClassesInMap(renames, m_access);
180 }
181 }
182
183 private void indexBehavior(CtBehavior behavior) {
184 // get the behavior entry
185 final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
186 if (behaviorEntry instanceof MethodEntry) {
187 MethodEntry methodEntry = (MethodEntry)behaviorEntry;
188
189 // index implementation
190 m_methodImplementations.put(behaviorEntry.getClassName(), methodEntry);
191
192 // look for bridge and bridged methods
193 CtMethod bridgedMethod = getBridgedMethod((CtMethod)behavior);
194 if (bridgedMethod != null) {
195 m_bridgedMethods.put(methodEntry, EntryFactory.getMethodEntry(bridgedMethod));
196 }
197 }
198 // looks like we don't care about constructors here
199 }
200
201 private void indexBehaviorReferences(CtBehavior behavior) {
202 // index method calls
203 final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
204 try {
205 behavior.instrument(new ExprEditor() {
206 @Override
207 public void edit(MethodCall call) {
208 MethodEntry calledMethodEntry = EntryFactory.getMethodEntry(call);
209 ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledMethodEntry);
210 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) {
211 calledMethodEntry = new MethodEntry(
212 resolvedClassEntry,
213 calledMethodEntry.getName(),
214 calledMethodEntry.getSignature()
215 );
216 }
217 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
218 calledMethodEntry,
219 call.getMethodName(),
220 behaviorEntry
221 );
222 m_behaviorReferences.put(calledMethodEntry, reference);
223 }
224
225 @Override
226 public void edit(FieldAccess call) {
227 FieldEntry calledFieldEntry = EntryFactory.getFieldEntry(call);
228 ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledFieldEntry);
229 if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) {
230 calledFieldEntry = new FieldEntry(calledFieldEntry, resolvedClassEntry);
231 }
232 EntryReference<FieldEntry,BehaviorEntry> reference = new EntryReference<FieldEntry,BehaviorEntry>(
233 calledFieldEntry,
234 call.getFieldName(),
235 behaviorEntry
236 );
237 m_fieldReferences.put(calledFieldEntry, reference);
238 }
239
240 @Override
241 public void edit(ConstructorCall call) {
242 ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
243 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
244 calledConstructorEntry,
245 call.getMethodName(),
246 behaviorEntry
247 );
248 m_behaviorReferences.put(calledConstructorEntry, reference);
249 }
250
251 @Override
252 public void edit(NewExpr call) {
253 ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call);
254 EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>(
255 calledConstructorEntry,
256 call.getClassName(),
257 behaviorEntry
258 );
259 m_behaviorReferences.put(calledConstructorEntry, reference);
260 }
261 });
262 } catch (CannotCompileException ex) {
263 throw new Error(ex);
264 }
265 }
266
267 private CtMethod getBridgedMethod(CtMethod method) {
268
269 // bridge methods just call another method, cast it to the return type, and return the result
270 // let's see if we can detect this scenario
271
272 // skip non-synthetic methods
273 if ((method.getModifiers() & AccessFlag.SYNTHETIC) == 0) {
274 return null;
275 }
276
277 // get all the called methods
278 final List<MethodCall> methodCalls = Lists.newArrayList();
279 try {
280 method.instrument(new ExprEditor() {
281 @Override
282 public void edit(MethodCall call) {
283 methodCalls.add(call);
284 }
285 });
286 } catch (CannotCompileException ex) {
287 // this is stupid... we're not even compiling anything
288 throw new Error(ex);
289 }
290
291 // is there just one?
292 if (methodCalls.size() != 1) {
293 return null;
294 }
295 MethodCall call = methodCalls.get(0);
296
297 try {
298 // we have a bridge method!
299 return call.getMethod();
300 } catch (NotFoundException ex) {
301 // can't find the type? not a bridge method
302 return null;
303 }
304 }
305
306 private ClassEntry findOuterClass(CtClass c) {
307
308 ClassEntry classEntry = EntryFactory.getClassEntry(c);
309
310 // does this class already have an outer class?
311 if (classEntry.isInnerClass()) {
312 return classEntry.getOuterClassEntry();
313 }
314
315 // inner classes:
316 // have constructors that can (illegally) set synthetic fields
317 // the outer class is the only class that calls constructors
318
319 // use the synthetic fields to find the synthetic constructors
320 for (CtConstructor constructor : c.getDeclaredConstructors()) {
321 Set<String> syntheticFieldTypes = Sets.newHashSet();
322 if (!isIllegalConstructor(syntheticFieldTypes, constructor)) {
323 continue;
324 }
325
326 ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
327
328 // gather the classes from the illegally-set synthetic fields
329 Set<ClassEntry> illegallySetClasses = Sets.newHashSet();
330 for (String type : syntheticFieldTypes) {
331 if (type.startsWith("L")) {
332 ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1));
333 if (isSaneOuterClass(outerClassEntry, classEntry)) {
334 illegallySetClasses.add(outerClassEntry);
335 }
336 }
337 }
338
339 // who calls this constructor?
340 Set<ClassEntry> callerClasses = Sets.newHashSet();
341 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : getBehaviorReferences(constructorEntry)) {
342
343 // make sure it's not a call to super
344 if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) {
345
346 // is the entry a superclass of the context?
347 ClassEntry calledClassEntry = reference.entry.getClassEntry();
348 ClassEntry superclassEntry = m_translationIndex.getSuperclass(reference.context.getClassEntry());
349 if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) {
350 // it's a super call, skip
351 continue;
352 }
353 }
354
355 if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) {
356 callerClasses.add(reference.context.getClassEntry());
357 }
358 }
359
360 // do we have an answer yet?
361 if (callerClasses.isEmpty()) {
362 if (illegallySetClasses.size() == 1) {
363 return illegallySetClasses.iterator().next();
364 } else {
365 System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry));
366 }
367 } else {
368 if (callerClasses.size() == 1) {
369 return callerClasses.iterator().next();
370 } else {
371 // multiple callers, do the illegally set classes narrow it down?
372 Set<ClassEntry> intersection = Sets.newHashSet(callerClasses);
373 intersection.retainAll(illegallySetClasses);
374 if (intersection.size() == 1) {
375 return intersection.iterator().next();
376 } else {
377 System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses));
378 }
379 }
380 }
381 }
382
383 return null;
384 }
385
386 private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) {
387
388 // clearly this would be silly
389 if (outerClassEntry.equals(innerClassEntry)) {
390 return false;
391 }
392
393 // is the outer class in the jar?
394 if (!m_obfClassEntries.contains(outerClassEntry)) {
395 return false;
396 }
397
398 return true;
399 }
400
401 @SuppressWarnings("unchecked")
402 private boolean isIllegalConstructor(Set<String> syntheticFieldTypes, CtConstructor constructor) {
403
404 // illegal constructors only set synthetic member fields, then call super()
405 String className = constructor.getDeclaringClass().getName();
406
407 // collect all the field accesses, constructor calls, and method calls
408 final List<FieldAccess> illegalFieldWrites = Lists.newArrayList();
409 final List<ConstructorCall> constructorCalls = Lists.newArrayList();
410 try {
411 constructor.instrument(new ExprEditor() {
412 @Override
413 public void edit(FieldAccess fieldAccess) {
414 if (fieldAccess.isWriter() && constructorCalls.isEmpty()) {
415 illegalFieldWrites.add(fieldAccess);
416 }
417 }
418
419 @Override
420 public void edit(ConstructorCall constructorCall) {
421 constructorCalls.add(constructorCall);
422 }
423 });
424 } catch (CannotCompileException ex) {
425 // we're not compiling anything... this is stupid
426 throw new Error(ex);
427 }
428
429 // are there any illegal field writes?
430 if (illegalFieldWrites.isEmpty()) {
431 return false;
432 }
433
434 // are all the writes to synthetic fields?
435 for (FieldAccess fieldWrite : illegalFieldWrites) {
436
437 // all illegal writes have to be to the local class
438 if (!fieldWrite.getClassName().equals(className)) {
439 System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName()));
440 return false;
441 }
442
443 // find the field
444 FieldInfo fieldInfo = null;
445 for (FieldInfo info : (List<FieldInfo>)constructor.getDeclaringClass().getClassFile().getFields()) {
446 if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) {
447 fieldInfo = info;
448 break;
449 }
450 }
451 if (fieldInfo == null) {
452 // field is in a superclass or something, can't be a local synthetic member
453 return false;
454 }
455
456 // is this field synthetic?
457 boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0;
458 if (isSynthetic) {
459 syntheticFieldTypes.add(fieldInfo.getDescriptor());
460 } else {
461 System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName()));
462 return false;
463 }
464 }
465
466 // we passed all the tests!
467 return true;
468 }
469
470 private BehaviorEntry isAnonymousClass(CtClass c, ClassEntry outerClassEntry) {
471
472 // is this class already marked anonymous?
473 EnclosingMethodAttribute enclosingMethodAttribute = (EnclosingMethodAttribute)c.getClassFile().getAttribute(EnclosingMethodAttribute.tag);
474 if (enclosingMethodAttribute != null) {
475 if (enclosingMethodAttribute.methodIndex() > 0) {
476 return EntryFactory.getBehaviorEntry(
477 Descriptor.toJvmName(enclosingMethodAttribute.className()),
478 enclosingMethodAttribute.methodName(),
479 enclosingMethodAttribute.methodDescriptor()
480 );
481 } else {
482 // an attribute but no method? assume not anonymous
483 return null;
484 }
485 }
486
487 // if there's an inner class attribute, but not an enclosing method attribute, then it's not anonymous
488 InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
489 if (innerClassesAttribute != null) {
490 return null;
491 }
492
493 ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
494
495 // anonymous classes:
496 // can't be abstract
497 // have only one constructor
498 // it's called exactly once by the outer class
499 // the type the instance is assigned to can't be this type
500
501 // is abstract?
502 if (Modifier.isAbstract(c.getModifiers())) {
503 return null;
504 }
505
506 // is there exactly one constructor?
507 if (c.getDeclaredConstructors().length != 1) {
508 return null;
509 }
510 CtConstructor constructor = c.getDeclaredConstructors()[0];
511
512 // is this constructor called exactly once?
513 ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor);
514 Collection<EntryReference<BehaviorEntry,BehaviorEntry>> references = getBehaviorReferences(constructorEntry);
515 if (references.size() != 1) {
516 return null;
517 }
518
519 // does the caller use this type?
520 BehaviorEntry caller = references.iterator().next().context;
521 for (FieldEntry fieldEntry : getReferencedFields(caller)) {
522 if (fieldEntry.getType().hasClass() && fieldEntry.getType().getClassEntry().equals(innerClassEntry)) {
523 // caller references this type, so it can't be anonymous
524 return null;
525 }
526 }
527 for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) {
528 if (behaviorEntry.getSignature().hasClass(innerClassEntry)) {
529 return null;
530 }
531 }
532
533 return caller;
534 }
535
536 public Set<ClassEntry> getObfClassEntries() {
537 return m_obfClassEntries;
538 }
539
540 public Collection<FieldEntry> getObfFieldEntries() {
541 return m_fields.values();
542 }
543
544 public Collection<FieldEntry> getObfFieldEntries(ClassEntry classEntry) {
545 return m_fields.get(classEntry);
546 }
547
548 public Collection<BehaviorEntry> getObfBehaviorEntries() {
549 return m_behaviors.values();
550 }
551
552 public Collection<BehaviorEntry> getObfBehaviorEntries(ClassEntry classEntry) {
553 return m_behaviors.get(classEntry);
554 }
555
556 public TranslationIndex getTranslationIndex() {
557 return m_translationIndex;
558 }
559
560 public Access getAccess(Entry entry) {
561 return m_access.get(entry);
562 }
563
564 public ClassInheritanceTreeNode getClassInheritance(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
565
566 // get the root node
567 List<String> ancestry = Lists.newArrayList();
568 ancestry.add(obfClassEntry.getName());
569 for (ClassEntry classEntry : m_translationIndex.getAncestry(obfClassEntry)) {
570 ancestry.add(classEntry.getName());
571 }
572 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(
573 deobfuscatingTranslator,
574 ancestry.get(ancestry.size() - 1)
575 );
576
577 // expand all children recursively
578 rootNode.load(m_translationIndex, true);
579
580 return rootNode;
581 }
582
583 public ClassImplementationsTreeNode getClassImplementations(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) {
584
585 // is this even an interface?
586 if (isInterface(obfClassEntry.getClassName())) {
587 ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(deobfuscatingTranslator, obfClassEntry);
588 node.load(this);
589 return node;
590 }
591 return null;
592 }
593
594 public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
595
596 // travel to the ancestor implementation
597 ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry();
598 for (ClassEntry ancestorClassEntry : m_translationIndex.getAncestry(obfMethodEntry.getClassEntry())) {
599 MethodEntry ancestorMethodEntry = new MethodEntry(
600 new ClassEntry(ancestorClassEntry),
601 obfMethodEntry.getName(),
602 obfMethodEntry.getSignature()
603 );
604 if (containsObfBehavior(ancestorMethodEntry)) {
605 baseImplementationClassEntry = ancestorClassEntry;
606 }
607 }
608
609 // make a root node at the base
610 MethodEntry methodEntry = new MethodEntry(
611 baseImplementationClassEntry,
612 obfMethodEntry.getName(),
613 obfMethodEntry.getSignature()
614 );
615 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode(
616 deobfuscatingTranslator,
617 methodEntry,
618 containsObfBehavior(methodEntry)
619 );
620
621 // expand the full tree
622 rootNode.load(this, true);
623
624 return rootNode;
625 }
626
627 public List<MethodImplementationsTreeNode> getMethodImplementations(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) {
628
629 List<MethodEntry> interfaceMethodEntries = Lists.newArrayList();
630
631 // is this method on an interface?
632 if (isInterface(obfMethodEntry.getClassName())) {
633 interfaceMethodEntries.add(obfMethodEntry);
634 } else {
635 // get the interface class
636 for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) {
637
638 // is this method defined in this interface?
639 MethodEntry methodInterface = new MethodEntry(
640 interfaceEntry,
641 obfMethodEntry.getName(),
642 obfMethodEntry.getSignature()
643 );
644 if (containsObfBehavior(methodInterface)) {
645 interfaceMethodEntries.add(methodInterface);
646 }
647 }
648 }
649
650 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
651 if (!interfaceMethodEntries.isEmpty()) {
652 for (MethodEntry interfaceMethodEntry : interfaceMethodEntries) {
653 MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(deobfuscatingTranslator, interfaceMethodEntry);
654 node.load(this);
655 nodes.add(node);
656 }
657 }
658 return nodes;
659 }
660
661 public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) {
662 Set<MethodEntry> methodEntries = Sets.newHashSet();
663 getRelatedMethodImplementations(methodEntries, getMethodInheritance(null, obfMethodEntry));
664 return methodEntries;
665 }
666
667 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) {
668 MethodEntry methodEntry = node.getMethodEntry();
669 if (containsObfBehavior(methodEntry)) {
670 // collect the entry
671 methodEntries.add(methodEntry);
672 }
673
674 // look at interface methods too
675 for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(null, methodEntry)) {
676 getRelatedMethodImplementations(methodEntries, implementationsNode);
677 }
678
679 // recurse
680 for (int i = 0; i < node.getChildCount(); i++) {
681 getRelatedMethodImplementations(methodEntries, (MethodInheritanceTreeNode)node.getChildAt(i));
682 }
683 }
684
685 private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) {
686 MethodEntry methodEntry = node.getMethodEntry();
687 if (containsObfBehavior(methodEntry)) {
688 // collect the entry
689 methodEntries.add(methodEntry);
690 }
691
692 // recurse
693 for (int i = 0; i < node.getChildCount(); i++) {
694 getRelatedMethodImplementations(methodEntries, (MethodImplementationsTreeNode)node.getChildAt(i));
695 }
696 }
697
698 public Collection<EntryReference<FieldEntry,BehaviorEntry>> getFieldReferences(FieldEntry fieldEntry) {
699 return m_fieldReferences.get(fieldEntry);
700 }
701
702 public Collection<FieldEntry> getReferencedFields(BehaviorEntry behaviorEntry) {
703 // linear search is fast enough for now
704 Set<FieldEntry> fieldEntries = Sets.newHashSet();
705 for (EntryReference<FieldEntry,BehaviorEntry> reference : m_fieldReferences.values()) {
706 if (reference.context == behaviorEntry) {
707 fieldEntries.add(reference.entry);
708 }
709 }
710 return fieldEntries;
711 }
712
713 public Collection<EntryReference<BehaviorEntry,BehaviorEntry>> getBehaviorReferences(BehaviorEntry behaviorEntry) {
714 return m_behaviorReferences.get(behaviorEntry);
715 }
716
717 public Collection<BehaviorEntry> getReferencedBehaviors(BehaviorEntry behaviorEntry) {
718 // linear search is fast enough for now
719 Set<BehaviorEntry> behaviorEntries = Sets.newHashSet();
720 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : m_behaviorReferences.values()) {
721 if (reference.context == behaviorEntry) {
722 behaviorEntries.add(reference.entry);
723 }
724 }
725 return behaviorEntries;
726 }
727
728 public Collection<ClassEntry> getInnerClasses(ClassEntry obfOuterClassEntry) {
729 return m_innerClassesByOuter.get(obfOuterClassEntry);
730 }
731
732 public ClassEntry getOuterClass(ClassEntry obfInnerClassEntry) {
733 return m_outerClassesByInner.get(obfInnerClassEntry);
734 }
735
736 public boolean isAnonymousClass(ClassEntry obfInnerClassEntry) {
737 return m_anonymousClasses.containsKey(obfInnerClassEntry);
738 }
739
740 public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName) {
741 return m_anonymousClasses.get(obfInnerClassName);
742 }
743
744 public Set<ClassEntry> getInterfaces(String className) {
745 ClassEntry classEntry = new ClassEntry(className);
746 Set<ClassEntry> interfaces = new HashSet<ClassEntry>();
747 interfaces.addAll(m_translationIndex.getInterfaces(classEntry));
748 for (ClassEntry ancestor : m_translationIndex.getAncestry(classEntry)) {
749 interfaces.addAll(m_translationIndex.getInterfaces(ancestor));
750 }
751 return interfaces;
752 }
753
754 public Set<String> getImplementingClasses(String targetInterfaceName) {
755
756 // linear search is fast enough for now
757 Set<String> classNames = Sets.newHashSet();
758 for (Map.Entry<ClassEntry,ClassEntry> entry : m_translationIndex.getClassInterfaces()) {
759 ClassEntry classEntry = entry.getKey();
760 ClassEntry interfaceEntry = entry.getValue();
761 if (interfaceEntry.getName().equals(targetInterfaceName)) {
762 classNames.add(classEntry.getClassName());
763 m_translationIndex.getSubclassNamesRecursively(classNames, classEntry);
764 }
765 }
766 return classNames;
767 }
768
769 public boolean isInterface(String className) {
770 return m_translationIndex.isInterface(new ClassEntry(className));
771 }
772
773 public boolean containsObfClass(ClassEntry obfClassEntry) {
774 return m_obfClassEntries.contains(obfClassEntry);
775 }
776
777 public boolean containsObfField(FieldEntry obfFieldEntry) {
778 return m_access.containsKey(obfFieldEntry);
779 }
780
781 public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) {
782 return m_access.containsKey(obfBehaviorEntry);
783 }
784
785 public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) {
786 // check the behavior
787 if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) {
788 return false;
789 }
790
791 // check the argument
792 if (obfArgumentEntry.getIndex() >= obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size()) {
793 return false;
794 }
795
796 return true;
797 }
798
799 public boolean containsObfEntry(Entry obfEntry) {
800 if (obfEntry instanceof ClassEntry) {
801 return containsObfClass((ClassEntry)obfEntry);
802 } else if (obfEntry instanceof FieldEntry) {
803 return containsObfField((FieldEntry)obfEntry);
804 } else if (obfEntry instanceof BehaviorEntry) {
805 return containsObfBehavior((BehaviorEntry)obfEntry);
806 } else if (obfEntry instanceof ArgumentEntry) {
807 return containsObfArgument((ArgumentEntry)obfEntry);
808 } else {
809 throw new Error("Entry type not supported: " + obfEntry.getClass().getName());
810 }
811 }
812
813 public MethodEntry getBridgedMethod(MethodEntry bridgeMethodEntry) {
814 return m_bridgedMethods.get(bridgeMethodEntry);
815 }
816
817 public List<ClassEntry> getObfClassChain(ClassEntry obfClassEntry) {
818
819 // build class chain in inner-to-outer order
820 List<ClassEntry> obfClassChain = Lists.newArrayList(obfClassEntry);
821 ClassEntry checkClassEntry = obfClassEntry;
822 while (true) {
823 ClassEntry obfOuterClassEntry = getOuterClass(checkClassEntry);
824 if (obfOuterClassEntry != null) {
825 obfClassChain.add(obfOuterClassEntry);
826 checkClassEntry = obfOuterClassEntry;
827 } else {
828 break;
829 }
830 }
831
832 // switch to outer-to-inner order
833 Collections.reverse(obfClassChain);
834
835 return obfClassChain;
836 }
837}
diff --git a/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java
new file mode 100644
index 00000000..aa0aeca6
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java
@@ -0,0 +1,101 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.List;
14
15import javax.swing.tree.DefaultMutableTreeNode;
16
17import com.google.common.collect.Lists;
18
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.MethodEntry;
21import cuchaz.enigma.mapping.Translator;
22
23public class MethodImplementationsTreeNode extends DefaultMutableTreeNode {
24
25 private static final long serialVersionUID = 3781080657461899915L;
26
27 private Translator m_deobfuscatingTranslator;
28 private MethodEntry m_entry;
29
30 public MethodImplementationsTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) {
31 if (entry == null) {
32 throw new IllegalArgumentException("entry cannot be null!");
33 }
34
35 m_deobfuscatingTranslator = deobfuscatingTranslator;
36 m_entry = entry;
37 }
38
39 public MethodEntry getMethodEntry() {
40 return m_entry;
41 }
42
43 public String getDeobfClassName() {
44 return m_deobfuscatingTranslator.translateClass(m_entry.getClassName());
45 }
46
47 public String getDeobfMethodName() {
48 return m_deobfuscatingTranslator.translate(m_entry);
49 }
50
51 @Override
52 public String toString() {
53 String className = getDeobfClassName();
54 if (className == null) {
55 className = m_entry.getClassName();
56 }
57
58 String methodName = getDeobfMethodName();
59 if (methodName == null) {
60 methodName = m_entry.getName();
61 }
62 return className + "." + methodName + "()";
63 }
64
65 public void load(JarIndex index) {
66
67 // get all method implementations
68 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
69 for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) {
70 MethodEntry methodEntry = new MethodEntry(
71 new ClassEntry(implementingClassName),
72 m_entry.getName(),
73 m_entry.getSignature()
74 );
75 if (index.containsObfBehavior(methodEntry)) {
76 nodes.add(new MethodImplementationsTreeNode(m_deobfuscatingTranslator, methodEntry));
77 }
78 }
79
80 // add them to this node
81 for (MethodImplementationsTreeNode node : nodes) {
82 this.add(node);
83 }
84 }
85
86 public static MethodImplementationsTreeNode findNode(MethodImplementationsTreeNode node, MethodEntry entry) {
87 // is this the node?
88 if (node.getMethodEntry().equals(entry)) {
89 return node;
90 }
91
92 // recurse
93 for (int i = 0; i < node.getChildCount(); i++) {
94 MethodImplementationsTreeNode foundNode = findNode((MethodImplementationsTreeNode)node.getChildAt(i), entry);
95 if (foundNode != null) {
96 return foundNode;
97 }
98 }
99 return null;
100 }
101}
diff --git a/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java
new file mode 100644
index 00000000..0da3c8c9
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java
@@ -0,0 +1,114 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..4d81bf1c
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/ReferenceTreeNode.java
@@ -0,0 +1,18 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.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/RelatedMethodChecker.java b/src/cuchaz/enigma/analysis/RelatedMethodChecker.java
new file mode 100644
index 00000000..e592a1c3
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/RelatedMethodChecker.java
@@ -0,0 +1,106 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.Map;
14import java.util.Set;
15
16import com.google.common.collect.Maps;
17import com.google.common.collect.Sets;
18
19import cuchaz.enigma.mapping.BehaviorEntry;
20import cuchaz.enigma.mapping.ClassEntry;
21import cuchaz.enigma.mapping.EntryFactory;
22import cuchaz.enigma.mapping.MethodEntry;
23import cuchaz.enigma.mapping.MethodMapping;
24
25public class RelatedMethodChecker {
26
27 private JarIndex m_jarIndex;
28 private Map<Set<MethodEntry>,String> m_deobfNamesByGroup;
29 private Map<MethodEntry,String> m_deobfNamesByObfMethod;
30 private Map<MethodEntry,Set<MethodEntry>> m_groupsByObfMethod;
31 private Set<Set<MethodEntry>> m_inconsistentGroups;
32
33 public RelatedMethodChecker(JarIndex jarIndex) {
34 m_jarIndex = jarIndex;
35 m_deobfNamesByGroup = Maps.newHashMap();
36 m_deobfNamesByObfMethod = Maps.newHashMap();
37 m_groupsByObfMethod = Maps.newHashMap();
38 m_inconsistentGroups = Sets.newHashSet();
39 }
40
41 public void checkMethod(ClassEntry classEntry, MethodMapping methodMapping) {
42
43 // TEMP: disable the expensive check for now, maybe we can optimize it later, or just use it for debugging
44 if (true) return;
45
46 BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping);
47 if (!(obfBehaviorEntry instanceof MethodEntry)) {
48 // only methods have related implementations
49 return;
50 }
51 MethodEntry obfMethodEntry = (MethodEntry)obfBehaviorEntry;
52 String deobfName = methodMapping.getDeobfName();
53 m_deobfNamesByObfMethod.put(obfMethodEntry, deobfName);
54
55 // have we seen this method's group before?
56 Set<MethodEntry> group = m_groupsByObfMethod.get(obfMethodEntry);
57 if (group == null) {
58
59 // no, compute the group and save the name
60 group = m_jarIndex.getRelatedMethodImplementations(obfMethodEntry);
61 m_deobfNamesByGroup.put(group, deobfName);
62
63 assert(group.contains(obfMethodEntry));
64 for (MethodEntry relatedMethodEntry : group) {
65 m_groupsByObfMethod.put(relatedMethodEntry, group);
66 }
67 }
68
69 // check the name
70 if (!sameName(m_deobfNamesByGroup.get(group), deobfName)) {
71 m_inconsistentGroups.add(group);
72 }
73 }
74
75 private boolean sameName(String a, String b) {
76 if (a == null && b == null) {
77 return true;
78 } else if (a != null && b != null) {
79 return a.equals(b);
80 }
81 return false;
82 }
83
84 public boolean hasProblems() {
85 return m_inconsistentGroups.size() > 0;
86 }
87
88 public String getReport() {
89 StringBuilder buf = new StringBuilder();
90 buf.append(m_inconsistentGroups.size());
91 buf.append(" groups of methods related by inheritance and/or interfaces have different deobf names!\n");
92 for (Set<MethodEntry> group : m_inconsistentGroups) {
93 buf.append("\tGroup with ");
94 buf.append(group.size());
95 buf.append(" methods:\n");
96 for (MethodEntry methodEntry : group) {
97 buf.append("\t\t");
98 buf.append(methodEntry.toString());
99 buf.append(" => ");
100 buf.append(m_deobfNamesByObfMethod.get(methodEntry));
101 buf.append("\n");
102 }
103 }
104 return buf.toString();
105 }
106}
diff --git a/src/cuchaz/enigma/analysis/SourceIndex.java b/src/cuchaz/enigma/analysis/SourceIndex.java
new file mode 100644
index 00000000..3c4ac464
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/SourceIndex.java
@@ -0,0 +1,184 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.util.Collection;
14import java.util.List;
15import java.util.Map;
16import java.util.TreeMap;
17
18import com.google.common.collect.HashMultimap;
19import com.google.common.collect.Lists;
20import com.google.common.collect.Maps;
21import com.google.common.collect.Multimap;
22import com.strobel.decompiler.languages.Region;
23import com.strobel.decompiler.languages.java.ast.AstNode;
24import com.strobel.decompiler.languages.java.ast.Identifier;
25
26import cuchaz.enigma.mapping.Entry;
27
28public class SourceIndex {
29
30 private String m_source;
31 private TreeMap<Token,EntryReference<Entry,Entry>> m_tokenToReference;
32 private Multimap<EntryReference<Entry,Entry>,Token> m_referenceToTokens;
33 private Map<Entry,Token> m_declarationToToken;
34 private List<Integer> m_lineOffsets;
35 private boolean m_ignoreBadTokens;
36
37 public SourceIndex(String source) {
38 this(source, true);
39 }
40
41 public SourceIndex(String source, boolean ignoreBadTokens) {
42 m_source = source;
43 m_ignoreBadTokens = ignoreBadTokens;
44 m_tokenToReference = Maps.newTreeMap();
45 m_referenceToTokens = HashMultimap.create();
46 m_declarationToToken = Maps.newHashMap();
47 m_lineOffsets = Lists.newArrayList();
48
49 // count the lines
50 m_lineOffsets.add(0);
51 for (int i = 0; i < source.length(); i++) {
52 if (source.charAt(i) == '\n') {
53 m_lineOffsets.add(i + 1);
54 }
55 }
56 }
57
58 public String getSource() {
59 return m_source;
60 }
61
62 public Token getToken(AstNode node) {
63
64 // get the text of the node
65 String name = "";
66 if (node instanceof Identifier) {
67 name = ((Identifier)node).getName();
68 }
69
70 // get a token for this node's region
71 Region region = node.getRegion();
72 if (region.getBeginLine() == 0 || region.getEndLine() == 0) {
73 // DEBUG
74 System.err.println(String.format("WARNING: %s \"%s\" has invalid region: %s", node.getNodeType(), name, region));
75 return null;
76 }
77 Token token = new Token(
78 toPos(region.getBeginLine(), region.getBeginColumn()),
79 toPos(region.getEndLine(), region.getEndColumn()),
80 m_source
81 );
82 if (token.start == 0) {
83 // DEBUG
84 System.err.println(String.format("WARNING: %s \"%s\" has invalid start: %s", node.getNodeType(), name, region));
85 return null;
86 }
87
88 // DEBUG
89 // System.out.println( String.format( "%s \"%s\" region: %s", node.getNodeType(), name, region ) );
90
91 // if the token has a $ in it, something's wrong. Ignore this token
92 if (name.lastIndexOf('$') >= 0 && m_ignoreBadTokens) {
93 // DEBUG
94 System.err.println(String.format("WARNING: %s \"%s\" is probably a bad token. It was ignored", node.getNodeType(), name));
95 return null;
96 }
97
98 return token;
99 }
100
101 public void addReference(AstNode node, Entry deobfEntry, Entry deobfContext) {
102 Token token = getToken(node);
103 if (token != null) {
104 EntryReference<Entry,Entry> deobfReference = new EntryReference<Entry,Entry>(deobfEntry, token.text, deobfContext);
105 m_tokenToReference.put(token, deobfReference);
106 m_referenceToTokens.put(deobfReference, token);
107 }
108 }
109
110 public void addDeclaration(AstNode node, Entry deobfEntry) {
111 Token token = getToken(node);
112 if (token != null) {
113 EntryReference<Entry,Entry> reference = new EntryReference<Entry,Entry>(deobfEntry, token.text);
114 m_tokenToReference.put(token, reference);
115 m_referenceToTokens.put(reference, token);
116 m_declarationToToken.put(deobfEntry, token);
117 }
118 }
119
120 public Token getReferenceToken(int pos) {
121 Token token = m_tokenToReference.floorKey(new Token(pos, pos, null));
122 if (token != null && token.contains(pos)) {
123 return token;
124 }
125 return null;
126 }
127
128 public Collection<Token> getReferenceTokens(EntryReference<Entry,Entry> deobfReference) {
129 return m_referenceToTokens.get(deobfReference);
130 }
131
132 public EntryReference<Entry,Entry> getDeobfReference(Token token) {
133 if (token == null) {
134 return null;
135 }
136 return m_tokenToReference.get(token);
137 }
138
139 public void replaceDeobfReference(Token token, EntryReference<Entry,Entry> newDeobfReference) {
140 EntryReference<Entry,Entry> oldDeobfReference = m_tokenToReference.get(token);
141 m_tokenToReference.put(token, newDeobfReference);
142 Collection<Token> tokens = m_referenceToTokens.get(oldDeobfReference);
143 m_referenceToTokens.removeAll(oldDeobfReference);
144 m_referenceToTokens.putAll(newDeobfReference, tokens);
145 }
146
147 public Iterable<Token> referenceTokens() {
148 return m_tokenToReference.keySet();
149 }
150
151 public Iterable<Token> declarationTokens() {
152 return m_declarationToToken.values();
153 }
154
155 public Iterable<Entry> declarations() {
156 return m_declarationToToken.keySet();
157 }
158
159 public Token getDeclarationToken(Entry deobfEntry) {
160 return m_declarationToToken.get(deobfEntry);
161 }
162
163 public int getLineNumber(int pos) {
164 // line number is 1-based
165 int line = 0;
166 for (Integer offset : m_lineOffsets) {
167 if (offset > pos) {
168 break;
169 }
170 line++;
171 }
172 return line;
173 }
174
175 public int getColumnNumber(int pos) {
176 // column number is 1-based
177 return pos - m_lineOffsets.get(getLineNumber(pos) - 1) + 1;
178 }
179
180 private int toPos(int line, int col) {
181 // line and col are 1-based
182 return m_lineOffsets.get(line - 1) + col - 1;
183 }
184}
diff --git a/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java
new file mode 100644
index 00000000..a660a376
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java
@@ -0,0 +1,150 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.assembler.metadata.MemberReference;
14import com.strobel.assembler.metadata.MethodDefinition;
15import com.strobel.assembler.metadata.MethodReference;
16import com.strobel.assembler.metadata.ParameterDefinition;
17import com.strobel.assembler.metadata.TypeReference;
18import com.strobel.decompiler.languages.TextLocation;
19import com.strobel.decompiler.languages.java.ast.AstNode;
20import com.strobel.decompiler.languages.java.ast.IdentifierExpression;
21import com.strobel.decompiler.languages.java.ast.InvocationExpression;
22import com.strobel.decompiler.languages.java.ast.Keys;
23import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
24import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression;
25import com.strobel.decompiler.languages.java.ast.ParameterDeclaration;
26import com.strobel.decompiler.languages.java.ast.SimpleType;
27import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression;
28import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression;
29
30import cuchaz.enigma.mapping.ArgumentEntry;
31import cuchaz.enigma.mapping.BehaviorEntry;
32import cuchaz.enigma.mapping.ClassEntry;
33import cuchaz.enigma.mapping.ConstructorEntry;
34import cuchaz.enigma.mapping.FieldEntry;
35import cuchaz.enigma.mapping.MethodEntry;
36import cuchaz.enigma.mapping.ProcyonEntryFactory;
37import cuchaz.enigma.mapping.Signature;
38import cuchaz.enigma.mapping.Type;
39
40public class SourceIndexBehaviorVisitor extends SourceIndexVisitor {
41
42 private BehaviorEntry m_behaviorEntry;
43
44 public SourceIndexBehaviorVisitor(BehaviorEntry behaviorEntry) {
45 m_behaviorEntry = behaviorEntry;
46 }
47
48 @Override
49 public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) {
50 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
51
52 // get the behavior entry
53 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
54 BehaviorEntry behaviorEntry = null;
55 if (ref instanceof MethodReference) {
56 MethodReference methodRef = (MethodReference)ref;
57 if (methodRef.isConstructor()) {
58 behaviorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature()));
59 } else if (methodRef.isTypeInitializer()) {
60 behaviorEntry = new ConstructorEntry(classEntry);
61 } else {
62 behaviorEntry = new MethodEntry(classEntry, ref.getName(), new Signature(ref.getErasedSignature()));
63 }
64 }
65 if (behaviorEntry != null) {
66 // get the node for the token
67 AstNode tokenNode = null;
68 if (node.getTarget() instanceof MemberReferenceExpression) {
69 tokenNode = ((MemberReferenceExpression)node.getTarget()).getMemberNameToken();
70 } else if (node.getTarget() instanceof SuperReferenceExpression) {
71 tokenNode = node.getTarget();
72 } else if (node.getTarget() instanceof ThisReferenceExpression) {
73 tokenNode = node.getTarget();
74 }
75 if (tokenNode != null) {
76 index.addReference(tokenNode, behaviorEntry, m_behaviorEntry);
77 }
78 }
79
80 return recurse(node, index);
81 }
82
83 @Override
84 public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) {
85 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
86 if (ref != null) {
87 // make sure this is actually a field
88 if (ref.getErasedSignature().indexOf('(') >= 0) {
89 throw new Error("Expected a field here! got " + ref);
90 }
91
92 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
93 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature()));
94 index.addReference(node.getMemberNameToken(), fieldEntry, m_behaviorEntry);
95 }
96
97 return recurse(node, index);
98 }
99
100 @Override
101 public Void visitSimpleType(SimpleType node, SourceIndex index) {
102 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
103 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
104 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
105 index.addReference(node.getIdentifierToken(), classEntry, m_behaviorEntry);
106 }
107
108 return recurse(node, index);
109 }
110
111 @Override
112 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
113 ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION);
114 if (def.getMethod() instanceof MethodDefinition) {
115 MethodDefinition methodDef = (MethodDefinition)def.getMethod();
116 BehaviorEntry behaviorEntry = ProcyonEntryFactory.getBehaviorEntry(methodDef);
117 ArgumentEntry argumentEntry = new ArgumentEntry(behaviorEntry, def.getPosition(), node.getName());
118 index.addDeclaration(node.getNameToken(), argumentEntry);
119 }
120
121 return recurse(node, index);
122 }
123
124 @Override
125 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
126 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
127 if (ref != null) {
128 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
129 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature()));
130 index.addReference(node.getIdentifierToken(), fieldEntry, m_behaviorEntry);
131 }
132
133 return recurse(node, index);
134 }
135
136 @Override
137 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
138 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
139 if (ref != null) {
140 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
141 ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature()));
142 if (node.getType() instanceof SimpleType) {
143 SimpleType simpleTypeNode = (SimpleType)node.getType();
144 index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, m_behaviorEntry);
145 }
146 }
147
148 return recurse(node, index);
149 }
150}
diff --git a/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
new file mode 100644
index 00000000..db0bc0b7
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java
@@ -0,0 +1,112 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.assembler.metadata.FieldDefinition;
14import com.strobel.assembler.metadata.MethodDefinition;
15import com.strobel.assembler.metadata.TypeDefinition;
16import com.strobel.assembler.metadata.TypeReference;
17import com.strobel.decompiler.languages.TextLocation;
18import com.strobel.decompiler.languages.java.ast.AstNode;
19import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration;
20import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration;
21import com.strobel.decompiler.languages.java.ast.FieldDeclaration;
22import com.strobel.decompiler.languages.java.ast.Keys;
23import com.strobel.decompiler.languages.java.ast.MethodDeclaration;
24import com.strobel.decompiler.languages.java.ast.SimpleType;
25import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
26import com.strobel.decompiler.languages.java.ast.VariableInitializer;
27
28import cuchaz.enigma.mapping.BehaviorEntry;
29import cuchaz.enigma.mapping.ClassEntry;
30import cuchaz.enigma.mapping.ConstructorEntry;
31import cuchaz.enigma.mapping.FieldEntry;
32import cuchaz.enigma.mapping.ProcyonEntryFactory;
33
34public class SourceIndexClassVisitor extends SourceIndexVisitor {
35
36 private ClassEntry m_classEntry;
37
38 public SourceIndexClassVisitor(ClassEntry classEntry) {
39 m_classEntry = classEntry;
40 }
41
42 @Override
43 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
44 // is this this class, or a subtype?
45 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
46 ClassEntry classEntry = new ClassEntry(def.getInternalName());
47 if (!classEntry.equals(m_classEntry)) {
48 // it's a sub-type, recurse
49 index.addDeclaration(node.getNameToken(), classEntry);
50 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
51 }
52
53 return recurse(node, index);
54 }
55
56 @Override
57 public Void visitSimpleType(SimpleType node, SourceIndex index) {
58 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
59 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
60 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
61 index.addReference(node.getIdentifierToken(), classEntry, m_classEntry);
62 }
63
64 return recurse(node, index);
65 }
66
67 @Override
68 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
69 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
70 BehaviorEntry behaviorEntry = ProcyonEntryFactory.getBehaviorEntry(def);
71 AstNode tokenNode = node.getNameToken();
72
73 if (behaviorEntry instanceof ConstructorEntry) {
74 ConstructorEntry constructorEntry = (ConstructorEntry)behaviorEntry;
75 if (constructorEntry.isStatic()) {
76 // for static initializers, check elsewhere for the token node
77 tokenNode = node.getModifiers().firstOrNullObject();
78 }
79 }
80 index.addDeclaration(tokenNode, behaviorEntry);
81 return node.acceptVisitor(new SourceIndexBehaviorVisitor(behaviorEntry), index);
82 }
83
84 @Override
85 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
86 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
87 ConstructorEntry constructorEntry = ProcyonEntryFactory.getConstructorEntry(def);
88 index.addDeclaration(node.getNameToken(), constructorEntry);
89 return node.acceptVisitor(new SourceIndexBehaviorVisitor(constructorEntry), index);
90 }
91
92 @Override
93 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
94 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
95 FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def);
96 assert (node.getVariables().size() == 1);
97 VariableInitializer variable = node.getVariables().firstOrNullObject();
98 index.addDeclaration(variable.getNameToken(), fieldEntry);
99
100 return recurse(node, index);
101 }
102
103 @Override
104 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
105 // treat enum declarations as field declarations
106 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
107 FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def);
108 index.addDeclaration(node.getNameToken(), fieldEntry);
109
110 return recurse(node, index);
111 }
112}
diff --git a/src/cuchaz/enigma/analysis/SourceIndexVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexVisitor.java
new file mode 100644
index 00000000..08698267
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/SourceIndexVisitor.java
@@ -0,0 +1,452 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import com.strobel.assembler.metadata.TypeDefinition;
14import com.strobel.decompiler.languages.java.ast.Annotation;
15import com.strobel.decompiler.languages.java.ast.AnonymousObjectCreationExpression;
16import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression;
17import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression;
18import com.strobel.decompiler.languages.java.ast.ArraySpecifier;
19import com.strobel.decompiler.languages.java.ast.AssertStatement;
20import com.strobel.decompiler.languages.java.ast.AssignmentExpression;
21import com.strobel.decompiler.languages.java.ast.AstNode;
22import com.strobel.decompiler.languages.java.ast.BinaryOperatorExpression;
23import com.strobel.decompiler.languages.java.ast.BlockStatement;
24import com.strobel.decompiler.languages.java.ast.BreakStatement;
25import com.strobel.decompiler.languages.java.ast.CaseLabel;
26import com.strobel.decompiler.languages.java.ast.CastExpression;
27import com.strobel.decompiler.languages.java.ast.CatchClause;
28import com.strobel.decompiler.languages.java.ast.ClassOfExpression;
29import com.strobel.decompiler.languages.java.ast.Comment;
30import com.strobel.decompiler.languages.java.ast.CompilationUnit;
31import com.strobel.decompiler.languages.java.ast.ComposedType;
32import com.strobel.decompiler.languages.java.ast.ConditionalExpression;
33import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration;
34import com.strobel.decompiler.languages.java.ast.ContinueStatement;
35import com.strobel.decompiler.languages.java.ast.DoWhileStatement;
36import com.strobel.decompiler.languages.java.ast.EmptyStatement;
37import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration;
38import com.strobel.decompiler.languages.java.ast.ExpressionStatement;
39import com.strobel.decompiler.languages.java.ast.FieldDeclaration;
40import com.strobel.decompiler.languages.java.ast.ForEachStatement;
41import com.strobel.decompiler.languages.java.ast.ForStatement;
42import com.strobel.decompiler.languages.java.ast.GotoStatement;
43import com.strobel.decompiler.languages.java.ast.IAstVisitor;
44import com.strobel.decompiler.languages.java.ast.Identifier;
45import com.strobel.decompiler.languages.java.ast.IdentifierExpression;
46import com.strobel.decompiler.languages.java.ast.IfElseStatement;
47import com.strobel.decompiler.languages.java.ast.ImportDeclaration;
48import com.strobel.decompiler.languages.java.ast.IndexerExpression;
49import com.strobel.decompiler.languages.java.ast.InstanceInitializer;
50import com.strobel.decompiler.languages.java.ast.InstanceOfExpression;
51import com.strobel.decompiler.languages.java.ast.InvocationExpression;
52import com.strobel.decompiler.languages.java.ast.JavaTokenNode;
53import com.strobel.decompiler.languages.java.ast.Keys;
54import com.strobel.decompiler.languages.java.ast.LabelStatement;
55import com.strobel.decompiler.languages.java.ast.LabeledStatement;
56import com.strobel.decompiler.languages.java.ast.LambdaExpression;
57import com.strobel.decompiler.languages.java.ast.LocalTypeDeclarationStatement;
58import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
59import com.strobel.decompiler.languages.java.ast.MethodDeclaration;
60import com.strobel.decompiler.languages.java.ast.MethodGroupExpression;
61import com.strobel.decompiler.languages.java.ast.NewLineNode;
62import com.strobel.decompiler.languages.java.ast.NullReferenceExpression;
63import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression;
64import com.strobel.decompiler.languages.java.ast.PackageDeclaration;
65import com.strobel.decompiler.languages.java.ast.ParameterDeclaration;
66import com.strobel.decompiler.languages.java.ast.ParenthesizedExpression;
67import com.strobel.decompiler.languages.java.ast.PrimitiveExpression;
68import com.strobel.decompiler.languages.java.ast.ReturnStatement;
69import com.strobel.decompiler.languages.java.ast.SimpleType;
70import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression;
71import com.strobel.decompiler.languages.java.ast.SwitchSection;
72import com.strobel.decompiler.languages.java.ast.SwitchStatement;
73import com.strobel.decompiler.languages.java.ast.SynchronizedStatement;
74import com.strobel.decompiler.languages.java.ast.TextNode;
75import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression;
76import com.strobel.decompiler.languages.java.ast.ThrowStatement;
77import com.strobel.decompiler.languages.java.ast.TryCatchStatement;
78import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
79import com.strobel.decompiler.languages.java.ast.TypeParameterDeclaration;
80import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression;
81import com.strobel.decompiler.languages.java.ast.UnaryOperatorExpression;
82import com.strobel.decompiler.languages.java.ast.VariableDeclarationStatement;
83import com.strobel.decompiler.languages.java.ast.VariableInitializer;
84import com.strobel.decompiler.languages.java.ast.WhileStatement;
85import com.strobel.decompiler.languages.java.ast.WildcardType;
86import com.strobel.decompiler.patterns.Pattern;
87
88import cuchaz.enigma.mapping.ClassEntry;
89
90public class SourceIndexVisitor implements IAstVisitor<SourceIndex,Void> {
91
92 @Override
93 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
94 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
95 ClassEntry classEntry = new ClassEntry(def.getInternalName());
96 index.addDeclaration(node.getNameToken(), classEntry);
97
98 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
99 }
100
101 protected Void recurse(AstNode node, SourceIndex index) {
102 for (final AstNode child : node.getChildren()) {
103 child.acceptVisitor(this, index);
104 }
105 return null;
106 }
107
108 @Override
109 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
110 return recurse(node, index);
111 }
112
113 @Override
114 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
115 return recurse(node, index);
116 }
117
118 @Override
119 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
120 return recurse(node, index);
121 }
122
123 @Override
124 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
125 return recurse(node, index);
126 }
127
128 @Override
129 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
130 return recurse(node, index);
131 }
132
133 @Override
134 public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) {
135 return recurse(node, index);
136 }
137
138 @Override
139 public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) {
140 return recurse(node, index);
141 }
142
143 @Override
144 public Void visitSimpleType(SimpleType node, SourceIndex index) {
145 return recurse(node, index);
146 }
147
148 @Override
149 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
150 return recurse(node, index);
151 }
152
153 @Override
154 public Void visitComment(Comment node, SourceIndex index) {
155 return recurse(node, index);
156 }
157
158 @Override
159 public Void visitPatternPlaceholder(AstNode node, Pattern pattern, SourceIndex index) {
160 return recurse(node, index);
161 }
162
163 @Override
164 public Void visitTypeReference(TypeReferenceExpression node, SourceIndex index) {
165 return recurse(node, index);
166 }
167
168 @Override
169 public Void visitJavaTokenNode(JavaTokenNode node, SourceIndex index) {
170 return recurse(node, index);
171 }
172
173 @Override
174 public Void visitIdentifier(Identifier node, SourceIndex index) {
175 return recurse(node, index);
176 }
177
178 @Override
179 public Void visitNullReferenceExpression(NullReferenceExpression node, SourceIndex index) {
180 return recurse(node, index);
181 }
182
183 @Override
184 public Void visitThisReferenceExpression(ThisReferenceExpression node, SourceIndex index) {
185 return recurse(node, index);
186 }
187
188 @Override
189 public Void visitSuperReferenceExpression(SuperReferenceExpression node, SourceIndex index) {
190 return recurse(node, index);
191 }
192
193 @Override
194 public Void visitClassOfExpression(ClassOfExpression node, SourceIndex index) {
195 return recurse(node, index);
196 }
197
198 @Override
199 public Void visitBlockStatement(BlockStatement node, SourceIndex index) {
200 return recurse(node, index);
201 }
202
203 @Override
204 public Void visitExpressionStatement(ExpressionStatement node, SourceIndex index) {
205 return recurse(node, index);
206 }
207
208 @Override
209 public Void visitBreakStatement(BreakStatement node, SourceIndex index) {
210 return recurse(node, index);
211 }
212
213 @Override
214 public Void visitContinueStatement(ContinueStatement node, SourceIndex index) {
215 return recurse(node, index);
216 }
217
218 @Override
219 public Void visitDoWhileStatement(DoWhileStatement node, SourceIndex index) {
220 return recurse(node, index);
221 }
222
223 @Override
224 public Void visitEmptyStatement(EmptyStatement node, SourceIndex index) {
225 return recurse(node, index);
226 }
227
228 @Override
229 public Void visitIfElseStatement(IfElseStatement node, SourceIndex index) {
230 return recurse(node, index);
231 }
232
233 @Override
234 public Void visitLabelStatement(LabelStatement node, SourceIndex index) {
235 return recurse(node, index);
236 }
237
238 @Override
239 public Void visitLabeledStatement(LabeledStatement node, SourceIndex index) {
240 return recurse(node, index);
241 }
242
243 @Override
244 public Void visitReturnStatement(ReturnStatement node, SourceIndex index) {
245 return recurse(node, index);
246 }
247
248 @Override
249 public Void visitSwitchStatement(SwitchStatement node, SourceIndex index) {
250 return recurse(node, index);
251 }
252
253 @Override
254 public Void visitSwitchSection(SwitchSection node, SourceIndex index) {
255 return recurse(node, index);
256 }
257
258 @Override
259 public Void visitCaseLabel(CaseLabel node, SourceIndex index) {
260 return recurse(node, index);
261 }
262
263 @Override
264 public Void visitThrowStatement(ThrowStatement node, SourceIndex index) {
265 return recurse(node, index);
266 }
267
268 @Override
269 public Void visitCatchClause(CatchClause node, SourceIndex index) {
270 return recurse(node, index);
271 }
272
273 @Override
274 public Void visitAnnotation(Annotation node, SourceIndex index) {
275 return recurse(node, index);
276 }
277
278 @Override
279 public Void visitNewLine(NewLineNode node, SourceIndex index) {
280 return recurse(node, index);
281 }
282
283 @Override
284 public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) {
285 return recurse(node, index);
286 }
287
288 @Override
289 public Void visitVariableInitializer(VariableInitializer node, SourceIndex index) {
290 return recurse(node, index);
291 }
292
293 @Override
294 public Void visitText(TextNode node, SourceIndex index) {
295 return recurse(node, index);
296 }
297
298 @Override
299 public Void visitImportDeclaration(ImportDeclaration node, SourceIndex index) {
300 return recurse(node, index);
301 }
302
303 @Override
304 public Void visitInitializerBlock(InstanceInitializer node, SourceIndex index) {
305 return recurse(node, index);
306 }
307
308 @Override
309 public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, SourceIndex index) {
310 return recurse(node, index);
311 }
312
313 @Override
314 public Void visitCompilationUnit(CompilationUnit node, SourceIndex index) {
315 return recurse(node, index);
316 }
317
318 @Override
319 public Void visitPackageDeclaration(PackageDeclaration node, SourceIndex index) {
320 return recurse(node, index);
321 }
322
323 @Override
324 public Void visitArraySpecifier(ArraySpecifier node, SourceIndex index) {
325 return recurse(node, index);
326 }
327
328 @Override
329 public Void visitComposedType(ComposedType node, SourceIndex index) {
330 return recurse(node, index);
331 }
332
333 @Override
334 public Void visitWhileStatement(WhileStatement node, SourceIndex index) {
335 return recurse(node, index);
336 }
337
338 @Override
339 public Void visitPrimitiveExpression(PrimitiveExpression node, SourceIndex index) {
340 return recurse(node, index);
341 }
342
343 @Override
344 public Void visitCastExpression(CastExpression node, SourceIndex index) {
345 return recurse(node, index);
346 }
347
348 @Override
349 public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, SourceIndex index) {
350 return recurse(node, index);
351 }
352
353 @Override
354 public Void visitInstanceOfExpression(InstanceOfExpression node, SourceIndex index) {
355 return recurse(node, index);
356 }
357
358 @Override
359 public Void visitIndexerExpression(IndexerExpression node, SourceIndex index) {
360 return recurse(node, index);
361 }
362
363 @Override
364 public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, SourceIndex index) {
365 return recurse(node, index);
366 }
367
368 @Override
369 public Void visitConditionalExpression(ConditionalExpression node, SourceIndex index) {
370 return recurse(node, index);
371 }
372
373 @Override
374 public Void visitArrayInitializerExpression(ArrayInitializerExpression node, SourceIndex index) {
375 return recurse(node, index);
376 }
377
378 @Override
379 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
380 return recurse(node, index);
381 }
382
383 @Override
384 public Void visitArrayCreationExpression(ArrayCreationExpression node, SourceIndex index) {
385 return recurse(node, index);
386 }
387
388 @Override
389 public Void visitAssignmentExpression(AssignmentExpression node, SourceIndex index) {
390 return recurse(node, index);
391 }
392
393 @Override
394 public Void visitForStatement(ForStatement node, SourceIndex index) {
395 return recurse(node, index);
396 }
397
398 @Override
399 public Void visitForEachStatement(ForEachStatement node, SourceIndex index) {
400 return recurse(node, index);
401 }
402
403 @Override
404 public Void visitTryCatchStatement(TryCatchStatement node, SourceIndex index) {
405 return recurse(node, index);
406 }
407
408 @Override
409 public Void visitGotoStatement(GotoStatement node, SourceIndex index) {
410 return recurse(node, index);
411 }
412
413 @Override
414 public Void visitParenthesizedExpression(ParenthesizedExpression node, SourceIndex index) {
415 return recurse(node, index);
416 }
417
418 @Override
419 public Void visitSynchronizedStatement(SynchronizedStatement node, SourceIndex index) {
420 return recurse(node, index);
421 }
422
423 @Override
424 public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, SourceIndex index) {
425 return recurse(node, index);
426 }
427
428 @Override
429 public Void visitWildcardType(WildcardType node, SourceIndex index) {
430 return recurse(node, index);
431 }
432
433 @Override
434 public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) {
435 return recurse(node, index);
436 }
437
438 @Override
439 public Void visitAssertStatement(AssertStatement node, SourceIndex index) {
440 return recurse(node, index);
441 }
442
443 @Override
444 public Void visitLambdaExpression(LambdaExpression node, SourceIndex index) {
445 return recurse(node, index);
446 }
447
448 @Override
449 public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, SourceIndex index) {
450 return recurse(node, index);
451 }
452}
diff --git a/src/cuchaz/enigma/analysis/Token.java b/src/cuchaz/enigma/analysis/Token.java
new file mode 100644
index 00000000..76d63276
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/Token.java
@@ -0,0 +1,56 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..a491cfce
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/TranslationIndex.java
@@ -0,0 +1,298 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.io.IOException;
14import java.io.InputStream;
15import java.io.ObjectInputStream;
16import java.io.ObjectOutputStream;
17import java.io.OutputStream;
18import java.io.Serializable;
19import java.util.Collection;
20import java.util.HashMap;
21import java.util.List;
22import java.util.Map;
23import java.util.Set;
24import java.util.zip.GZIPInputStream;
25import java.util.zip.GZIPOutputStream;
26
27import javassist.CtBehavior;
28import javassist.CtClass;
29import javassist.CtField;
30import javassist.bytecode.Descriptor;
31
32import com.google.common.collect.HashMultimap;
33import com.google.common.collect.Lists;
34import com.google.common.collect.Maps;
35import com.google.common.collect.Multimap;
36
37import cuchaz.enigma.mapping.ArgumentEntry;
38import cuchaz.enigma.mapping.BehaviorEntry;
39import cuchaz.enigma.mapping.ClassEntry;
40import cuchaz.enigma.mapping.Entry;
41import cuchaz.enigma.mapping.EntryFactory;
42import cuchaz.enigma.mapping.FieldEntry;
43import cuchaz.enigma.mapping.Translator;
44
45public class TranslationIndex implements Serializable {
46
47 private static final long serialVersionUID = 738687982126844179L;
48
49 private Map<ClassEntry,ClassEntry> m_superclasses;
50 private Multimap<ClassEntry,FieldEntry> m_fieldEntries;
51 private Multimap<ClassEntry,BehaviorEntry> m_behaviorEntries;
52 private Multimap<ClassEntry,ClassEntry> m_interfaces;
53
54 public TranslationIndex() {
55 m_superclasses = Maps.newHashMap();
56 m_fieldEntries = HashMultimap.create();
57 m_behaviorEntries = HashMultimap.create();
58 m_interfaces = HashMultimap.create();
59 }
60
61 public TranslationIndex(TranslationIndex other, Translator translator) {
62
63 // translate the superclasses
64 m_superclasses = Maps.newHashMap();
65 for (Map.Entry<ClassEntry,ClassEntry> mapEntry : other.m_superclasses.entrySet()) {
66 m_superclasses.put(
67 translator.translateEntry(mapEntry.getKey()),
68 translator.translateEntry(mapEntry.getValue())
69 );
70 }
71
72 // translate the interfaces
73 m_interfaces = HashMultimap.create();
74 for (Map.Entry<ClassEntry,ClassEntry> mapEntry : other.m_interfaces.entries()) {
75 m_interfaces.put(
76 translator.translateEntry(mapEntry.getKey()),
77 translator.translateEntry(mapEntry.getValue())
78 );
79 }
80
81 // translate the fields
82 m_fieldEntries = HashMultimap.create();
83 for (Map.Entry<ClassEntry,FieldEntry> mapEntry : other.m_fieldEntries.entries()) {
84 m_fieldEntries.put(
85 translator.translateEntry(mapEntry.getKey()),
86 translator.translateEntry(mapEntry.getValue())
87 );
88 }
89
90 m_behaviorEntries = HashMultimap.create();
91 for (Map.Entry<ClassEntry,BehaviorEntry> mapEntry : other.m_behaviorEntries.entries()) {
92 m_behaviorEntries.put(
93 translator.translateEntry(mapEntry.getKey()),
94 translator.translateEntry(mapEntry.getValue())
95 );
96 }
97 }
98
99 public void indexClass(CtClass c) {
100 indexClass(c, true);
101 }
102
103 public void indexClass(CtClass c, boolean indexMembers) {
104
105 ClassEntry classEntry = EntryFactory.getClassEntry(c);
106 if (isJre(classEntry)) {
107 return;
108 }
109
110 // add the superclass
111 ClassEntry superclassEntry = EntryFactory.getSuperclassEntry(c);
112 if (superclassEntry != null) {
113 m_superclasses.put(classEntry, superclassEntry);
114 }
115
116 // add the interfaces
117 for (String interfaceClassName : c.getClassFile().getInterfaces()) {
118 ClassEntry interfaceClassEntry = new ClassEntry(Descriptor.toJvmName(interfaceClassName));
119 if (!isJre(interfaceClassEntry)) {
120 m_interfaces.put(classEntry, interfaceClassEntry);
121 }
122 }
123
124 if (indexMembers) {
125 // add fields
126 for (CtField field : c.getDeclaredFields()) {
127 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
128 m_fieldEntries.put(fieldEntry.getClassEntry(), fieldEntry);
129 }
130
131 // add behaviors
132 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
133 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
134 m_behaviorEntries.put(behaviorEntry.getClassEntry(), behaviorEntry);
135 }
136 }
137 }
138
139 public void renameClasses(Map<String,String> renames) {
140 EntryRenamer.renameClassesInMap(renames, m_superclasses);
141 EntryRenamer.renameClassesInMultimap(renames, m_fieldEntries);
142 EntryRenamer.renameClassesInMultimap(renames, m_behaviorEntries);
143 }
144
145 public ClassEntry getSuperclass(ClassEntry classEntry) {
146 return m_superclasses.get(classEntry);
147 }
148
149 public List<ClassEntry> getAncestry(ClassEntry classEntry) {
150 List<ClassEntry> ancestors = Lists.newArrayList();
151 while (classEntry != null) {
152 classEntry = getSuperclass(classEntry);
153 if (classEntry != null) {
154 ancestors.add(classEntry);
155 }
156 }
157 return ancestors;
158 }
159
160 public List<ClassEntry> getSubclass(ClassEntry classEntry) {
161
162 // linear search is fast enough for now
163 List<ClassEntry> subclasses = Lists.newArrayList();
164 for (Map.Entry<ClassEntry,ClassEntry> entry : m_superclasses.entrySet()) {
165 ClassEntry subclass = entry.getKey();
166 ClassEntry superclass = entry.getValue();
167 if (classEntry.equals(superclass)) {
168 subclasses.add(subclass);
169 }
170 }
171 return subclasses;
172 }
173
174 public void getSubclassesRecursively(Set<ClassEntry> out, ClassEntry classEntry) {
175 for (ClassEntry subclassEntry : getSubclass(classEntry)) {
176 out.add(subclassEntry);
177 getSubclassesRecursively(out, subclassEntry);
178 }
179 }
180
181 public void getSubclassNamesRecursively(Set<String> out, ClassEntry classEntry) {
182 for (ClassEntry subclassEntry : getSubclass(classEntry)) {
183 out.add(subclassEntry.getName());
184 getSubclassNamesRecursively(out, subclassEntry);
185 }
186 }
187
188 public Collection<Map.Entry<ClassEntry,ClassEntry>> getClassInterfaces() {
189 return m_interfaces.entries();
190 }
191
192 public Collection<ClassEntry> getInterfaces(ClassEntry classEntry) {
193 return m_interfaces.get(classEntry);
194 }
195
196 public boolean isInterface(ClassEntry classEntry) {
197 return m_interfaces.containsValue(classEntry);
198 }
199
200 public boolean entryExists(Entry entry) {
201 if (entry instanceof FieldEntry) {
202 return fieldExists((FieldEntry)entry);
203 } else if (entry instanceof BehaviorEntry) {
204 return behaviorExists((BehaviorEntry)entry);
205 } else if (entry instanceof ArgumentEntry) {
206 return behaviorExists(((ArgumentEntry)entry).getBehaviorEntry());
207 }
208 throw new IllegalArgumentException("Cannot check existence for " + entry.getClass());
209 }
210
211 public boolean fieldExists(FieldEntry fieldEntry) {
212 return m_fieldEntries.containsEntry(fieldEntry.getClassEntry(), fieldEntry);
213 }
214
215 public boolean behaviorExists(BehaviorEntry behaviorEntry) {
216 return m_behaviorEntries.containsEntry(behaviorEntry.getClassEntry(), behaviorEntry);
217 }
218
219 public ClassEntry resolveEntryClass(Entry entry) {
220
221 if (entry instanceof ClassEntry) {
222 return (ClassEntry)entry;
223 }
224
225 ClassEntry superclassEntry = resolveSuperclass(entry);
226 if (superclassEntry != null) {
227 return superclassEntry;
228 }
229
230 ClassEntry interfaceEntry = resolveInterface(entry);
231 if (interfaceEntry != null) {
232 return interfaceEntry;
233 }
234
235 return null;
236 }
237
238 public ClassEntry resolveSuperclass(Entry entry) {
239
240 // this entry could refer to a method on a class where the method is not actually implemented
241 // travel up the inheritance tree to find the closest implementation
242 while (!entryExists(entry)) {
243
244 // is there a parent class?
245 ClassEntry superclassEntry = getSuperclass(entry.getClassEntry());
246 if (superclassEntry == null) {
247 // this is probably a method from a class in a library
248 // we can't trace the implementation up any higher unless we index the library
249 return null;
250 }
251
252 // move up to the parent class
253 entry = entry.cloneToNewClass(superclassEntry);
254 }
255 return entry.getClassEntry();
256 }
257
258 public ClassEntry resolveInterface(Entry entry) {
259
260 // the interfaces for any class is a forest
261 // so let's look at all the trees
262 for (ClassEntry interfaceEntry : m_interfaces.get(entry.getClassEntry())) {
263 ClassEntry resolvedClassEntry = resolveSuperclass(entry.cloneToNewClass(interfaceEntry));
264 if (resolvedClassEntry != null) {
265 return resolvedClassEntry;
266 }
267 }
268 return null;
269 }
270
271 private boolean isJre(ClassEntry classEntry) {
272 String packageName = classEntry.getPackageName();
273 return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax"));
274 }
275
276 public void write(OutputStream out)
277 throws IOException {
278 GZIPOutputStream gzipout = new GZIPOutputStream(out);
279 ObjectOutputStream oout = new ObjectOutputStream(gzipout);
280 oout.writeObject(m_superclasses);
281 oout.writeObject(m_fieldEntries);
282 oout.writeObject(m_behaviorEntries);
283 gzipout.finish();
284 }
285
286 @SuppressWarnings("unchecked")
287 public void read(InputStream in)
288 throws IOException {
289 try {
290 ObjectInputStream oin = new ObjectInputStream(new GZIPInputStream(in));
291 m_superclasses = (HashMap<ClassEntry,ClassEntry>)oin.readObject();
292 m_fieldEntries = (HashMultimap<ClassEntry,FieldEntry>)oin.readObject();
293 m_behaviorEntries = (HashMultimap<ClassEntry,BehaviorEntry>)oin.readObject();
294 } catch (ClassNotFoundException ex) {
295 throw new Error(ex);
296 }
297 }
298}
diff --git a/src/cuchaz/enigma/analysis/TreeDumpVisitor.java b/src/cuchaz/enigma/analysis/TreeDumpVisitor.java
new file mode 100644
index 00000000..0a90bacc
--- /dev/null
+++ b/src/cuchaz/enigma/analysis/TreeDumpVisitor.java
@@ -0,0 +1,512 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.analysis;
12
13import java.io.File;
14import java.io.FileWriter;
15import java.io.IOException;
16import java.io.Writer;
17
18import com.strobel.componentmodel.Key;
19import com.strobel.decompiler.languages.java.ast.Annotation;
20import com.strobel.decompiler.languages.java.ast.AnonymousObjectCreationExpression;
21import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression;
22import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression;
23import com.strobel.decompiler.languages.java.ast.ArraySpecifier;
24import com.strobel.decompiler.languages.java.ast.AssertStatement;
25import com.strobel.decompiler.languages.java.ast.AssignmentExpression;
26import com.strobel.decompiler.languages.java.ast.AstNode;
27import com.strobel.decompiler.languages.java.ast.BinaryOperatorExpression;
28import com.strobel.decompiler.languages.java.ast.BlockStatement;
29import com.strobel.decompiler.languages.java.ast.BreakStatement;
30import com.strobel.decompiler.languages.java.ast.CaseLabel;
31import com.strobel.decompiler.languages.java.ast.CastExpression;
32import com.strobel.decompiler.languages.java.ast.CatchClause;
33import com.strobel.decompiler.languages.java.ast.ClassOfExpression;
34import com.strobel.decompiler.languages.java.ast.Comment;
35import com.strobel.decompiler.languages.java.ast.CompilationUnit;
36import com.strobel.decompiler.languages.java.ast.ComposedType;
37import com.strobel.decompiler.languages.java.ast.ConditionalExpression;
38import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration;
39import com.strobel.decompiler.languages.java.ast.ContinueStatement;
40import com.strobel.decompiler.languages.java.ast.DoWhileStatement;
41import com.strobel.decompiler.languages.java.ast.EmptyStatement;
42import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration;
43import com.strobel.decompiler.languages.java.ast.ExpressionStatement;
44import com.strobel.decompiler.languages.java.ast.FieldDeclaration;
45import com.strobel.decompiler.languages.java.ast.ForEachStatement;
46import com.strobel.decompiler.languages.java.ast.ForStatement;
47import com.strobel.decompiler.languages.java.ast.GotoStatement;
48import com.strobel.decompiler.languages.java.ast.IAstVisitor;
49import com.strobel.decompiler.languages.java.ast.Identifier;
50import com.strobel.decompiler.languages.java.ast.IdentifierExpression;
51import com.strobel.decompiler.languages.java.ast.IfElseStatement;
52import com.strobel.decompiler.languages.java.ast.ImportDeclaration;
53import com.strobel.decompiler.languages.java.ast.IndexerExpression;
54import com.strobel.decompiler.languages.java.ast.InstanceInitializer;
55import com.strobel.decompiler.languages.java.ast.InstanceOfExpression;
56import com.strobel.decompiler.languages.java.ast.InvocationExpression;
57import com.strobel.decompiler.languages.java.ast.JavaTokenNode;
58import com.strobel.decompiler.languages.java.ast.Keys;
59import com.strobel.decompiler.languages.java.ast.LabelStatement;
60import com.strobel.decompiler.languages.java.ast.LabeledStatement;
61import com.strobel.decompiler.languages.java.ast.LambdaExpression;
62import com.strobel.decompiler.languages.java.ast.LocalTypeDeclarationStatement;
63import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
64import com.strobel.decompiler.languages.java.ast.MethodDeclaration;
65import com.strobel.decompiler.languages.java.ast.MethodGroupExpression;
66import com.strobel.decompiler.languages.java.ast.NewLineNode;
67import com.strobel.decompiler.languages.java.ast.NullReferenceExpression;
68import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression;
69import com.strobel.decompiler.languages.java.ast.PackageDeclaration;
70import com.strobel.decompiler.languages.java.ast.ParameterDeclaration;
71import com.strobel.decompiler.languages.java.ast.ParenthesizedExpression;
72import com.strobel.decompiler.languages.java.ast.PrimitiveExpression;
73import com.strobel.decompiler.languages.java.ast.ReturnStatement;
74import com.strobel.decompiler.languages.java.ast.SimpleType;
75import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression;
76import com.strobel.decompiler.languages.java.ast.SwitchSection;
77import com.strobel.decompiler.languages.java.ast.SwitchStatement;
78import com.strobel.decompiler.languages.java.ast.SynchronizedStatement;
79import com.strobel.decompiler.languages.java.ast.TextNode;
80import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression;
81import com.strobel.decompiler.languages.java.ast.ThrowStatement;
82import com.strobel.decompiler.languages.java.ast.TryCatchStatement;
83import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
84import com.strobel.decompiler.languages.java.ast.TypeParameterDeclaration;
85import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression;
86import com.strobel.decompiler.languages.java.ast.UnaryOperatorExpression;
87import com.strobel.decompiler.languages.java.ast.VariableDeclarationStatement;
88import com.strobel.decompiler.languages.java.ast.VariableInitializer;
89import com.strobel.decompiler.languages.java.ast.WhileStatement;
90import com.strobel.decompiler.languages.java.ast.WildcardType;
91import com.strobel.decompiler.patterns.Pattern;
92
93public class TreeDumpVisitor implements IAstVisitor<Void,Void> {
94
95 private File m_file;
96 private Writer m_out;
97
98 public TreeDumpVisitor(File file) {
99 m_file = file;
100 m_out = null;
101 }
102
103 @Override
104 public Void visitCompilationUnit(CompilationUnit node, Void ignored) {
105 try {
106 m_out = new FileWriter(m_file);
107 recurse(node, ignored);
108 m_out.close();
109 return null;
110 } catch (IOException ex) {
111 throw new Error(ex);
112 }
113 }
114
115 private Void recurse(AstNode node, Void ignored) {
116 // show the tree
117 try {
118 m_out.write(getIndent(node) + node.getClass().getSimpleName() + " " + getText(node) + " " + dumpUserData(node) + " " + node.getRegion() + "\n");
119 } catch (IOException ex) {
120 throw new Error(ex);
121 }
122
123 // recurse
124 for (final AstNode child : node.getChildren()) {
125 child.acceptVisitor(this, ignored);
126 }
127 return null;
128 }
129
130 private String getText(AstNode node) {
131 if (node instanceof Identifier) {
132 return "\"" + ((Identifier)node).getName() + "\"";
133 }
134 return "";
135 }
136
137 private String dumpUserData(AstNode node) {
138 StringBuilder buf = new StringBuilder();
139 for (Key<?> key : Keys.ALL_KEYS) {
140 Object val = node.getUserData(key);
141 if (val != null) {
142 buf.append(String.format(" [%s=%s]", key, val));
143 }
144 }
145 return buf.toString();
146 }
147
148 private String getIndent(AstNode node) {
149 StringBuilder buf = new StringBuilder();
150 int depth = getDepth(node);
151 for (int i = 0; i < depth; i++) {
152 buf.append("\t");
153 }
154 return buf.toString();
155 }
156
157 private int getDepth(AstNode node) {
158 int depth = -1;
159 while (node != null) {
160 depth++;
161 node = node.getParent();
162 }
163 return depth;
164 }
165
166 // OVERRIDES WE DON'T CARE ABOUT
167
168 @Override
169 public Void visitInvocationExpression(InvocationExpression node, Void ignored) {
170 return recurse(node, ignored);
171 }
172
173 @Override
174 public Void visitMemberReferenceExpression(MemberReferenceExpression node, Void ignored) {
175 return recurse(node, ignored);
176 }
177
178 @Override
179 public Void visitSimpleType(SimpleType node, Void ignored) {
180 return recurse(node, ignored);
181 }
182
183 @Override
184 public Void visitMethodDeclaration(MethodDeclaration node, Void ignored) {
185 return recurse(node, ignored);
186 }
187
188 @Override
189 public Void visitConstructorDeclaration(ConstructorDeclaration node, Void ignored) {
190 return recurse(node, ignored);
191 }
192
193 @Override
194 public Void visitParameterDeclaration(ParameterDeclaration node, Void ignored) {
195 return recurse(node, ignored);
196 }
197
198 @Override
199 public Void visitFieldDeclaration(FieldDeclaration node, Void ignored) {
200 return recurse(node, ignored);
201 }
202
203 @Override
204 public Void visitTypeDeclaration(TypeDeclaration node, Void ignored) {
205 return recurse(node, ignored);
206 }
207
208 @Override
209 public Void visitComment(Comment node, Void ignored) {
210 return recurse(node, ignored);
211 }
212
213 @Override
214 public Void visitPatternPlaceholder(AstNode node, Pattern pattern, Void ignored) {
215 return recurse(node, ignored);
216 }
217
218 @Override
219 public Void visitTypeReference(TypeReferenceExpression node, Void ignored) {
220 return recurse(node, ignored);
221 }
222
223 @Override
224 public Void visitJavaTokenNode(JavaTokenNode node, Void ignored) {
225 return recurse(node, ignored);
226 }
227
228 @Override
229 public Void visitIdentifier(Identifier node, Void ignored) {
230 return recurse(node, ignored);
231 }
232
233 @Override
234 public Void visitNullReferenceExpression(NullReferenceExpression node, Void ignored) {
235 return recurse(node, ignored);
236 }
237
238 @Override
239 public Void visitThisReferenceExpression(ThisReferenceExpression node, Void ignored) {
240 return recurse(node, ignored);
241 }
242
243 @Override
244 public Void visitSuperReferenceExpression(SuperReferenceExpression node, Void ignored) {
245 return recurse(node, ignored);
246 }
247
248 @Override
249 public Void visitClassOfExpression(ClassOfExpression node, Void ignored) {
250 return recurse(node, ignored);
251 }
252
253 @Override
254 public Void visitBlockStatement(BlockStatement node, Void ignored) {
255 return recurse(node, ignored);
256 }
257
258 @Override
259 public Void visitExpressionStatement(ExpressionStatement node, Void ignored) {
260 return recurse(node, ignored);
261 }
262
263 @Override
264 public Void visitBreakStatement(BreakStatement node, Void ignored) {
265 return recurse(node, ignored);
266 }
267
268 @Override
269 public Void visitContinueStatement(ContinueStatement node, Void ignored) {
270 return recurse(node, ignored);
271 }
272
273 @Override
274 public Void visitDoWhileStatement(DoWhileStatement node, Void ignored) {
275 return recurse(node, ignored);
276 }
277
278 @Override
279 public Void visitEmptyStatement(EmptyStatement node, Void ignored) {
280 return recurse(node, ignored);
281 }
282
283 @Override
284 public Void visitIfElseStatement(IfElseStatement node, Void ignored) {
285 return recurse(node, ignored);
286 }
287
288 @Override
289 public Void visitLabelStatement(LabelStatement node, Void ignored) {
290 return recurse(node, ignored);
291 }
292
293 @Override
294 public Void visitLabeledStatement(LabeledStatement node, Void ignored) {
295 return recurse(node, ignored);
296 }
297
298 @Override
299 public Void visitReturnStatement(ReturnStatement node, Void ignored) {
300 return recurse(node, ignored);
301 }
302
303 @Override
304 public Void visitSwitchStatement(SwitchStatement node, Void ignored) {
305 return recurse(node, ignored);
306 }
307
308 @Override
309 public Void visitSwitchSection(SwitchSection node, Void ignored) {
310 return recurse(node, ignored);
311 }
312
313 @Override
314 public Void visitCaseLabel(CaseLabel node, Void ignored) {
315 return recurse(node, ignored);
316 }
317
318 @Override
319 public Void visitThrowStatement(ThrowStatement node, Void ignored) {
320 return recurse(node, ignored);
321 }
322
323 @Override
324 public Void visitCatchClause(CatchClause node, Void ignored) {
325 return recurse(node, ignored);
326 }
327
328 @Override
329 public Void visitAnnotation(Annotation node, Void ignored) {
330 return recurse(node, ignored);
331 }
332
333 @Override
334 public Void visitNewLine(NewLineNode node, Void ignored) {
335 return recurse(node, ignored);
336 }
337
338 @Override
339 public Void visitVariableDeclaration(VariableDeclarationStatement node, Void ignored) {
340 return recurse(node, ignored);
341 }
342
343 @Override
344 public Void visitVariableInitializer(VariableInitializer node, Void ignored) {
345 return recurse(node, ignored);
346 }
347
348 @Override
349 public Void visitText(TextNode node, Void ignored) {
350 return recurse(node, ignored);
351 }
352
353 @Override
354 public Void visitImportDeclaration(ImportDeclaration node, Void ignored) {
355 return recurse(node, ignored);
356 }
357
358 @Override
359 public Void visitInitializerBlock(InstanceInitializer node, Void ignored) {
360 return recurse(node, ignored);
361 }
362
363 @Override
364 public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, Void ignored) {
365 return recurse(node, ignored);
366 }
367
368 @Override
369 public Void visitPackageDeclaration(PackageDeclaration node, Void ignored) {
370 return recurse(node, ignored);
371 }
372
373 @Override
374 public Void visitArraySpecifier(ArraySpecifier node, Void ignored) {
375 return recurse(node, ignored);
376 }
377
378 @Override
379 public Void visitComposedType(ComposedType node, Void ignored) {
380 return recurse(node, ignored);
381 }
382
383 @Override
384 public Void visitWhileStatement(WhileStatement node, Void ignored) {
385 return recurse(node, ignored);
386 }
387
388 @Override
389 public Void visitPrimitiveExpression(PrimitiveExpression node, Void ignored) {
390 return recurse(node, ignored);
391 }
392
393 @Override
394 public Void visitCastExpression(CastExpression node, Void ignored) {
395 return recurse(node, ignored);
396 }
397
398 @Override
399 public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, Void ignored) {
400 return recurse(node, ignored);
401 }
402
403 @Override
404 public Void visitInstanceOfExpression(InstanceOfExpression node, Void ignored) {
405 return recurse(node, ignored);
406 }
407
408 @Override
409 public Void visitIndexerExpression(IndexerExpression node, Void ignored) {
410 return recurse(node, ignored);
411 }
412
413 @Override
414 public Void visitIdentifierExpression(IdentifierExpression node, Void ignored) {
415 return recurse(node, ignored);
416 }
417
418 @Override
419 public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, Void ignored) {
420 return recurse(node, ignored);
421 }
422
423 @Override
424 public Void visitConditionalExpression(ConditionalExpression node, Void ignored) {
425 return recurse(node, ignored);
426 }
427
428 @Override
429 public Void visitArrayInitializerExpression(ArrayInitializerExpression node, Void ignored) {
430 return recurse(node, ignored);
431 }
432
433 @Override
434 public Void visitObjectCreationExpression(ObjectCreationExpression node, Void ignored) {
435 return recurse(node, ignored);
436 }
437
438 @Override
439 public Void visitArrayCreationExpression(ArrayCreationExpression node, Void ignored) {
440 return recurse(node, ignored);
441 }
442
443 @Override
444 public Void visitAssignmentExpression(AssignmentExpression node, Void ignored) {
445 return recurse(node, ignored);
446 }
447
448 @Override
449 public Void visitForStatement(ForStatement node, Void ignored) {
450 return recurse(node, ignored);
451 }
452
453 @Override
454 public Void visitForEachStatement(ForEachStatement node, Void ignored) {
455 return recurse(node, ignored);
456 }
457
458 @Override
459 public Void visitTryCatchStatement(TryCatchStatement node, Void ignored) {
460 return recurse(node, ignored);
461 }
462
463 @Override
464 public Void visitGotoStatement(GotoStatement node, Void ignored) {
465 return recurse(node, ignored);
466 }
467
468 @Override
469 public Void visitParenthesizedExpression(ParenthesizedExpression node, Void ignored) {
470 return recurse(node, ignored);
471 }
472
473 @Override
474 public Void visitSynchronizedStatement(SynchronizedStatement node, Void ignored) {
475 return recurse(node, ignored);
476 }
477
478 @Override
479 public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, Void ignored) {
480 return recurse(node, ignored);
481 }
482
483 @Override
484 public Void visitWildcardType(WildcardType node, Void ignored) {
485 return recurse(node, ignored);
486 }
487
488 @Override
489 public Void visitMethodGroupExpression(MethodGroupExpression node, Void ignored) {
490 return recurse(node, ignored);
491 }
492
493 @Override
494 public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void ignored) {
495 return recurse(node, ignored);
496 }
497
498 @Override
499 public Void visitAssertStatement(AssertStatement node, Void ignored) {
500 return recurse(node, ignored);
501 }
502
503 @Override
504 public Void visitLambdaExpression(LambdaExpression node, Void ignored) {
505 return recurse(node, ignored);
506 }
507
508 @Override
509 public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, Void ignored) {
510 return recurse(node, ignored);
511 }
512}
diff --git a/src/cuchaz/enigma/bytecode/CheckCastIterator.java b/src/cuchaz/enigma/bytecode/CheckCastIterator.java
new file mode 100644
index 00000000..517b9d62
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/CheckCastIterator.java
@@ -0,0 +1,127 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.Iterator;
14
15import javassist.bytecode.BadBytecode;
16import javassist.bytecode.CodeAttribute;
17import javassist.bytecode.CodeIterator;
18import javassist.bytecode.ConstPool;
19import javassist.bytecode.Descriptor;
20import javassist.bytecode.Opcode;
21import cuchaz.enigma.bytecode.CheckCastIterator.CheckCast;
22import cuchaz.enigma.mapping.ClassEntry;
23import cuchaz.enigma.mapping.MethodEntry;
24import cuchaz.enigma.mapping.Signature;
25
26public class CheckCastIterator implements Iterator<CheckCast> {
27
28 public static class CheckCast {
29
30 public String className;
31 public MethodEntry prevMethodEntry;
32
33 public CheckCast(String className, MethodEntry prevMethodEntry) {
34 this.className = className;
35 this.prevMethodEntry = prevMethodEntry;
36 }
37 }
38
39 private ConstPool m_constants;
40 private CodeAttribute m_attribute;
41 private CodeIterator m_iter;
42 private CheckCast m_next;
43
44 public CheckCastIterator(CodeAttribute codeAttribute) throws BadBytecode {
45 m_constants = codeAttribute.getConstPool();
46 m_attribute = codeAttribute;
47 m_iter = m_attribute.iterator();
48
49 m_next = getNext();
50 }
51
52 @Override
53 public boolean hasNext() {
54 return m_next != null;
55 }
56
57 @Override
58 public CheckCast next() {
59 CheckCast out = m_next;
60 try {
61 m_next = getNext();
62 } catch (BadBytecode ex) {
63 throw new Error(ex);
64 }
65 return out;
66 }
67
68 @Override
69 public void remove() {
70 throw new UnsupportedOperationException();
71 }
72
73 private CheckCast getNext() throws BadBytecode {
74 int prevPos = 0;
75 while (m_iter.hasNext()) {
76 int pos = m_iter.next();
77 int opcode = m_iter.byteAt(pos);
78 switch (opcode) {
79 case Opcode.CHECKCAST:
80
81 // get the type of this op code (next two bytes are a classinfo index)
82 MethodEntry prevMethodEntry = getMethodEntry(prevPos);
83 if (prevMethodEntry != null) {
84 return new CheckCast(m_constants.getClassInfo(m_iter.s16bitAt(pos + 1)), prevMethodEntry);
85 }
86 break;
87 }
88 prevPos = pos;
89 }
90 return null;
91 }
92
93 private MethodEntry getMethodEntry(int pos) {
94 switch (m_iter.byteAt(pos)) {
95 case Opcode.INVOKEVIRTUAL:
96 case Opcode.INVOKESTATIC:
97 case Opcode.INVOKEDYNAMIC:
98 case Opcode.INVOKESPECIAL: {
99 int index = m_iter.s16bitAt(pos + 1);
100 return new MethodEntry(
101 new ClassEntry(Descriptor.toJvmName(m_constants.getMethodrefClassName(index))),
102 m_constants.getMethodrefName(index),
103 new Signature(m_constants.getMethodrefType(index))
104 );
105 }
106
107 case Opcode.INVOKEINTERFACE: {
108 int index = m_iter.s16bitAt(pos + 1);
109 return new MethodEntry(
110 new ClassEntry(Descriptor.toJvmName(m_constants.getInterfaceMethodrefClassName(index))),
111 m_constants.getInterfaceMethodrefName(index),
112 new Signature(m_constants.getInterfaceMethodrefType(index))
113 );
114 }
115 }
116 return null;
117 }
118
119 public Iterable<CheckCast> casts() {
120 return new Iterable<CheckCast>() {
121 @Override
122 public Iterator<CheckCast> iterator() {
123 return CheckCastIterator.this;
124 }
125 };
126 }
127}
diff --git a/src/cuchaz/enigma/bytecode/ClassProtectifier.java b/src/cuchaz/enigma/bytecode/ClassProtectifier.java
new file mode 100644
index 00000000..f1ee4e77
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/ClassProtectifier.java
@@ -0,0 +1,51 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.CtField;
16import javassist.bytecode.AccessFlag;
17import javassist.bytecode.InnerClassesAttribute;
18
19
20public class ClassProtectifier {
21
22 public static CtClass protectify(CtClass c) {
23
24 // protectify all the fields
25 for (CtField field : c.getDeclaredFields()) {
26 field.setModifiers(protectify(field.getModifiers()));
27 }
28
29 // protectify all the methods and constructors
30 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
31 behavior.setModifiers(protectify(behavior.getModifiers()));
32 }
33
34 // protectify all the inner classes
35 InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
36 if (attr != null) {
37 for (int i=0; i<attr.tableLength(); i++) {
38 attr.setAccessFlags(i, protectify(attr.accessFlags(i)));
39 }
40 }
41
42 return c;
43 }
44
45 private static int protectify(int flags) {
46 if (AccessFlag.isPrivate(flags)) {
47 flags = AccessFlag.setProtected(flags);
48 }
49 return flags;
50 }
51}
diff --git a/src/cuchaz/enigma/bytecode/ClassPublifier.java b/src/cuchaz/enigma/bytecode/ClassPublifier.java
new file mode 100644
index 00000000..dbefd426
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/ClassPublifier.java
@@ -0,0 +1,51 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.CtField;
16import javassist.bytecode.AccessFlag;
17import javassist.bytecode.InnerClassesAttribute;
18
19
20public class ClassPublifier {
21
22 public static CtClass publify(CtClass c) {
23
24 // publify all the fields
25 for (CtField field : c.getDeclaredFields()) {
26 field.setModifiers(publify(field.getModifiers()));
27 }
28
29 // publify all the methods and constructors
30 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
31 behavior.setModifiers(publify(behavior.getModifiers()));
32 }
33
34 // publify all the inner classes
35 InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
36 if (attr != null) {
37 for (int i=0; i<attr.tableLength(); i++) {
38 attr.setAccessFlags(i, publify(attr.accessFlags(i)));
39 }
40 }
41
42 return c;
43 }
44
45 private static int publify(int flags) {
46 if (AccessFlag.isPrivate(flags) || AccessFlag.isProtected(flags)) {
47 flags = AccessFlag.setPublic(flags);
48 }
49 return flags;
50 }
51}
diff --git a/src/cuchaz/enigma/bytecode/ClassRenamer.java b/src/cuchaz/enigma/bytecode/ClassRenamer.java
new file mode 100644
index 00000000..4d95f30e
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/ClassRenamer.java
@@ -0,0 +1,544 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.lang.reflect.InvocationTargetException;
14import java.lang.reflect.Method;
15import java.util.Arrays;
16import java.util.HashMap;
17import java.util.List;
18import java.util.Map;
19
20import javassist.CtClass;
21import javassist.bytecode.AttributeInfo;
22import javassist.bytecode.BadBytecode;
23import javassist.bytecode.ByteArray;
24import javassist.bytecode.ClassFile;
25import javassist.bytecode.CodeAttribute;
26import javassist.bytecode.ConstPool;
27import javassist.bytecode.Descriptor;
28import javassist.bytecode.FieldInfo;
29import javassist.bytecode.InnerClassesAttribute;
30import javassist.bytecode.LocalVariableTypeAttribute;
31import javassist.bytecode.MethodInfo;
32import javassist.bytecode.SignatureAttribute;
33import javassist.bytecode.SignatureAttribute.ArrayType;
34import javassist.bytecode.SignatureAttribute.BaseType;
35import javassist.bytecode.SignatureAttribute.ClassSignature;
36import javassist.bytecode.SignatureAttribute.ClassType;
37import javassist.bytecode.SignatureAttribute.MethodSignature;
38import javassist.bytecode.SignatureAttribute.NestedClassType;
39import javassist.bytecode.SignatureAttribute.ObjectType;
40import javassist.bytecode.SignatureAttribute.Type;
41import javassist.bytecode.SignatureAttribute.TypeArgument;
42import javassist.bytecode.SignatureAttribute.TypeParameter;
43import javassist.bytecode.SignatureAttribute.TypeVariable;
44import cuchaz.enigma.mapping.ClassEntry;
45import cuchaz.enigma.mapping.ClassNameReplacer;
46import cuchaz.enigma.mapping.Translator;
47
48public class ClassRenamer {
49
50 private static enum SignatureType {
51 Class {
52
53 @Override
54 public String rename(String signature, ReplacerClassMap map) {
55 return renameClassSignature(signature, map);
56 }
57 },
58 Field {
59
60 @Override
61 public String rename(String signature, ReplacerClassMap map) {
62 return renameFieldSignature(signature, map);
63 }
64 },
65 Method {
66
67 @Override
68 public String rename(String signature, ReplacerClassMap map) {
69 return renameMethodSignature(signature, map);
70 }
71 };
72
73 public abstract String rename(String signature, ReplacerClassMap map);
74 }
75
76 private static class ReplacerClassMap extends HashMap<String,String> {
77
78 private static final long serialVersionUID = 317915213205066168L;
79
80 private ClassNameReplacer m_replacer;
81
82 public ReplacerClassMap(ClassNameReplacer replacer) {
83 m_replacer = replacer;
84 }
85
86 @Override
87 public String get(Object obj) {
88 if (obj instanceof String) {
89 return get((String)obj);
90 }
91 return null;
92 }
93
94 public String get(String className) {
95 return m_replacer.replace(className);
96 }
97 }
98
99 public static void renameClasses(CtClass c, final Translator translator) {
100 renameClasses(c, new ClassNameReplacer() {
101 @Override
102 public String replace(String className) {
103 ClassEntry entry = translator.translateEntry(new ClassEntry(className));
104 if (entry != null) {
105 return entry.getName();
106 }
107 return null;
108 }
109 });
110 }
111
112 public static void moveAllClassesOutOfDefaultPackage(CtClass c, final String newPackageName) {
113 renameClasses(c, new ClassNameReplacer() {
114 @Override
115 public String replace(String className) {
116 ClassEntry entry = new ClassEntry(className);
117 if (entry.isInDefaultPackage()) {
118 return newPackageName + "/" + entry.getName();
119 }
120 return null;
121 }
122 });
123 }
124
125 public static void moveAllClassesIntoDefaultPackage(CtClass c, final String oldPackageName) {
126 renameClasses(c, new ClassNameReplacer() {
127 @Override
128 public String replace(String className) {
129 ClassEntry entry = new ClassEntry(className);
130 if (entry.getPackageName().equals(oldPackageName)) {
131 return entry.getSimpleName();
132 }
133 return null;
134 }
135 });
136 }
137
138 @SuppressWarnings("unchecked")
139 public static void renameClasses(CtClass c, ClassNameReplacer replacer) {
140
141 // sadly, we can't use CtClass.renameClass() because SignatureAttribute.renameClass() is extremely buggy =(
142
143 ReplacerClassMap map = new ReplacerClassMap(replacer);
144 ClassFile classFile = c.getClassFile();
145
146 // rename the constant pool (covers ClassInfo, MethodTypeInfo, and NameAndTypeInfo)
147 ConstPool constPool = c.getClassFile().getConstPool();
148 constPool.renameClass(map);
149
150 // rename class attributes
151 renameAttributes(classFile.getAttributes(), map, SignatureType.Class);
152
153 // rename methods
154 for (MethodInfo methodInfo : (List<MethodInfo>)classFile.getMethods()) {
155 methodInfo.setDescriptor(Descriptor.rename(methodInfo.getDescriptor(), map));
156 renameAttributes(methodInfo.getAttributes(), map, SignatureType.Method);
157 }
158
159 // rename fields
160 for (FieldInfo fieldInfo : (List<FieldInfo>)classFile.getFields()) {
161 fieldInfo.setDescriptor(Descriptor.rename(fieldInfo.getDescriptor(), map));
162 renameAttributes(fieldInfo.getAttributes(), map, SignatureType.Field);
163 }
164
165 // rename the class name itself last
166 // NOTE: don't use the map here, because setName() calls the buggy SignatureAttribute.renameClass()
167 // we only want to replace exactly this class name
168 String newName = renameClassName(c.getName(), map);
169 if (newName != null) {
170 c.setName(newName);
171 }
172
173 // replace simple names in the InnerClasses attribute too
174 InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
175 if (attr != null) {
176 for (int i = 0; i < attr.tableLength(); i++) {
177
178 // get the inner class full name (which has already been translated)
179 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(attr.innerClass(i)));
180
181 if (attr.innerNameIndex(i) != 0) {
182 // update the inner name
183 attr.setInnerNameIndex(i, constPool.addUtf8Info(classEntry.getInnermostClassName()));
184 }
185
186 /* DEBUG
187 System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i)));
188 */
189 }
190 }
191 }
192
193 @SuppressWarnings("unchecked")
194 private static void renameAttributes(List<AttributeInfo> attributes, ReplacerClassMap map, SignatureType type) {
195 try {
196
197 // make the rename class method accessible
198 Method renameClassMethod = AttributeInfo.class.getDeclaredMethod("renameClass", Map.class);
199 renameClassMethod.setAccessible(true);
200
201 for (AttributeInfo attribute : attributes) {
202 if (attribute instanceof SignatureAttribute) {
203 // this has to be handled specially because SignatureAttribute.renameClass() is buggy as hell
204 SignatureAttribute signatureAttribute = (SignatureAttribute)attribute;
205 String newSignature = type.rename(signatureAttribute.getSignature(), map);
206 if (newSignature != null) {
207 signatureAttribute.setSignature(newSignature);
208 }
209 } else if (attribute instanceof CodeAttribute) {
210 // code attributes have signature attributes too (indirectly)
211 CodeAttribute codeAttribute = (CodeAttribute)attribute;
212 renameAttributes(codeAttribute.getAttributes(), map, type);
213 } else if (attribute instanceof LocalVariableTypeAttribute) {
214 // lvt attributes have signature attributes too
215 LocalVariableTypeAttribute localVariableAttribute = (LocalVariableTypeAttribute)attribute;
216 renameLocalVariableTypeAttribute(localVariableAttribute, map);
217 } else {
218 renameClassMethod.invoke(attribute, map);
219 }
220 }
221
222 } catch(NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
223 throw new Error("Unable to call javassist methods by reflection!", ex);
224 }
225 }
226
227 private static void renameLocalVariableTypeAttribute(LocalVariableTypeAttribute attribute, ReplacerClassMap map) {
228
229 // adapted from LocalVariableAttribute.renameClass()
230 ConstPool cp = attribute.getConstPool();
231 int n = attribute.tableLength();
232 byte[] info = attribute.get();
233 for (int i = 0; i < n; ++i) {
234 int pos = i * 10 + 2;
235 int index = ByteArray.readU16bit(info, pos + 6);
236 if (index != 0) {
237 String signature = cp.getUtf8Info(index);
238 String newSignature = renameLocalVariableSignature(signature, map);
239 if (newSignature != null) {
240 ByteArray.write16bit(cp.addUtf8Info(newSignature), info, pos + 6);
241 }
242 }
243 }
244 }
245
246 private static String renameLocalVariableSignature(String signature, ReplacerClassMap map) {
247
248 // for some reason, signatures with . in them don't count as field signatures
249 // looks like anonymous classes delimit with . in stead of $
250 // convert the . to $, but keep track of how many we replace
251 // we need to put them back after we translate
252 int start = signature.lastIndexOf('$') + 1;
253 int numConverted = 0;
254 StringBuilder buf = new StringBuilder(signature);
255 for (int i=buf.length()-1; i>=start; i--) {
256 char c = buf.charAt(i);
257 if (c == '.') {
258 buf.setCharAt(i, '$');
259 numConverted++;
260 }
261 }
262 signature = buf.toString();
263
264 // translate
265 String newSignature = renameFieldSignature(signature, map);
266 if (newSignature != null) {
267
268 // put the delimiters back
269 buf = new StringBuilder(newSignature);
270 for (int i=buf.length()-1; i>=0 && numConverted > 0; i--) {
271 char c = buf.charAt(i);
272 if (c == '$') {
273 buf.setCharAt(i, '.');
274 numConverted--;
275 }
276 }
277 assert(numConverted == 0);
278 newSignature = buf.toString();
279
280 return newSignature;
281 }
282
283 return null;
284 }
285
286 private static String renameClassSignature(String signature, ReplacerClassMap map) {
287 try {
288 ClassSignature type = renameType(SignatureAttribute.toClassSignature(signature), map);
289 if (type != null) {
290 return type.encode();
291 }
292 return null;
293 } catch (BadBytecode ex) {
294 throw new Error("Can't parse field signature: " + signature);
295 }
296 }
297
298 private static String renameFieldSignature(String signature, ReplacerClassMap map) {
299 try {
300 ObjectType type = renameType(SignatureAttribute.toFieldSignature(signature), map);
301 if (type != null) {
302 return type.encode();
303 }
304 return null;
305 } catch (BadBytecode ex) {
306 throw new Error("Can't parse class signature: " + signature);
307 }
308 }
309
310 private static String renameMethodSignature(String signature, ReplacerClassMap map) {
311 try {
312 MethodSignature type = renameType(SignatureAttribute.toMethodSignature(signature), map);
313 if (type != null) {
314 return type.encode();
315 }
316 return null;
317 } catch (BadBytecode ex) {
318 throw new Error("Can't parse method signature: " + signature);
319 }
320 }
321
322 private static ClassSignature renameType(ClassSignature type, ReplacerClassMap map) {
323
324 TypeParameter[] typeParamTypes = type.getParameters();
325 if (typeParamTypes != null) {
326 typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length);
327 for (int i=0; i<typeParamTypes.length; i++) {
328 TypeParameter newParamType = renameType(typeParamTypes[i], map);
329 if (newParamType != null) {
330 typeParamTypes[i] = newParamType;
331 }
332 }
333 }
334
335 ClassType superclassType = type.getSuperClass();
336 if (superclassType != ClassType.OBJECT) {
337 ClassType newSuperclassType = renameType(superclassType, map);
338 if (newSuperclassType != null) {
339 superclassType = newSuperclassType;
340 }
341 }
342
343 ClassType[] interfaceTypes = type.getInterfaces();
344 if (interfaceTypes != null) {
345 interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length);
346 for (int i=0; i<interfaceTypes.length; i++) {
347 ClassType newInterfaceType = renameType(interfaceTypes[i], map);
348 if (newInterfaceType != null) {
349 interfaceTypes[i] = newInterfaceType;
350 }
351 }
352 }
353
354 return new ClassSignature(typeParamTypes, superclassType, interfaceTypes);
355 }
356
357 private static MethodSignature renameType(MethodSignature type, ReplacerClassMap map) {
358
359 TypeParameter[] typeParamTypes = type.getTypeParameters();
360 if (typeParamTypes != null) {
361 typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length);
362 for (int i=0; i<typeParamTypes.length; i++) {
363 TypeParameter newParamType = renameType(typeParamTypes[i], map);
364 if (newParamType != null) {
365 typeParamTypes[i] = newParamType;
366 }
367 }
368 }
369
370 Type[] paramTypes = type.getParameterTypes();
371 if (paramTypes != null) {
372 paramTypes = Arrays.copyOf(paramTypes, paramTypes.length);
373 for (int i=0; i<paramTypes.length; i++) {
374 Type newParamType = renameType(paramTypes[i], map);
375 if (newParamType != null) {
376 paramTypes[i] = newParamType;
377 }
378 }
379 }
380
381 Type returnType = type.getReturnType();
382 if (returnType != null) {
383 Type newReturnType = renameType(returnType, map);
384 if (newReturnType != null) {
385 returnType = newReturnType;
386 }
387 }
388
389 ObjectType[] exceptionTypes = type.getExceptionTypes();
390 if (exceptionTypes != null) {
391 exceptionTypes = Arrays.copyOf(exceptionTypes, exceptionTypes.length);
392 for (int i=0; i<exceptionTypes.length; i++) {
393 ObjectType newExceptionType = renameType(exceptionTypes[i], map);
394 if (newExceptionType != null) {
395 exceptionTypes[i] = newExceptionType;
396 }
397 }
398 }
399
400 return new MethodSignature(typeParamTypes, paramTypes, returnType, exceptionTypes);
401 }
402
403 private static Type renameType(Type type, ReplacerClassMap map) {
404 if (type instanceof ObjectType) {
405 return renameType((ObjectType)type, map);
406 } else if (type instanceof BaseType) {
407 return renameType((BaseType)type, map);
408 } else {
409 throw new Error("Don't know how to rename type " + type.getClass());
410 }
411 }
412
413 private static ObjectType renameType(ObjectType type, ReplacerClassMap map) {
414 if (type instanceof ArrayType) {
415 return renameType((ArrayType)type, map);
416 } else if (type instanceof ClassType) {
417 return renameType((ClassType)type, map);
418 } else if (type instanceof TypeVariable) {
419 return renameType((TypeVariable)type, map);
420 } else {
421 throw new Error("Don't know how to rename type " + type.getClass());
422 }
423 }
424
425 private static BaseType renameType(BaseType type, ReplacerClassMap map) {
426 // don't have to rename primitives
427 return null;
428 }
429
430 private static TypeVariable renameType(TypeVariable type, ReplacerClassMap map) {
431 // don't have to rename template args
432 return null;
433 }
434
435 private static ClassType renameType(ClassType type, ReplacerClassMap map) {
436
437 // translate type args
438 TypeArgument[] args = type.getTypeArguments();
439 if (args != null) {
440 args = Arrays.copyOf(args, args.length);
441 for (int i=0; i<args.length; i++) {
442 TypeArgument newType = renameType(args[i], map);
443 if (newType != null) {
444 args[i] = newType;
445 }
446 }
447 }
448
449 if (type instanceof NestedClassType) {
450 NestedClassType nestedType = (NestedClassType)type;
451
452 // translate the name
453 String name = nestedType.getName();
454 String newName = map.get(getClassName(type));
455 if (newName != null) {
456 name = new ClassEntry(newName).getInnermostClassName();
457 }
458
459 // translate the parent class too
460 ClassType parent = renameType(nestedType.getDeclaringClass(), map);
461 if (parent == null) {
462 parent = nestedType.getDeclaringClass();
463 }
464
465 return new NestedClassType(parent, name, args);
466 } else {
467
468 // translate the name
469 String name = type.getName();
470 String newName = renameClassName(name, map);
471 if (newName != null) {
472 name = newName;
473 }
474
475 return new ClassType(name, args);
476 }
477 }
478
479 private static String getClassName(ClassType type) {
480 if (type instanceof NestedClassType) {
481 NestedClassType nestedType = (NestedClassType)type;
482 return getClassName(nestedType.getDeclaringClass()) + "$" + Descriptor.toJvmName(type.getName());
483 } else {
484 return Descriptor.toJvmName(type.getName());
485 }
486 }
487
488 private static String renameClassName(String name, ReplacerClassMap map) {
489 String newName = map.get(Descriptor.toJvmName(name));
490 if (newName != null) {
491 return Descriptor.toJavaName(newName);
492 }
493 return null;
494 }
495
496 private static TypeArgument renameType(TypeArgument type, ReplacerClassMap map) {
497 ObjectType subType = type.getType();
498 if (subType != null) {
499 ObjectType newSubType = renameType(subType, map);
500 if (newSubType != null) {
501 switch (type.getKind()) {
502 case ' ': return new TypeArgument(newSubType);
503 case '+': return TypeArgument.subclassOf(newSubType);
504 case '-': return TypeArgument.superOf(newSubType);
505 default:
506 throw new Error("Unknown type kind: " + type.getKind());
507 }
508 }
509 }
510 return null;
511 }
512
513 private static ArrayType renameType(ArrayType type, ReplacerClassMap map) {
514 Type newSubType = renameType(type.getComponentType(), map);
515 if (newSubType != null) {
516 return new ArrayType(type.getDimension(), newSubType);
517 }
518 return null;
519 }
520
521 private static TypeParameter renameType(TypeParameter type, ReplacerClassMap map) {
522
523 ObjectType superclassType = type.getClassBound();
524 if (superclassType != null) {
525 ObjectType newSuperclassType = renameType(superclassType, map);
526 if (newSuperclassType != null) {
527 superclassType = newSuperclassType;
528 }
529 }
530
531 ObjectType[] interfaceTypes = type.getInterfaceBound();
532 if (interfaceTypes != null) {
533 interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length);
534 for (int i=0; i<interfaceTypes.length; i++) {
535 ObjectType newInterfaceType = renameType(interfaceTypes[i], map);
536 if (newInterfaceType != null) {
537 interfaceTypes[i] = newInterfaceType;
538 }
539 }
540 }
541
542 return new TypeParameter(type.getName(), superclassType, interfaceTypes);
543 }
544}
diff --git a/src/cuchaz/enigma/bytecode/ClassTranslator.java b/src/cuchaz/enigma/bytecode/ClassTranslator.java
new file mode 100644
index 00000000..74024598
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/ClassTranslator.java
@@ -0,0 +1,157 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.CtField;
16import javassist.CtMethod;
17import javassist.bytecode.ConstPool;
18import javassist.bytecode.Descriptor;
19import javassist.bytecode.EnclosingMethodAttribute;
20import javassist.bytecode.SourceFileAttribute;
21import cuchaz.enigma.mapping.BehaviorEntry;
22import cuchaz.enigma.mapping.ClassEntry;
23import cuchaz.enigma.mapping.EntryFactory;
24import cuchaz.enigma.mapping.FieldEntry;
25import cuchaz.enigma.mapping.Signature;
26import cuchaz.enigma.mapping.Translator;
27import cuchaz.enigma.mapping.Type;
28
29public class ClassTranslator {
30
31 private Translator m_translator;
32
33 public ClassTranslator(Translator translator) {
34 m_translator = translator;
35 }
36
37 public void translate(CtClass c) {
38
39 // NOTE: the order of these translations is very important
40
41 // translate all the field and method references in the code by editing the constant pool
42 ConstPool constants = c.getClassFile().getConstPool();
43 ConstPoolEditor editor = new ConstPoolEditor(constants);
44 for (int i = 1; i < constants.getSize(); i++) {
45 switch (constants.getTag(i)) {
46
47 case ConstPool.CONST_Fieldref: {
48
49 // translate the name and type
50 FieldEntry entry = EntryFactory.getFieldEntry(
51 Descriptor.toJvmName(constants.getFieldrefClassName(i)),
52 constants.getFieldrefName(i),
53 constants.getFieldrefType(i)
54 );
55 FieldEntry translatedEntry = m_translator.translateEntry(entry);
56 if (!entry.equals(translatedEntry)) {
57 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getType().toString());
58 }
59 }
60 break;
61
62 case ConstPool.CONST_Methodref:
63 case ConstPool.CONST_InterfaceMethodref: {
64
65 // translate the name and type (ie signature)
66 BehaviorEntry entry = EntryFactory.getBehaviorEntry(
67 Descriptor.toJvmName(editor.getMemberrefClassname(i)),
68 editor.getMemberrefName(i),
69 editor.getMemberrefType(i)
70 );
71 BehaviorEntry translatedEntry = m_translator.translateEntry(entry);
72 if (!entry.equals(translatedEntry)) {
73 editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString());
74 }
75 }
76 break;
77 }
78 }
79
80 ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
81
82 // translate all the fields
83 for (CtField field : c.getDeclaredFields()) {
84
85 // translate the name
86 FieldEntry entry = EntryFactory.getFieldEntry(field);
87 String translatedName = m_translator.translate(entry);
88 if (translatedName != null) {
89 field.setName(translatedName);
90 }
91
92 // translate the type
93 Type translatedType = m_translator.translateType(entry.getType());
94 field.getFieldInfo().setDescriptor(translatedType.toString());
95 }
96
97 // translate all the methods and constructors
98 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
99
100 BehaviorEntry entry = EntryFactory.getBehaviorEntry(behavior);
101
102 if (behavior instanceof CtMethod) {
103 CtMethod method = (CtMethod)behavior;
104
105 // translate the name
106 String translatedName = m_translator.translate(entry);
107 if (translatedName != null) {
108 method.setName(translatedName);
109 }
110 }
111
112 if (entry.getSignature() != null) {
113 // translate the signature
114 Signature translatedSignature = m_translator.translateSignature(entry.getSignature());
115 behavior.getMethodInfo().setDescriptor(translatedSignature.toString());
116 }
117 }
118
119 // translate the EnclosingMethod attribute
120 EnclosingMethodAttribute enclosingMethodAttr = (EnclosingMethodAttribute)c.getClassFile().getAttribute(EnclosingMethodAttribute.tag);
121 if (enclosingMethodAttr != null) {
122
123 if (enclosingMethodAttr.methodIndex() == 0) {
124 BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(Descriptor.toJvmName(enclosingMethodAttr.className()));
125 BehaviorEntry deobfBehaviorEntry = m_translator.translateEntry(obfBehaviorEntry);
126 c.getClassFile().addAttribute(new EnclosingMethodAttribute(
127 constants,
128 deobfBehaviorEntry.getClassName()
129 ));
130 } else {
131 BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(
132 Descriptor.toJvmName(enclosingMethodAttr.className()),
133 enclosingMethodAttr.methodName(),
134 enclosingMethodAttr.methodDescriptor()
135 );
136 BehaviorEntry deobfBehaviorEntry = m_translator.translateEntry(obfBehaviorEntry);
137 c.getClassFile().addAttribute(new EnclosingMethodAttribute(
138 constants,
139 deobfBehaviorEntry.getClassName(),
140 deobfBehaviorEntry.getName(),
141 deobfBehaviorEntry.getSignature().toString()
142 ));
143 }
144 }
145
146 // translate all the class names referenced in the code
147 // the above code only changed method/field/reference names and types, but not the rest of the class references
148 ClassRenamer.renameClasses(c, m_translator);
149
150 // translate the source file attribute too
151 ClassEntry deobfClassEntry = m_translator.translateEntry(classEntry);
152 if (deobfClassEntry != null) {
153 String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOutermostClassEntry().getSimpleName()) + ".java";
154 c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile));
155 }
156 }
157}
diff --git a/src/cuchaz/enigma/bytecode/ConstPoolEditor.java b/src/cuchaz/enigma/bytecode/ConstPoolEditor.java
new file mode 100644
index 00000000..a00b86b5
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/ConstPoolEditor.java
@@ -0,0 +1,263 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..08f2b3e2
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/InfoType.java
@@ -0,0 +1,317 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.Collection;
14import java.util.List;
15import java.util.Map;
16
17import com.google.common.collect.Lists;
18import com.google.common.collect.Maps;
19
20import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor;
21import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
22import cuchaz.enigma.bytecode.accessors.InvokeDynamicInfoAccessor;
23import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor;
24import cuchaz.enigma.bytecode.accessors.MethodHandleInfoAccessor;
25import cuchaz.enigma.bytecode.accessors.MethodTypeInfoAccessor;
26import cuchaz.enigma.bytecode.accessors.NameAndTypeInfoAccessor;
27import cuchaz.enigma.bytecode.accessors.StringInfoAccessor;
28
29public enum InfoType {
30
31 Utf8Info( 1, 0 ),
32 IntegerInfo( 3, 0 ),
33 FloatInfo( 4, 0 ),
34 LongInfo( 5, 0 ),
35 DoubleInfo( 6, 0 ),
36 ClassInfo( 7, 1 ) {
37
38 @Override
39 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
40 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
41 gatherIndexTree(indices, editor, accessor.getNameIndex());
42 }
43
44 @Override
45 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
46 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
47 accessor.setNameIndex(remapIndex(map, accessor.getNameIndex()));
48 }
49
50 @Override
51 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
52 ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem());
53 ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex());
54 return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag();
55 }
56 },
57 StringInfo( 8, 1 ) {
58
59 @Override
60 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
61 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
62 gatherIndexTree(indices, editor, accessor.getStringIndex());
63 }
64
65 @Override
66 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
67 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
68 accessor.setStringIndex(remapIndex(map, accessor.getStringIndex()));
69 }
70
71 @Override
72 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
73 StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem());
74 ConstInfoAccessor stringEntry = pool.getItem(accessor.getStringIndex());
75 return stringEntry != null && stringEntry.getTag() == Utf8Info.getTag();
76 }
77 },
78 FieldRefInfo( 9, 2 ) {
79
80 @Override
81 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
82 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
83 gatherIndexTree(indices, editor, accessor.getClassIndex());
84 gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex());
85 }
86
87 @Override
88 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
89 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
90 accessor.setClassIndex(remapIndex(map, accessor.getClassIndex()));
91 accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex()));
92 }
93
94 @Override
95 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
96 MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem());
97 ConstInfoAccessor classEntry = pool.getItem(accessor.getClassIndex());
98 ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex());
99 return classEntry != null && classEntry.getTag() == ClassInfo.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag();
100 }
101 },
102 // same as FieldRefInfo
103 MethodRefInfo( 10, 2 ) {
104
105 @Override
106 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
107 FieldRefInfo.gatherIndexTree(indices, editor, entry);
108 }
109
110 @Override
111 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
112 FieldRefInfo.remapIndices(map, entry);
113 }
114
115 @Override
116 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
117 return FieldRefInfo.subIndicesAreValid(entry, pool);
118 }
119 },
120 // same as FieldRefInfo
121 InterfaceMethodRefInfo( 11, 2 ) {
122
123 @Override
124 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
125 FieldRefInfo.gatherIndexTree(indices, editor, entry);
126 }
127
128 @Override
129 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
130 FieldRefInfo.remapIndices(map, entry);
131 }
132
133 @Override
134 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
135 return FieldRefInfo.subIndicesAreValid(entry, pool);
136 }
137 },
138 NameAndTypeInfo( 12, 1 ) {
139
140 @Override
141 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
142 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
143 gatherIndexTree(indices, editor, accessor.getNameIndex());
144 gatherIndexTree(indices, editor, accessor.getTypeIndex());
145 }
146
147 @Override
148 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
149 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
150 accessor.setNameIndex(remapIndex(map, accessor.getNameIndex()));
151 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
152 }
153
154 @Override
155 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
156 NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem());
157 ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex());
158 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
159 return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag() && typeEntry != null && typeEntry.getTag() == Utf8Info.getTag();
160 }
161 },
162 MethodHandleInfo( 15, 3 ) {
163
164 @Override
165 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
166 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
167 gatherIndexTree(indices, editor, accessor.getTypeIndex());
168 gatherIndexTree(indices, editor, accessor.getMethodRefIndex());
169 }
170
171 @Override
172 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
173 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
174 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
175 accessor.setMethodRefIndex(remapIndex(map, accessor.getMethodRefIndex()));
176 }
177
178 @Override
179 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
180 MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem());
181 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
182 ConstInfoAccessor methodRefEntry = pool.getItem(accessor.getMethodRefIndex());
183 return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag() && methodRefEntry != null && methodRefEntry.getTag() == MethodRefInfo.getTag();
184 }
185 },
186 MethodTypeInfo( 16, 1 ) {
187
188 @Override
189 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
190 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
191 gatherIndexTree(indices, editor, accessor.getTypeIndex());
192 }
193
194 @Override
195 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
196 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
197 accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex()));
198 }
199
200 @Override
201 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
202 MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem());
203 ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex());
204 return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag();
205 }
206 },
207 InvokeDynamicInfo( 18, 2 ) {
208
209 @Override
210 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
211 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
212 gatherIndexTree(indices, editor, accessor.getBootstrapIndex());
213 gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex());
214 }
215
216 @Override
217 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
218 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
219 accessor.setBootstrapIndex(remapIndex(map, accessor.getBootstrapIndex()));
220 accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex()));
221 }
222
223 @Override
224 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
225 InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem());
226 ConstInfoAccessor bootstrapEntry = pool.getItem(accessor.getBootstrapIndex());
227 ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex());
228 return bootstrapEntry != null && bootstrapEntry.getTag() == Utf8Info.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag();
229 }
230 };
231
232 private static Map<Integer,InfoType> m_types;
233
234 static {
235 m_types = Maps.newTreeMap();
236 for (InfoType type : values()) {
237 m_types.put(type.getTag(), type);
238 }
239 }
240
241 private int m_tag;
242 private int m_level;
243
244 private InfoType(int tag, int level) {
245 m_tag = tag;
246 m_level = level;
247 }
248
249 public int getTag() {
250 return m_tag;
251 }
252
253 public int getLevel() {
254 return m_level;
255 }
256
257 public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) {
258 // by default, do nothing
259 }
260
261 public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) {
262 // by default, do nothing
263 }
264
265 public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
266 // by default, everything is good
267 return true;
268 }
269
270 public boolean selfIndexIsValid(ConstInfoAccessor entry, ConstPoolEditor pool) {
271 ConstInfoAccessor entryCheck = pool.getItem(entry.getIndex());
272 if (entryCheck == null) {
273 return false;
274 }
275 return entryCheck.getItem().equals(entry.getItem());
276 }
277
278 public static InfoType getByTag(int tag) {
279 return m_types.get(tag);
280 }
281
282 public static List<InfoType> getByLevel(int level) {
283 List<InfoType> types = Lists.newArrayList();
284 for (InfoType type : values()) {
285 if (type.getLevel() == level) {
286 types.add(type);
287 }
288 }
289 return types;
290 }
291
292 public static List<InfoType> getSortedByLevel() {
293 List<InfoType> types = Lists.newArrayList();
294 types.addAll(getByLevel(0));
295 types.addAll(getByLevel(1));
296 types.addAll(getByLevel(2));
297 types.addAll(getByLevel(3));
298 return types;
299 }
300
301 public static void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, int index) {
302 // add own index
303 indices.add(index);
304
305 // recurse
306 ConstInfoAccessor entry = editor.getItem(index);
307 entry.getType().gatherIndexTree(indices, editor, entry);
308 }
309
310 private static int remapIndex(Map<Integer,Integer> map, int index) {
311 Integer newIndex = map.get(index);
312 if (newIndex == null) {
313 newIndex = index;
314 }
315 return newIndex;
316 }
317}
diff --git a/src/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/cuchaz/enigma/bytecode/InnerClassWriter.java
new file mode 100644
index 00000000..bdb1b5df
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/InnerClassWriter.java
@@ -0,0 +1,132 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.Collection;
14import java.util.List;
15
16import com.google.common.collect.Lists;
17
18import javassist.CtClass;
19import javassist.bytecode.AccessFlag;
20import javassist.bytecode.ConstPool;
21import javassist.bytecode.EnclosingMethodAttribute;
22import javassist.bytecode.InnerClassesAttribute;
23import cuchaz.enigma.analysis.JarIndex;
24import cuchaz.enigma.mapping.BehaviorEntry;
25import cuchaz.enigma.mapping.ClassEntry;
26import cuchaz.enigma.mapping.EntryFactory;
27
28public class InnerClassWriter {
29
30 private JarIndex m_index;
31
32 public InnerClassWriter(JarIndex index) {
33 m_index = index;
34 }
35
36 public void write(CtClass c) {
37
38 // don't change anything if there's already an attribute there
39 InnerClassesAttribute oldAttr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag);
40 if (oldAttr != null) {
41 // bail!
42 return;
43 }
44
45 ClassEntry obfClassEntry = EntryFactory.getClassEntry(c);
46 List<ClassEntry> obfClassChain = m_index.getObfClassChain(obfClassEntry);
47
48 boolean isInnerClass = obfClassChain.size() > 1;
49 if (isInnerClass) {
50
51 // it's an inner class, rename it to the fully qualified name
52 c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName());
53
54 BehaviorEntry caller = m_index.getAnonymousClassCaller(obfClassEntry);
55 if (caller != null) {
56
57 // write the enclosing method attribute
58 if (caller.getName().equals("<clinit>")) {
59 c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName()));
60 } else {
61 c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature().toString()));
62 }
63 }
64 }
65
66 // does this class have any inner classes?
67 Collection<ClassEntry> obfInnerClassEntries = m_index.getInnerClasses(obfClassEntry);
68
69 if (isInnerClass || !obfInnerClassEntries.isEmpty()) {
70
71 // create an inner class attribute
72 InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool());
73 c.getClassFile().addAttribute(attr);
74
75 // write the ancestry, but not the outermost class
76 for (int i=1; i<obfClassChain.size(); i++) {
77 ClassEntry obfInnerClassEntry = obfClassChain.get(i);
78 writeInnerClass(attr, obfClassChain, obfInnerClassEntry);
79
80 // update references to use the fully qualified inner class name
81 c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(obfClassChain).getName());
82 }
83
84 // write the inner classes
85 for (ClassEntry obfInnerClassEntry : obfInnerClassEntries) {
86
87 // extend the class chain
88 List<ClassEntry> extendedObfClassChain = Lists.newArrayList(obfClassChain);
89 extendedObfClassChain.add(obfInnerClassEntry);
90
91 writeInnerClass(attr, extendedObfClassChain, obfInnerClassEntry);
92
93 // update references to use the fully qualified inner class name
94 c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName());
95 }
96 }
97 }
98
99 private void writeInnerClass(InnerClassesAttribute attr, List<ClassEntry> obfClassChain, ClassEntry obfClassEntry) {
100
101 // get the new inner class name
102 ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain);
103 ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry();
104
105 // here's what the JVM spec says about the InnerClasses attribute
106 // append(inner, parent, 0 if anonymous else simple name, flags);
107
108 // update the attribute with this inner class
109 ConstPool constPool = attr.getConstPool();
110 int innerClassIndex = constPool.addClassInfo(obfInnerClassEntry.getName());
111 int parentClassIndex = constPool.addClassInfo(obfOuterClassEntry.getName());
112 int innerClassNameIndex = 0;
113 int accessFlags = AccessFlag.PUBLIC;
114 // TODO: need to figure out if we can put static or not
115 if (!m_index.isAnonymousClass(obfClassEntry)) {
116 innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnermostClassName());
117 }
118
119 attr.append(innerClassIndex, parentClassIndex, innerClassNameIndex, accessFlags);
120
121 /* DEBUG
122 System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)",
123 obfClassEntry,
124 attr.innerClass(attr.tableLength() - 1),
125 attr.outerClass(attr.tableLength() - 1),
126 attr.innerName(attr.tableLength() - 1),
127 Constants.NonePackage + "/" + obfInnerClassName,
128 obfClassEntry.getName()
129 ));
130 */
131 }
132}
diff --git a/src/cuchaz/enigma/bytecode/LocalVariableRenamer.java b/src/cuchaz/enigma/bytecode/LocalVariableRenamer.java
new file mode 100644
index 00000000..ae0455f9
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/LocalVariableRenamer.java
@@ -0,0 +1,123 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.bytecode.ByteArray;
16import javassist.bytecode.CodeAttribute;
17import javassist.bytecode.ConstPool;
18import javassist.bytecode.LocalVariableAttribute;
19import javassist.bytecode.LocalVariableTypeAttribute;
20import cuchaz.enigma.mapping.ArgumentEntry;
21import cuchaz.enigma.mapping.BehaviorEntry;
22import cuchaz.enigma.mapping.EntryFactory;
23import cuchaz.enigma.mapping.Translator;
24
25
26public class LocalVariableRenamer {
27
28 private Translator m_translator;
29
30 public LocalVariableRenamer(Translator translator) {
31 m_translator = translator;
32 }
33
34 public void rename(CtClass c) {
35 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
36
37 // if there's a local variable table, just rename everything to v1, v2, v3, ... for now
38 CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute();
39 if (codeAttribute == null) {
40 continue;
41 }
42
43 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
44 ConstPool constants = c.getClassFile().getConstPool();
45
46 LocalVariableAttribute table = (LocalVariableAttribute)codeAttribute.getAttribute(LocalVariableAttribute.tag);
47 if (table != null) {
48 renameLVT(behaviorEntry, constants, table);
49 }
50
51 LocalVariableTypeAttribute typeTable = (LocalVariableTypeAttribute)codeAttribute.getAttribute(LocalVariableAttribute.typeTag);
52 if (typeTable != null) {
53 renameLVTT(typeTable, table);
54 }
55 }
56 }
57
58 // DEBUG
59 @SuppressWarnings("unused")
60 private void dumpTable(LocalVariableAttribute table) {
61 for (int i=0; i<table.tableLength(); i++) {
62 System.out.println(String.format("\t%d (%d): %s %s",
63 i, table.index(i), table.variableName(i), table.descriptor(i)
64 ));
65 }
66 }
67
68 private void renameLVT(BehaviorEntry behaviorEntry, ConstPool constants, LocalVariableAttribute table) {
69
70 // skip empty tables
71 if (table.tableLength() <= 0) {
72 return;
73 }
74
75 // where do we start counting variables?
76 int starti = 0;
77 if (table.variableName(0).equals("this")) {
78 // skip the "this" variable
79 starti = 1;
80 }
81
82 // rename method arguments first
83 int numArgs = 0;
84 if (behaviorEntry.getSignature() != null) {
85 numArgs = behaviorEntry.getSignature().getArgumentTypes().size();
86 for (int i=starti; i<starti + numArgs && i<table.tableLength(); i++) {
87 int argi = i - starti;
88 String argName = m_translator.translate(new ArgumentEntry(behaviorEntry, argi, ""));
89 if (argName == null) {
90 argName = "a" + (argi + 1);
91 }
92 renameVariable(table, i, constants.addUtf8Info(argName));
93 }
94 }
95
96 // then rename the rest of the args, if any
97 for (int i=starti + numArgs; i<table.tableLength(); i++) {
98 int firstIndex = table.index(starti + numArgs);
99 renameVariable(table, i, constants.addUtf8Info("v" + (table.index(i) - firstIndex + 1)));
100 }
101 }
102
103 private void renameLVTT(LocalVariableTypeAttribute typeTable, LocalVariableAttribute table) {
104 // rename args to the same names as in the LVT
105 for (int i=0; i<typeTable.tableLength(); i++) {
106 renameVariable(typeTable, i, getNameIndex(table, typeTable.index(i)));
107 }
108 }
109
110 private void renameVariable(LocalVariableAttribute table, int i, int stringId) {
111 // based off of LocalVariableAttribute.nameIndex()
112 ByteArray.write16bit(stringId, table.get(), i*10 + 6);
113 }
114
115 private int getNameIndex(LocalVariableAttribute table, int index) {
116 for (int i=0; i<table.tableLength(); i++) {
117 if (table.index(i) == index) {
118 return table.nameIndex(i);
119 }
120 }
121 return 0;
122 }
123}
diff --git a/src/cuchaz/enigma/bytecode/MethodParameterWriter.java b/src/cuchaz/enigma/bytecode/MethodParameterWriter.java
new file mode 100644
index 00000000..0bdf47a4
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/MethodParameterWriter.java
@@ -0,0 +1,70 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.util.ArrayList;
14import java.util.List;
15
16import javassist.CtBehavior;
17import javassist.CtClass;
18import javassist.bytecode.CodeAttribute;
19import javassist.bytecode.LocalVariableAttribute;
20import cuchaz.enigma.mapping.ArgumentEntry;
21import cuchaz.enigma.mapping.BehaviorEntry;
22import cuchaz.enigma.mapping.EntryFactory;
23import cuchaz.enigma.mapping.Signature;
24import cuchaz.enigma.mapping.Translator;
25
26public class MethodParameterWriter {
27
28 private Translator m_translator;
29
30 public MethodParameterWriter(Translator translator) {
31 m_translator = translator;
32 }
33
34 public void writeMethodArguments(CtClass c) {
35
36 // Procyon will read method arguments from the "MethodParameters" attribute, so write those
37 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
38
39 // if there's a local variable table here, don't write a MethodParameters attribute
40 // let the local variable writer deal with it instead
41 // procyon starts doing really weird things if we give it both attributes
42 CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute();
43 if (codeAttribute != null && codeAttribute.getAttribute(LocalVariableAttribute.tag) != null) {
44 continue;
45 }
46
47 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
48
49 // get the number of arguments
50 Signature signature = behaviorEntry.getSignature();
51 if (signature == null) {
52 // static initializers have no signatures, or arguments
53 continue;
54 }
55 int numParams = signature.getArgumentTypes().size();
56 if (numParams <= 0) {
57 continue;
58 }
59
60 // get the list of argument names
61 List<String> names = new ArrayList<String>(numParams);
62 for (int i = 0; i < numParams; i++) {
63 names.add(m_translator.translate(new ArgumentEntry(behaviorEntry, i, "")));
64 }
65
66 // save the mappings to the class
67 MethodParametersAttribute.updateClass(behavior.getMethodInfo(), names);
68 }
69 }
70}
diff --git a/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java b/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java
new file mode 100644
index 00000000..512e65a0
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java
@@ -0,0 +1,86 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode;
12
13import java.io.ByteArrayOutputStream;
14import java.io.DataOutputStream;
15import java.io.IOException;
16import java.util.ArrayList;
17import java.util.List;
18
19import javassist.bytecode.AttributeInfo;
20import javassist.bytecode.ConstPool;
21import javassist.bytecode.MethodInfo;
22
23public class MethodParametersAttribute extends AttributeInfo {
24
25 private MethodParametersAttribute(ConstPool pool, List<Integer> parameterNameIndices) {
26 super(pool, "MethodParameters", writeStruct(parameterNameIndices));
27 }
28
29 public static void updateClass(MethodInfo info, List<String> names) {
30
31 // add the names to the class const pool
32 ConstPool constPool = info.getConstPool();
33 List<Integer> parameterNameIndices = new ArrayList<Integer>();
34 for (String name : names) {
35 if (name != null) {
36 parameterNameIndices.add(constPool.addUtf8Info(name));
37 } else {
38 parameterNameIndices.add(0);
39 }
40 }
41
42 // add the attribute to the method
43 info.addAttribute(new MethodParametersAttribute(constPool, parameterNameIndices));
44 }
45
46 private static byte[] writeStruct(List<Integer> parameterNameIndices) {
47 // JVM 8 Spec says the struct looks like this:
48 // http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.24
49 // uint8 num_params
50 // for each param:
51 // uint16 name_index -> points to UTF8 entry in constant pool, or 0 for no entry
52 // uint16 access_flags -> don't care, just set to 0
53
54 ByteArrayOutputStream buf = new ByteArrayOutputStream();
55 DataOutputStream out = new DataOutputStream(buf);
56
57 // NOTE: java hates unsigned integers, so we have to be careful here
58 // the writeShort(), writeByte() methods will read 16,8 low-order bits from the int argument
59 // as long as the int argument is in range of the unsigned short/byte type, it will be written as an unsigned short/byte
60 // if the int is out of range, the byte stream won't look the way we want and weird things will happen
61 final int SIZEOF_UINT8 = 1;
62 final int SIZEOF_UINT16 = 2;
63 final int MAX_UINT8 = (1 << 8) - 1;
64 final int MAX_UINT16 = (1 << 16) - 1;
65
66 try {
67 assert (parameterNameIndices.size() >= 0 && parameterNameIndices.size() <= MAX_UINT8);
68 out.writeByte(parameterNameIndices.size());
69
70 for (Integer index : parameterNameIndices) {
71 assert (index >= 0 && index <= MAX_UINT16);
72 out.writeShort(index);
73
74 // just write 0 for the access flags
75 out.writeShort(0);
76 }
77
78 out.close();
79 byte[] data = buf.toByteArray();
80 assert (data.length == SIZEOF_UINT8 + parameterNameIndices.size() * (SIZEOF_UINT16 + SIZEOF_UINT16));
81 return data;
82 } catch (IOException ex) {
83 throw new Error(ex);
84 }
85 }
86}
diff --git a/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
new file mode 100644
index 00000000..9072c29a
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..ede04738
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java
@@ -0,0 +1,156 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.bytecode.accessors;
12
13import java.io.ByteArrayInputStream;
14import java.io.ByteArrayOutputStream;
15import java.io.DataInputStream;
16import java.io.DataOutputStream;
17import java.io.IOException;
18import java.io.PrintWriter;
19import java.lang.reflect.Constructor;
20import java.lang.reflect.Field;
21import java.lang.reflect.Method;
22
23import cuchaz.enigma.bytecode.InfoType;
24
25public class ConstInfoAccessor {
26
27 private static Class<?> m_class;
28 private static Field m_index;
29 private static Method m_getTag;
30
31 static {
32 try {
33 m_class = Class.forName("javassist.bytecode.ConstInfo");
34 m_index = m_class.getDeclaredField("index");
35 m_index.setAccessible(true);
36 m_getTag = m_class.getMethod("getTag");
37 m_getTag.setAccessible(true);
38 } catch (Exception ex) {
39 throw new Error(ex);
40 }
41 }
42
43 private Object m_item;
44
45 public ConstInfoAccessor(Object item) {
46 if (item == null) {
47 throw new IllegalArgumentException("item cannot be null!");
48 }
49 m_item = item;
50 }
51
52 public ConstInfoAccessor(DataInputStream in) throws IOException {
53 try {
54 // read the entry
55 String className = in.readUTF();
56 int oldIndex = in.readInt();
57
58 // NOTE: ConstInfo instances write a type id (a "tag"), but they don't read it back
59 // so we have to read it here
60 in.readByte();
61
62 Constructor<?> constructor = Class.forName(className).getConstructor(DataInputStream.class, int.class);
63 constructor.setAccessible(true);
64 m_item = constructor.newInstance(in, oldIndex);
65 } catch (IOException ex) {
66 throw ex;
67 } catch (Exception ex) {
68 throw new Error(ex);
69 }
70 }
71
72 public Object getItem() {
73 return m_item;
74 }
75
76 public int getIndex() {
77 try {
78 return (Integer)m_index.get(m_item);
79 } catch (Exception ex) {
80 throw new Error(ex);
81 }
82 }
83
84 public void setIndex(int val) {
85 try {
86 m_index.set(m_item, val);
87 } catch (Exception ex) {
88 throw new Error(ex);
89 }
90 }
91
92 public int getTag() {
93 try {
94 return (Integer)m_getTag.invoke(m_item);
95 } catch (Exception ex) {
96 throw new Error(ex);
97 }
98 }
99
100 public ConstInfoAccessor copy() {
101 return new ConstInfoAccessor(copyItem());
102 }
103
104 public Object copyItem() {
105 // I don't know of a simpler way to copy one of these silly things...
106 try {
107 // serialize the item
108 ByteArrayOutputStream buf = new ByteArrayOutputStream();
109 DataOutputStream out = new DataOutputStream(buf);
110 write(out);
111
112 // deserialize the item
113 DataInputStream in = new DataInputStream(new ByteArrayInputStream(buf.toByteArray()));
114 Object item = new ConstInfoAccessor(in).getItem();
115 in.close();
116
117 return item;
118 } catch (Exception ex) {
119 throw new Error(ex);
120 }
121 }
122
123 public void write(DataOutputStream out) throws IOException {
124 try {
125 out.writeUTF(m_item.getClass().getName());
126 out.writeInt(getIndex());
127
128 Method method = m_item.getClass().getMethod("write", DataOutputStream.class);
129 method.setAccessible(true);
130 method.invoke(m_item, out);
131 } catch (IOException ex) {
132 throw ex;
133 } catch (Exception ex) {
134 throw new Error(ex);
135 }
136 }
137
138 @Override
139 public String toString() {
140 try {
141 ByteArrayOutputStream buf = new ByteArrayOutputStream();
142 PrintWriter out = new PrintWriter(buf);
143 Method print = m_item.getClass().getMethod("print", PrintWriter.class);
144 print.setAccessible(true);
145 print.invoke(m_item, out);
146 out.close();
147 return buf.toString().replace("\n", "");
148 } catch (Exception ex) {
149 throw new Error(ex);
150 }
151 }
152
153 public InfoType getType() {
154 return InfoType.getByTag(getTag());
155 }
156}
diff --git a/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java
new file mode 100644
index 00000000..82af0b99
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..71ee5b73
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..172b0c51
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..0099a843
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..3ecc1297
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java
@@ -0,0 +1,74 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..f150612e
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..38e8ff99
--- /dev/null
+++ b/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java
@@ -0,0 +1,28 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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/ClassForest.java b/src/cuchaz/enigma/convert/ClassForest.java
new file mode 100644
index 00000000..0407730e
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassForest.java
@@ -0,0 +1,60 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Collection;
14
15import com.google.common.collect.HashMultimap;
16import com.google.common.collect.Multimap;
17
18import cuchaz.enigma.mapping.ClassEntry;
19
20
21public class ClassForest {
22
23 private ClassIdentifier m_identifier;
24 private Multimap<ClassIdentity,ClassEntry> m_forest;
25
26 public ClassForest(ClassIdentifier identifier) {
27 m_identifier = identifier;
28 m_forest = HashMultimap.create();
29 }
30
31 public void addAll(Iterable<ClassEntry> entries) {
32 for (ClassEntry entry : entries) {
33 add(entry);
34 }
35 }
36
37 public void add(ClassEntry entry) {
38 try {
39 m_forest.put(m_identifier.identify(entry), entry);
40 } catch (ClassNotFoundException ex) {
41 throw new Error("Unable to find class " + entry.getName());
42 }
43 }
44
45 public Collection<ClassIdentity> identities() {
46 return m_forest.keySet();
47 }
48
49 public Collection<ClassEntry> classes() {
50 return m_forest.values();
51 }
52
53 public Collection<ClassEntry> getClasses(ClassIdentity identity) {
54 return m_forest.get(identity);
55 }
56
57 public boolean containsIdentity(ClassIdentity identity) {
58 return m_forest.containsKey(identity);
59 }
60}
diff --git a/src/cuchaz/enigma/convert/ClassIdentifier.java b/src/cuchaz/enigma/convert/ClassIdentifier.java
new file mode 100644
index 00000000..ee5e9033
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassIdentifier.java
@@ -0,0 +1,54 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Map;
14import java.util.jar.JarFile;
15
16import com.google.common.collect.Maps;
17
18import javassist.CtClass;
19import cuchaz.enigma.TranslatingTypeLoader;
20import cuchaz.enigma.analysis.JarIndex;
21import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
22import cuchaz.enigma.mapping.ClassEntry;
23
24
25public class ClassIdentifier {
26
27 private JarIndex m_index;
28 private SidedClassNamer m_namer;
29 private boolean m_useReferences;
30 private TranslatingTypeLoader m_loader;
31 private Map<ClassEntry,ClassIdentity> m_cache;
32
33 public ClassIdentifier(JarFile jar, JarIndex index, SidedClassNamer namer, boolean useReferences) {
34 m_index = index;
35 m_namer = namer;
36 m_useReferences = useReferences;
37 m_loader = new TranslatingTypeLoader(jar, index);
38 m_cache = Maps.newHashMap();
39 }
40
41 public ClassIdentity identify(ClassEntry classEntry)
42 throws ClassNotFoundException {
43 ClassIdentity identity = m_cache.get(classEntry);
44 if (identity == null) {
45 CtClass c = m_loader.loadClass(classEntry.getName());
46 if (c == null) {
47 throw new ClassNotFoundException(classEntry.getName());
48 }
49 identity = new ClassIdentity(c, m_namer, m_index, m_useReferences);
50 m_cache.put(classEntry, identity);
51 }
52 return identity;
53 }
54}
diff --git a/src/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java
new file mode 100644
index 00000000..2e164ae7
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassIdentity.java
@@ -0,0 +1,468 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.io.UnsupportedEncodingException;
14import java.security.MessageDigest;
15import java.security.NoSuchAlgorithmException;
16import java.util.Enumeration;
17import java.util.List;
18import java.util.Map;
19import java.util.Set;
20
21import javassist.CannotCompileException;
22import javassist.CtBehavior;
23import javassist.CtClass;
24import javassist.CtConstructor;
25import javassist.CtField;
26import javassist.CtMethod;
27import javassist.bytecode.BadBytecode;
28import javassist.bytecode.CodeIterator;
29import javassist.bytecode.ConstPool;
30import javassist.bytecode.Descriptor;
31import javassist.bytecode.Opcode;
32import javassist.expr.ConstructorCall;
33import javassist.expr.ExprEditor;
34import javassist.expr.FieldAccess;
35import javassist.expr.MethodCall;
36import javassist.expr.NewExpr;
37
38import com.google.common.collect.HashMultiset;
39import com.google.common.collect.Lists;
40import com.google.common.collect.Maps;
41import com.google.common.collect.Multiset;
42import com.google.common.collect.Sets;
43
44import cuchaz.enigma.Constants;
45import cuchaz.enigma.Util;
46import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
47import cuchaz.enigma.analysis.EntryReference;
48import cuchaz.enigma.analysis.JarIndex;
49import cuchaz.enigma.bytecode.ConstPoolEditor;
50import cuchaz.enigma.bytecode.InfoType;
51import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor;
52import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
53import cuchaz.enigma.mapping.BehaviorEntry;
54import cuchaz.enigma.mapping.ClassEntry;
55import cuchaz.enigma.mapping.ClassNameReplacer;
56import cuchaz.enigma.mapping.Entry;
57import cuchaz.enigma.mapping.EntryFactory;
58import cuchaz.enigma.mapping.FieldEntry;
59import cuchaz.enigma.mapping.Signature;
60import cuchaz.enigma.mapping.Type;
61
62public class ClassIdentity {
63
64 private ClassEntry m_classEntry;
65 private SidedClassNamer m_namer;
66 private Multiset<String> m_fields;
67 private Multiset<String> m_methods;
68 private Multiset<String> m_constructors;
69 private String m_staticInitializer;
70 private String m_extends;
71 private Multiset<String> m_implements;
72 private Set<String> m_stringLiterals;
73 private Multiset<String> m_implementations;
74 private Multiset<String> m_references;
75 private String m_outer;
76
77 private final ClassNameReplacer m_classNameReplacer = new ClassNameReplacer() {
78
79 private Map<String,String> m_classNames = Maps.newHashMap();
80
81 @Override
82 public String replace(String className) {
83
84 // classes not in the none package can be passed through
85 ClassEntry classEntry = new ClassEntry(className);
86 if (!classEntry.getPackageName().equals(Constants.NonePackage)) {
87 return className;
88 }
89
90 // is this class ourself?
91 if (className.equals(m_classEntry.getName())) {
92 return "CSelf";
93 }
94
95 // try the namer
96 if (m_namer != null) {
97 String newName = m_namer.getName(className);
98 if (newName != null) {
99 return newName;
100 }
101 }
102
103 // otherwise, use local naming
104 if (!m_classNames.containsKey(className)) {
105 m_classNames.put(className, getNewClassName());
106 }
107 return m_classNames.get(className);
108 }
109
110 private String getNewClassName() {
111 return String.format("C%03d", m_classNames.size());
112 }
113 };
114
115 public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) {
116 m_namer = namer;
117
118 // stuff from the bytecode
119
120 m_classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
121 m_fields = HashMultiset.create();
122 for (CtField field : c.getDeclaredFields()) {
123 m_fields.add(scrubType(field.getSignature()));
124 }
125 m_methods = HashMultiset.create();
126 for (CtMethod method : c.getDeclaredMethods()) {
127 m_methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method));
128 }
129 m_constructors = HashMultiset.create();
130 for (CtConstructor constructor : c.getDeclaredConstructors()) {
131 m_constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor));
132 }
133 m_staticInitializer = "";
134 if (c.getClassInitializer() != null) {
135 m_staticInitializer = getBehaviorSignature(c.getClassInitializer());
136 }
137 m_extends = "";
138 if (c.getClassFile().getSuperclass() != null) {
139 m_extends = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
140 }
141 m_implements = HashMultiset.create();
142 for (String interfaceName : c.getClassFile().getInterfaces()) {
143 m_implements.add(scrubClassName(Descriptor.toJvmName(interfaceName)));
144 }
145
146 m_stringLiterals = Sets.newHashSet();
147 ConstPool constants = c.getClassFile().getConstPool();
148 for (int i=1; i<constants.getSize(); i++) {
149 if (constants.getTag(i) == ConstPool.CONST_String) {
150 m_stringLiterals.add(constants.getStringInfo(i));
151 }
152 }
153
154 // stuff from the jar index
155
156 m_implementations = HashMultiset.create();
157 ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, m_classEntry);
158 if (implementationsNode != null) {
159 @SuppressWarnings("unchecked")
160 Enumeration<ClassImplementationsTreeNode> implementations = implementationsNode.children();
161 while (implementations.hasMoreElements()) {
162 ClassImplementationsTreeNode node = implementations.nextElement();
163 m_implementations.add(scrubClassName(node.getClassEntry().getName()));
164 }
165 }
166
167 m_references = HashMultiset.create();
168 if (useReferences) {
169 for (CtField field : c.getDeclaredFields()) {
170 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
171 for (EntryReference<FieldEntry,BehaviorEntry> reference : index.getFieldReferences(fieldEntry)) {
172 addReference(reference);
173 }
174 }
175 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
176 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
177 for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(behaviorEntry)) {
178 addReference(reference);
179 }
180 }
181 }
182
183 m_outer = EntryFactory.getClassEntry(c).getOuterClassName();
184 }
185
186 private void addReference(EntryReference<? extends Entry,BehaviorEntry> reference) {
187 if (reference.context.getSignature() != null) {
188 m_references.add(String.format("%s_%s",
189 scrubClassName(reference.context.getClassName()),
190 scrubSignature(reference.context.getSignature())
191 ));
192 } else {
193 m_references.add(String.format("%s_<clinit>",
194 scrubClassName(reference.context.getClassName())
195 ));
196 }
197 }
198
199 public ClassEntry getClassEntry() {
200 return m_classEntry;
201 }
202
203 @Override
204 public String toString() {
205 StringBuilder buf = new StringBuilder();
206 buf.append("class: ");
207 buf.append(m_classEntry.getName());
208 buf.append(" ");
209 buf.append(hashCode());
210 buf.append("\n");
211 for (String field : m_fields) {
212 buf.append("\tfield ");
213 buf.append(field);
214 buf.append("\n");
215 }
216 for (String method : m_methods) {
217 buf.append("\tmethod ");
218 buf.append(method);
219 buf.append("\n");
220 }
221 for (String constructor : m_constructors) {
222 buf.append("\tconstructor ");
223 buf.append(constructor);
224 buf.append("\n");
225 }
226 if (m_staticInitializer.length() > 0) {
227 buf.append("\tinitializer ");
228 buf.append(m_staticInitializer);
229 buf.append("\n");
230 }
231 if (m_extends.length() > 0) {
232 buf.append("\textends ");
233 buf.append(m_extends);
234 buf.append("\n");
235 }
236 for (String interfaceName : m_implements) {
237 buf.append("\timplements ");
238 buf.append(interfaceName);
239 buf.append("\n");
240 }
241 for (String implementation : m_implementations) {
242 buf.append("\timplemented by ");
243 buf.append(implementation);
244 buf.append("\n");
245 }
246 for (String reference : m_references) {
247 buf.append("\treference ");
248 buf.append(reference);
249 buf.append("\n");
250 }
251 buf.append("\touter ");
252 buf.append(m_outer);
253 buf.append("\n");
254 return buf.toString();
255 }
256
257 private String scrubClassName(String className) {
258 return m_classNameReplacer.replace(className);
259 }
260
261 private String scrubType(String typeName) {
262 return scrubType(new Type(typeName)).toString();
263 }
264
265 private Type scrubType(Type type) {
266 if (type.hasClass()) {
267 return new Type(type, m_classNameReplacer);
268 } else {
269 return type;
270 }
271 }
272
273 private String scrubSignature(String signature) {
274 return scrubSignature(new Signature(signature)).toString();
275 }
276
277 private Signature scrubSignature(Signature signature) {
278 return new Signature(signature, m_classNameReplacer);
279 }
280
281 private boolean isClassMatchedUniquely(String className) {
282 return m_namer != null && m_namer.getName(Descriptor.toJvmName(className)) != null;
283 }
284
285 private String getBehaviorSignature(CtBehavior behavior) {
286 try {
287 // does this method have an implementation?
288 if (behavior.getMethodInfo().getCodeAttribute() == null) {
289 return "(none)";
290 }
291
292 // compute the hash from the opcodes
293 ConstPool constants = behavior.getMethodInfo().getConstPool();
294 final MessageDigest digest = MessageDigest.getInstance("MD5");
295 CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator();
296 while (iter.hasNext()) {
297 int pos = iter.next();
298
299 // update the hash with the opcode
300 int opcode = iter.byteAt(pos);
301 digest.update((byte)opcode);
302
303 switch (opcode) {
304 case Opcode.LDC: {
305 int constIndex = iter.byteAt(pos + 1);
306 updateHashWithConstant(digest, constants, constIndex);
307 }
308 break;
309
310 case Opcode.LDC_W:
311 case Opcode.LDC2_W: {
312 int constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2);
313 updateHashWithConstant(digest, constants, constIndex);
314 }
315 break;
316 }
317 }
318
319 // update hash with method and field accesses
320 behavior.instrument(new ExprEditor() {
321 @Override
322 public void edit(MethodCall call) {
323 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
324 updateHashWithString(digest, scrubSignature(call.getSignature()));
325 if (isClassMatchedUniquely(call.getClassName())) {
326 updateHashWithString(digest, call.getMethodName());
327 }
328 }
329
330 @Override
331 public void edit(FieldAccess access) {
332 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName())));
333 updateHashWithString(digest, scrubType(access.getSignature()));
334 if (isClassMatchedUniquely(access.getClassName())) {
335 updateHashWithString(digest, access.getFieldName());
336 }
337 }
338
339 @Override
340 public void edit(ConstructorCall call) {
341 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
342 updateHashWithString(digest, scrubSignature(call.getSignature()));
343 }
344
345 @Override
346 public void edit(NewExpr expr) {
347 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName())));
348 }
349 });
350
351 // convert the hash to a hex string
352 return toHex(digest.digest());
353 } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) {
354 throw new Error(ex);
355 }
356 }
357
358 private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) {
359 ConstPoolEditor editor = new ConstPoolEditor(constants);
360 ConstInfoAccessor item = editor.getItem(index);
361 if (item.getType() == InfoType.StringInfo) {
362 updateHashWithString(digest, constants.getStringInfo(index));
363 }
364 // TODO: other constants
365 }
366
367 private void updateHashWithString(MessageDigest digest, String val) {
368 try {
369 digest.update(val.getBytes("UTF8"));
370 } catch (UnsupportedEncodingException ex) {
371 throw new Error(ex);
372 }
373 }
374
375 private String toHex(byte[] bytes) {
376 // function taken from:
377 // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java
378 final char[] hexArray = "0123456789ABCDEF".toCharArray();
379 char[] hexChars = new char[bytes.length * 2];
380 for (int j = 0; j < bytes.length; j++) {
381 int v = bytes[j] & 0xFF;
382 hexChars[j * 2] = hexArray[v >>> 4];
383 hexChars[j * 2 + 1] = hexArray[v & 0x0F];
384 }
385 return new String(hexChars);
386 }
387
388 @Override
389 public boolean equals(Object other) {
390 if (other instanceof ClassIdentity) {
391 return equals((ClassIdentity)other);
392 }
393 return false;
394 }
395
396 public boolean equals(ClassIdentity other) {
397 return m_fields.equals(other.m_fields)
398 && m_methods.equals(other.m_methods)
399 && m_constructors.equals(other.m_constructors)
400 && m_staticInitializer.equals(other.m_staticInitializer)
401 && m_extends.equals(other.m_extends)
402 && m_implements.equals(other.m_implements)
403 && m_implementations.equals(other.m_implementations)
404 && m_references.equals(other.m_references);
405 }
406
407 @Override
408 public int hashCode() {
409 List<Object> objs = Lists.newArrayList();
410 objs.addAll(m_fields);
411 objs.addAll(m_methods);
412 objs.addAll(m_constructors);
413 objs.add(m_staticInitializer);
414 objs.add(m_extends);
415 objs.addAll(m_implements);
416 objs.addAll(m_implementations);
417 objs.addAll(m_references);
418 return Util.combineHashesOrdered(objs);
419 }
420
421 public int getMatchScore(ClassIdentity other) {
422 return 2*getNumMatches(m_extends, other.m_extends)
423 + 2*getNumMatches(m_outer, other.m_outer)
424 + 2*getNumMatches(m_implements, other.m_implements)
425 + getNumMatches(m_stringLiterals, other.m_stringLiterals)
426 + getNumMatches(m_fields, other.m_fields)
427 + getNumMatches(m_methods, other.m_methods)
428 + getNumMatches(m_constructors, other.m_constructors);
429 }
430
431 public int getMaxMatchScore() {
432 return 2 + 2 + 2*m_implements.size() + m_stringLiterals.size() + m_fields.size() + m_methods.size() + m_constructors.size();
433 }
434
435 public boolean matches(CtClass c) {
436 // just compare declaration counts
437 return m_fields.size() == c.getDeclaredFields().length
438 && m_methods.size() == c.getDeclaredMethods().length
439 && m_constructors.size() == c.getDeclaredConstructors().length;
440 }
441
442 private int getNumMatches(Set<String> a, Set<String> b) {
443 int numMatches = 0;
444 for (String val : a) {
445 if (b.contains(val)) {
446 numMatches++;
447 }
448 }
449 return numMatches;
450 }
451
452 private int getNumMatches(Multiset<String> a, Multiset<String> b) {
453 int numMatches = 0;
454 for (String val : a) {
455 if (b.contains(val)) {
456 numMatches++;
457 }
458 }
459 return numMatches;
460 }
461
462 private int getNumMatches(String a, String b) {
463 if (a.equals(b)) {
464 return 1;
465 }
466 return 0;
467 }
468}
diff --git a/src/cuchaz/enigma/convert/ClassMatch.java b/src/cuchaz/enigma/convert/ClassMatch.java
new file mode 100644
index 00000000..8c50a624
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassMatch.java
@@ -0,0 +1,88 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Collection;
14import java.util.Set;
15
16import com.google.common.collect.Sets;
17
18import cuchaz.enigma.Util;
19import cuchaz.enigma.mapping.ClassEntry;
20
21
22public class ClassMatch {
23
24 public Set<ClassEntry> sourceClasses;
25 public Set<ClassEntry> destClasses;
26
27 public ClassMatch(Collection<ClassEntry> sourceClasses, Collection<ClassEntry> destClasses) {
28 this.sourceClasses = Sets.newHashSet(sourceClasses);
29 this.destClasses = Sets.newHashSet(destClasses);
30 }
31
32 public ClassMatch(ClassEntry sourceClass, ClassEntry destClass) {
33 sourceClasses = Sets.newHashSet();
34 if (sourceClass != null) {
35 sourceClasses.add(sourceClass);
36 }
37 destClasses = Sets.newHashSet();
38 if (destClass != null) {
39 destClasses.add(destClass);
40 }
41 }
42
43 public boolean isMatched() {
44 return sourceClasses.size() > 0 && destClasses.size() > 0;
45 }
46
47 public boolean isAmbiguous() {
48 return sourceClasses.size() > 1 || destClasses.size() > 1;
49 }
50
51 public ClassEntry getUniqueSource() {
52 if (sourceClasses.size() != 1) {
53 throw new IllegalStateException("Match has ambiguous source!");
54 }
55 return sourceClasses.iterator().next();
56 }
57
58 public ClassEntry getUniqueDest() {
59 if (destClasses.size() != 1) {
60 throw new IllegalStateException("Match has ambiguous source!");
61 }
62 return destClasses.iterator().next();
63 }
64
65 public Set<ClassEntry> intersectSourceClasses(Set<ClassEntry> classes) {
66 Set<ClassEntry> intersection = Sets.newHashSet(sourceClasses);
67 intersection.retainAll(classes);
68 return intersection;
69 }
70
71 @Override
72 public int hashCode() {
73 return Util.combineHashesOrdered(sourceClasses, destClasses);
74 }
75
76 @Override
77 public boolean equals(Object other) {
78 if (other instanceof ClassMatch) {
79 return equals((ClassMatch)other);
80 }
81 return false;
82 }
83
84 public boolean equals(ClassMatch other) {
85 return this.sourceClasses.equals(other.sourceClasses)
86 && this.destClasses.equals(other.destClasses);
87 }
88}
diff --git a/src/cuchaz/enigma/convert/ClassMatches.java b/src/cuchaz/enigma/convert/ClassMatches.java
new file mode 100644
index 00000000..f70c1805
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassMatches.java
@@ -0,0 +1,163 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.ArrayList;
14import java.util.Collection;
15import java.util.Iterator;
16import java.util.Map;
17import java.util.Set;
18
19import com.google.common.collect.BiMap;
20import com.google.common.collect.HashBiMap;
21import com.google.common.collect.Maps;
22import com.google.common.collect.Sets;
23
24import cuchaz.enigma.mapping.ClassEntry;
25
26
27public class ClassMatches implements Iterable<ClassMatch> {
28
29 Collection<ClassMatch> m_matches;
30 Map<ClassEntry,ClassMatch> m_matchesBySource;
31 Map<ClassEntry,ClassMatch> m_matchesByDest;
32 BiMap<ClassEntry,ClassEntry> m_uniqueMatches;
33 Map<ClassEntry,ClassMatch> m_ambiguousMatchesBySource;
34 Map<ClassEntry,ClassMatch> m_ambiguousMatchesByDest;
35 Set<ClassEntry> m_unmatchedSourceClasses;
36 Set<ClassEntry> m_unmatchedDestClasses;
37
38 public ClassMatches() {
39 this(new ArrayList<ClassMatch>());
40 }
41
42 public ClassMatches(Collection<ClassMatch> matches) {
43 m_matches = matches;
44 m_matchesBySource = Maps.newHashMap();
45 m_matchesByDest = Maps.newHashMap();
46 m_uniqueMatches = HashBiMap.create();
47 m_ambiguousMatchesBySource = Maps.newHashMap();
48 m_ambiguousMatchesByDest = Maps.newHashMap();
49 m_unmatchedSourceClasses = Sets.newHashSet();
50 m_unmatchedDestClasses = Sets.newHashSet();
51
52 for (ClassMatch match : matches) {
53 indexMatch(match);
54 }
55 }
56
57 public void add(ClassMatch match) {
58 m_matches.add(match);
59 indexMatch(match);
60 }
61
62 public void remove(ClassMatch match) {
63 for (ClassEntry sourceClass : match.sourceClasses) {
64 m_matchesBySource.remove(sourceClass);
65 m_uniqueMatches.remove(sourceClass);
66 m_ambiguousMatchesBySource.remove(sourceClass);
67 m_unmatchedSourceClasses.remove(sourceClass);
68 }
69 for (ClassEntry destClass : match.destClasses) {
70 m_matchesByDest.remove(destClass);
71 m_uniqueMatches.inverse().remove(destClass);
72 m_ambiguousMatchesByDest.remove(destClass);
73 m_unmatchedDestClasses.remove(destClass);
74 }
75 m_matches.remove(match);
76 }
77
78 public int size() {
79 return m_matches.size();
80 }
81
82 @Override
83 public Iterator<ClassMatch> iterator() {
84 return m_matches.iterator();
85 }
86
87 private void indexMatch(ClassMatch match) {
88 if (!match.isMatched()) {
89 // unmatched
90 m_unmatchedSourceClasses.addAll(match.sourceClasses);
91 m_unmatchedDestClasses.addAll(match.destClasses);
92 } else {
93 if (match.isAmbiguous()) {
94 // ambiguously matched
95 for (ClassEntry entry : match.sourceClasses) {
96 m_ambiguousMatchesBySource.put(entry, match);
97 }
98 for (ClassEntry entry : match.destClasses) {
99 m_ambiguousMatchesByDest.put(entry, match);
100 }
101 } else {
102 // uniquely matched
103 m_uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest());
104 }
105 }
106 for (ClassEntry entry : match.sourceClasses) {
107 m_matchesBySource.put(entry, match);
108 }
109 for (ClassEntry entry : match.destClasses) {
110 m_matchesByDest.put(entry, match);
111 }
112 }
113
114 public BiMap<ClassEntry,ClassEntry> getUniqueMatches() {
115 return m_uniqueMatches;
116 }
117
118 public Set<ClassEntry> getUnmatchedSourceClasses() {
119 return m_unmatchedSourceClasses;
120 }
121
122 public Set<ClassEntry> getUnmatchedDestClasses() {
123 return m_unmatchedDestClasses;
124 }
125
126 public Set<ClassEntry> getAmbiguouslyMatchedSourceClasses() {
127 return m_ambiguousMatchesBySource.keySet();
128 }
129
130 public ClassMatch getAmbiguousMatchBySource(ClassEntry sourceClass) {
131 return m_ambiguousMatchesBySource.get(sourceClass);
132 }
133
134 public ClassMatch getMatchBySource(ClassEntry sourceClass) {
135 return m_matchesBySource.get(sourceClass);
136 }
137
138 public ClassMatch getMatchByDest(ClassEntry destClass) {
139 return m_matchesByDest.get(destClass);
140 }
141
142 public void removeSource(ClassEntry sourceClass) {
143 ClassMatch match = m_matchesBySource.get(sourceClass);
144 if (match != null) {
145 remove(match);
146 match.sourceClasses.remove(sourceClass);
147 if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) {
148 add(match);
149 }
150 }
151 }
152
153 public void removeDest(ClassEntry destClass) {
154 ClassMatch match = m_matchesByDest.get(destClass);
155 if (match != null) {
156 remove(match);
157 match.destClasses.remove(destClass);
158 if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) {
159 add(match);
160 }
161 }
162 }
163}
diff --git a/src/cuchaz/enigma/convert/ClassMatching.java b/src/cuchaz/enigma/convert/ClassMatching.java
new file mode 100644
index 00000000..633d1ac7
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassMatching.java
@@ -0,0 +1,155 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.ArrayList;
14import java.util.Collection;
15import java.util.List;
16import java.util.Map.Entry;
17import java.util.Set;
18
19import com.google.common.collect.BiMap;
20import com.google.common.collect.HashBiMap;
21import com.google.common.collect.Lists;
22import com.google.common.collect.Sets;
23
24import cuchaz.enigma.mapping.ClassEntry;
25
26public class ClassMatching {
27
28 private ClassForest m_sourceClasses;
29 private ClassForest m_destClasses;
30 private BiMap<ClassEntry,ClassEntry> m_knownMatches;
31
32 public ClassMatching(ClassIdentifier sourceIdentifier, ClassIdentifier destIdentifier) {
33 m_sourceClasses = new ClassForest(sourceIdentifier);
34 m_destClasses = new ClassForest(destIdentifier);
35 m_knownMatches = HashBiMap.create();
36 }
37
38 public void addKnownMatches(BiMap<ClassEntry,ClassEntry> knownMatches) {
39 m_knownMatches.putAll(knownMatches);
40 }
41
42 public void match(Iterable<ClassEntry> sourceClasses, Iterable<ClassEntry> destClasses) {
43 for (ClassEntry sourceClass : sourceClasses) {
44 if (!m_knownMatches.containsKey(sourceClass)) {
45 m_sourceClasses.add(sourceClass);
46 }
47 }
48 for (ClassEntry destClass : destClasses) {
49 if (!m_knownMatches.containsValue(destClass)) {
50 m_destClasses.add(destClass);
51 }
52 }
53 }
54
55 public Collection<ClassMatch> matches() {
56 List<ClassMatch> matches = Lists.newArrayList();
57 for (Entry<ClassEntry,ClassEntry> entry : m_knownMatches.entrySet()) {
58 matches.add(new ClassMatch(
59 entry.getKey(),
60 entry.getValue()
61 ));
62 }
63 for (ClassIdentity identity : m_sourceClasses.identities()) {
64 matches.add(new ClassMatch(
65 m_sourceClasses.getClasses(identity),
66 m_destClasses.getClasses(identity)
67 ));
68 }
69 for (ClassIdentity identity : m_destClasses.identities()) {
70 if (!m_sourceClasses.containsIdentity(identity)) {
71 matches.add(new ClassMatch(
72 new ArrayList<ClassEntry>(),
73 m_destClasses.getClasses(identity)
74 ));
75 }
76 }
77 return matches;
78 }
79
80 public Collection<ClassEntry> sourceClasses() {
81 Set<ClassEntry> classes = Sets.newHashSet();
82 for (ClassMatch match : matches()) {
83 classes.addAll(match.sourceClasses);
84 }
85 return classes;
86 }
87
88 public Collection<ClassEntry> destClasses() {
89 Set<ClassEntry> classes = Sets.newHashSet();
90 for (ClassMatch match : matches()) {
91 classes.addAll(match.destClasses);
92 }
93 return classes;
94 }
95
96 public BiMap<ClassEntry,ClassEntry> uniqueMatches() {
97 BiMap<ClassEntry,ClassEntry> uniqueMatches = HashBiMap.create();
98 for (ClassMatch match : matches()) {
99 if (match.isMatched() && !match.isAmbiguous()) {
100 uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest());
101 }
102 }
103 return uniqueMatches;
104 }
105
106 public Collection<ClassMatch> ambiguousMatches() {
107 List<ClassMatch> ambiguousMatches = Lists.newArrayList();
108 for (ClassMatch match : matches()) {
109 if (match.isMatched() && match.isAmbiguous()) {
110 ambiguousMatches.add(match);
111 }
112 }
113 return ambiguousMatches;
114 }
115
116 public Collection<ClassEntry> unmatchedSourceClasses() {
117 List<ClassEntry> classes = Lists.newArrayList();
118 for (ClassMatch match : matches()) {
119 if (!match.isMatched() && !match.sourceClasses.isEmpty()) {
120 classes.addAll(match.sourceClasses);
121 }
122 }
123 return classes;
124 }
125
126 public Collection<ClassEntry> unmatchedDestClasses() {
127 List<ClassEntry> classes = Lists.newArrayList();
128 for (ClassMatch match : matches()) {
129 if (!match.isMatched() && !match.destClasses.isEmpty()) {
130 classes.addAll(match.destClasses);
131 }
132 }
133 return classes;
134 }
135
136 @Override
137 public String toString() {
138
139 // count the ambiguous classes
140 int numAmbiguousSource = 0;
141 int numAmbiguousDest = 0;
142 for (ClassMatch match : ambiguousMatches()) {
143 numAmbiguousSource += match.sourceClasses.size();
144 numAmbiguousDest += match.destClasses.size();
145 }
146
147 StringBuilder buf = new StringBuilder();
148 buf.append(String.format("%20s%8s%8s\n", "", "Source", "Dest"));
149 buf.append(String.format("%20s%8d%8d\n", "Classes", sourceClasses().size(), destClasses().size()));
150 buf.append(String.format("%20s%8d%8d\n", "Uniquely matched", uniqueMatches().size(), uniqueMatches().size()));
151 buf.append(String.format("%20s%8d%8d\n", "Ambiguously matched", numAmbiguousSource, numAmbiguousDest));
152 buf.append(String.format("%20s%8d%8d\n", "Unmatched", unmatchedSourceClasses().size(), unmatchedDestClasses().size()));
153 return buf.toString();
154 }
155}
diff --git a/src/cuchaz/enigma/convert/ClassNamer.java b/src/cuchaz/enigma/convert/ClassNamer.java
new file mode 100644
index 00000000..e8fa7303
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassNamer.java
@@ -0,0 +1,66 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Map;
14
15import com.google.common.collect.BiMap;
16import com.google.common.collect.Maps;
17
18import cuchaz.enigma.mapping.ClassEntry;
19
20public class ClassNamer {
21
22 public interface SidedClassNamer {
23 String getName(String name);
24 }
25
26 private Map<String,String> m_sourceNames;
27 private Map<String,String> m_destNames;
28
29 public ClassNamer(BiMap<ClassEntry,ClassEntry> mappings) {
30 // convert the identity mappings to name maps
31 m_sourceNames = Maps.newHashMap();
32 m_destNames = Maps.newHashMap();
33 int i = 0;
34 for (Map.Entry<ClassEntry,ClassEntry> entry : mappings.entrySet()) {
35 String name = String.format("M%04d", i++);
36 m_sourceNames.put(entry.getKey().getName(), name);
37 m_destNames.put(entry.getValue().getName(), name);
38 }
39 }
40
41 public String getSourceName(String name) {
42 return m_sourceNames.get(name);
43 }
44
45 public String getDestName(String name) {
46 return m_destNames.get(name);
47 }
48
49 public SidedClassNamer getSourceNamer() {
50 return new SidedClassNamer() {
51 @Override
52 public String getName(String name) {
53 return getSourceName(name);
54 }
55 };
56 }
57
58 public SidedClassNamer getDestNamer() {
59 return new SidedClassNamer() {
60 @Override
61 public String getName(String name) {
62 return getDestName(name);
63 }
64 };
65 }
66}
diff --git a/src/cuchaz/enigma/convert/FieldMatches.java b/src/cuchaz/enigma/convert/FieldMatches.java
new file mode 100644
index 00000000..8439a84c
--- /dev/null
+++ b/src/cuchaz/enigma/convert/FieldMatches.java
@@ -0,0 +1,155 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Collection;
14import java.util.Set;
15
16import com.google.common.collect.BiMap;
17import com.google.common.collect.HashBiMap;
18import com.google.common.collect.HashMultimap;
19import com.google.common.collect.Multimap;
20import com.google.common.collect.Sets;
21
22import cuchaz.enigma.mapping.ClassEntry;
23import cuchaz.enigma.mapping.FieldEntry;
24
25
26public class FieldMatches {
27
28 private BiMap<FieldEntry,FieldEntry> m_matches;
29 private Multimap<ClassEntry,FieldEntry> m_matchedSourceFields;
30 private Multimap<ClassEntry,FieldEntry> m_unmatchedSourceFields;
31 private Multimap<ClassEntry,FieldEntry> m_unmatchedDestFields;
32 private Multimap<ClassEntry,FieldEntry> m_unmatchableSourceFields;
33
34 public FieldMatches() {
35 m_matches = HashBiMap.create();
36 m_matchedSourceFields = HashMultimap.create();
37 m_unmatchedSourceFields = HashMultimap.create();
38 m_unmatchedDestFields = HashMultimap.create();
39 m_unmatchableSourceFields = HashMultimap.create();
40 }
41
42 public void addMatch(FieldEntry srcField, FieldEntry destField) {
43 boolean wasAdded = m_matches.put(srcField, destField) == null;
44 assert (wasAdded);
45 wasAdded = m_matchedSourceFields.put(srcField.getClassEntry(), srcField);
46 assert (wasAdded);
47 }
48
49 public void addUnmatchedSourceField(FieldEntry fieldEntry) {
50 boolean wasAdded = m_unmatchedSourceFields.put(fieldEntry.getClassEntry(), fieldEntry);
51 assert (wasAdded);
52 }
53
54 public void addUnmatchedSourceFields(Iterable<FieldEntry> fieldEntries) {
55 for (FieldEntry fieldEntry : fieldEntries) {
56 addUnmatchedSourceField(fieldEntry);
57 }
58 }
59
60 public void addUnmatchedDestField(FieldEntry fieldEntry) {
61 boolean wasAdded = m_unmatchedDestFields.put(fieldEntry.getClassEntry(), fieldEntry);
62 assert (wasAdded);
63 }
64
65 public void addUnmatchedDestFields(Iterable<FieldEntry> fieldEntries) {
66 for (FieldEntry fieldEntry : fieldEntries) {
67 addUnmatchedDestField(fieldEntry);
68 }
69 }
70
71 public void addUnmatchableSourceField(FieldEntry sourceField) {
72 boolean wasAdded = m_unmatchableSourceFields.put(sourceField.getClassEntry(), sourceField);
73 assert (wasAdded);
74 }
75
76 public Set<ClassEntry> getSourceClassesWithUnmatchedFields() {
77 return m_unmatchedSourceFields.keySet();
78 }
79
80 public Collection<ClassEntry> getSourceClassesWithoutUnmatchedFields() {
81 Set<ClassEntry> out = Sets.newHashSet();
82 out.addAll(m_matchedSourceFields.keySet());
83 out.removeAll(m_unmatchedSourceFields.keySet());
84 return out;
85 }
86
87 public Collection<FieldEntry> getUnmatchedSourceFields() {
88 return m_unmatchedSourceFields.values();
89 }
90
91 public Collection<FieldEntry> getUnmatchedSourceFields(ClassEntry sourceClass) {
92 return m_unmatchedSourceFields.get(sourceClass);
93 }
94
95 public Collection<FieldEntry> getUnmatchedDestFields() {
96 return m_unmatchedDestFields.values();
97 }
98
99 public Collection<FieldEntry> getUnmatchedDestFields(ClassEntry destClass) {
100 return m_unmatchedDestFields.get(destClass);
101 }
102
103 public Collection<FieldEntry> getUnmatchableSourceFields() {
104 return m_unmatchableSourceFields.values();
105 }
106
107 public boolean hasSource(FieldEntry fieldEntry) {
108 return m_matches.containsKey(fieldEntry) || m_unmatchedSourceFields.containsValue(fieldEntry);
109 }
110
111 public boolean hasDest(FieldEntry fieldEntry) {
112 return m_matches.containsValue(fieldEntry) || m_unmatchedDestFields.containsValue(fieldEntry);
113 }
114
115 public BiMap<FieldEntry,FieldEntry> matches() {
116 return m_matches;
117 }
118
119 public boolean isMatchedSourceField(FieldEntry sourceField) {
120 return m_matches.containsKey(sourceField);
121 }
122
123 public boolean isMatchedDestField(FieldEntry destField) {
124 return m_matches.containsValue(destField);
125 }
126
127 public void makeMatch(FieldEntry sourceField, FieldEntry destField) {
128 boolean wasRemoved = m_unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
129 assert (wasRemoved);
130 wasRemoved = m_unmatchedDestFields.remove(destField.getClassEntry(), destField);
131 assert (wasRemoved);
132 addMatch(sourceField, destField);
133 }
134
135 public boolean isMatched(FieldEntry sourceField, FieldEntry destField) {
136 FieldEntry match = m_matches.get(sourceField);
137 return match != null && match.equals(destField);
138 }
139
140 public void unmakeMatch(FieldEntry sourceField, FieldEntry destField) {
141 boolean wasRemoved = m_matches.remove(sourceField) != null;
142 assert (wasRemoved);
143 wasRemoved = m_matchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
144 assert (wasRemoved);
145 addUnmatchedSourceField(sourceField);
146 addUnmatchedDestField(destField);
147 }
148
149 public void makeSourceUnmatchable(FieldEntry sourceField) {
150 assert(!isMatchedSourceField(sourceField));
151 boolean wasRemoved = m_unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
152 assert (wasRemoved);
153 addUnmatchableSourceField(sourceField);
154 }
155}
diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java
new file mode 100644
index 00000000..b457d6c4
--- /dev/null
+++ b/src/cuchaz/enigma/convert/MappingsConverter.java
@@ -0,0 +1,559 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Arrays;
14import java.util.Collection;
15import java.util.Collections;
16import java.util.Iterator;
17import java.util.LinkedHashMap;
18import java.util.List;
19import java.util.Map;
20import java.util.Set;
21import java.util.jar.JarFile;
22
23import com.google.common.collect.BiMap;
24import com.google.common.collect.HashMultimap;
25import com.google.common.collect.Lists;
26import com.google.common.collect.Maps;
27import com.google.common.collect.Multimap;
28import com.google.common.collect.Sets;
29
30import cuchaz.enigma.Deobfuscator;
31import cuchaz.enigma.analysis.JarIndex;
32import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
33import cuchaz.enigma.mapping.BehaviorEntry;
34import cuchaz.enigma.mapping.ClassEntry;
35import cuchaz.enigma.mapping.ClassMapping;
36import cuchaz.enigma.mapping.ClassNameReplacer;
37import cuchaz.enigma.mapping.ConstructorEntry;
38import cuchaz.enigma.mapping.Entry;
39import cuchaz.enigma.mapping.FieldEntry;
40import cuchaz.enigma.mapping.FieldMapping;
41import cuchaz.enigma.mapping.Mappings;
42import cuchaz.enigma.mapping.MappingsChecker;
43import cuchaz.enigma.mapping.MemberMapping;
44import cuchaz.enigma.mapping.MethodEntry;
45import cuchaz.enigma.mapping.MethodMapping;
46import cuchaz.enigma.mapping.Signature;
47import cuchaz.enigma.mapping.Type;
48
49public class MappingsConverter {
50
51 public static ClassMatches computeClassMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) {
52
53 // index jars
54 System.out.println("Indexing source jar...");
55 JarIndex sourceIndex = new JarIndex();
56 sourceIndex.indexJar(sourceJar, false);
57 System.out.println("Indexing dest jar...");
58 JarIndex destIndex = new JarIndex();
59 destIndex.indexJar(destJar, false);
60
61 // compute the matching
62 ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null);
63 return new ClassMatches(matching.matches());
64 }
65
66 public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap<ClassEntry,ClassEntry> knownMatches) {
67
68 System.out.println("Iteratively matching classes");
69
70 ClassMatching lastMatching = null;
71 int round = 0;
72 SidedClassNamer sourceNamer = null;
73 SidedClassNamer destNamer = null;
74 for (boolean useReferences : Arrays.asList(false, true)) {
75
76 int numUniqueMatchesLastTime = 0;
77 if (lastMatching != null) {
78 numUniqueMatchesLastTime = lastMatching.uniqueMatches().size();
79 }
80
81 while (true) {
82
83 System.out.println("Round " + (++round) + "...");
84
85 // init the matching with identity settings
86 ClassMatching matching = new ClassMatching(
87 new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences),
88 new ClassIdentifier(destJar, destIndex, destNamer, useReferences)
89 );
90
91 if (knownMatches != null) {
92 matching.addKnownMatches(knownMatches);
93 }
94
95 if (lastMatching == null) {
96 // search all classes
97 matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries());
98 } else {
99 // we already know about these matches from last time
100 matching.addKnownMatches(lastMatching.uniqueMatches());
101
102 // search unmatched and ambiguously-matched classes
103 matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses());
104 for (ClassMatch match : lastMatching.ambiguousMatches()) {
105 matching.match(match.sourceClasses, match.destClasses);
106 }
107 }
108 System.out.println(matching);
109 BiMap<ClassEntry,ClassEntry> uniqueMatches = matching.uniqueMatches();
110
111 // did we match anything new this time?
112 if (uniqueMatches.size() > numUniqueMatchesLastTime) {
113 numUniqueMatchesLastTime = uniqueMatches.size();
114 lastMatching = matching;
115 } else {
116 break;
117 }
118
119 // update the namers
120 ClassNamer namer = new ClassNamer(uniqueMatches);
121 sourceNamer = namer.getSourceNamer();
122 destNamer = namer.getDestNamer();
123 }
124 }
125
126 return lastMatching;
127 }
128
129 public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
130
131 // sort the unique matches by size of inner class chain
132 Multimap<Integer,java.util.Map.Entry<ClassEntry,ClassEntry>> matchesByDestChainSize = HashMultimap.create();
133 for (java.util.Map.Entry<ClassEntry,ClassEntry> match : matches.getUniqueMatches().entrySet()) {
134 int chainSize = destDeobfuscator.getJarIndex().getObfClassChain(match.getValue()).size();
135 matchesByDestChainSize.put(chainSize, match);
136 }
137
138 // build the mappings (in order of small-to-large inner chains)
139 Mappings newMappings = new Mappings();
140 List<Integer> chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet());
141 Collections.sort(chainSizes);
142 for (int chainSize : chainSizes) {
143 for (java.util.Map.Entry<ClassEntry,ClassEntry> match : matchesByDestChainSize.get(chainSize)) {
144
145 // get class info
146 ClassEntry obfSourceClassEntry = match.getKey();
147 ClassEntry obfDestClassEntry = match.getValue();
148 List<ClassEntry> destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(obfDestClassEntry);
149
150 ClassMapping sourceMapping = sourceDeobfuscator.getMappings().getClassByObf(obfSourceClassEntry);
151 if (sourceMapping == null) {
152 // if this class was never deobfuscated, don't try to match it
153 continue;
154 }
155
156 // find out where to make the dest class mapping
157 if (destClassChain.size() == 1) {
158 // not an inner class, add directly to mappings
159 newMappings.addClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, false));
160 } else {
161 // inner class, find the outer class mapping
162 ClassMapping destMapping = null;
163 for (int i=0; i<destClassChain.size()-1; i++) {
164 ClassEntry destChainClassEntry = destClassChain.get(i);
165 if (destMapping == null) {
166 destMapping = newMappings.getClassByObf(destChainClassEntry);
167 if (destMapping == null) {
168 destMapping = new ClassMapping(destChainClassEntry.getName());
169 newMappings.addClassMapping(destMapping);
170 }
171 } else {
172 destMapping = destMapping.getInnerClassByObfSimple(destChainClassEntry.getInnermostClassName());
173 if (destMapping == null) {
174 destMapping = new ClassMapping(destChainClassEntry.getName());
175 destMapping.addInnerClassMapping(destMapping);
176 }
177 }
178 }
179 destMapping.addInnerClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, true));
180 }
181 }
182 }
183 return newMappings;
184 }
185
186 private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping oldClassMapping, final ClassMatches matches, boolean useSimpleName) {
187
188 ClassNameReplacer replacer = new ClassNameReplacer() {
189 @Override
190 public String replace(String className) {
191 ClassEntry newClassEntry = matches.getUniqueMatches().get(new ClassEntry(className));
192 if (newClassEntry != null) {
193 return newClassEntry.getName();
194 }
195 return null;
196 }
197 };
198
199 ClassMapping newClassMapping;
200 String deobfName = oldClassMapping.getDeobfName();
201 if (deobfName != null) {
202 if (useSimpleName) {
203 deobfName = new ClassEntry(deobfName).getSimpleName();
204 }
205 newClassMapping = new ClassMapping(newObfClass.getName(), deobfName);
206 } else {
207 newClassMapping = new ClassMapping(newObfClass.getName());
208 }
209
210 // copy fields
211 for (FieldMapping fieldMapping : oldClassMapping.fields()) {
212 newClassMapping.addFieldMapping(new FieldMapping(fieldMapping, replacer));
213 }
214
215 // copy methods
216 for (MethodMapping methodMapping : oldClassMapping.methods()) {
217 newClassMapping.addMethodMapping(new MethodMapping(methodMapping, replacer));
218 }
219
220 return newClassMapping;
221 }
222
223 public static void convertMappings(Mappings mappings, BiMap<ClassEntry,ClassEntry> changes) {
224
225 // sort the changes so classes are renamed in the correct order
226 // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b
227 LinkedHashMap<ClassEntry,ClassEntry> sortedChanges = Maps.newLinkedHashMap();
228 int numChangesLeft = changes.size();
229 while (!changes.isEmpty()) {
230 Iterator<Map.Entry<ClassEntry,ClassEntry>> iter = changes.entrySet().iterator();
231 while (iter.hasNext()) {
232 Map.Entry<ClassEntry,ClassEntry> change = iter.next();
233 if (changes.containsKey(change.getValue())) {
234 sortedChanges.put(change.getKey(), change.getValue());
235 iter.remove();
236 }
237 }
238
239 // did we remove any changes?
240 if (numChangesLeft - changes.size() > 0) {
241 // keep going
242 numChangesLeft = changes.size();
243 } else {
244 // can't sort anymore. There must be a loop
245 break;
246 }
247 }
248 if (!changes.isEmpty()) {
249 throw new Error("Unable to sort class changes! There must be a cycle.");
250 }
251
252 // convert the mappings in the correct class order
253 for (Map.Entry<ClassEntry,ClassEntry> entry : sortedChanges.entrySet()) {
254 mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName());
255 }
256 }
257
258 public static interface Doer<T extends Entry> {
259 Collection<T> getDroppedEntries(MappingsChecker checker);
260 Collection<T> getObfEntries(JarIndex jarIndex);
261 Collection<? extends MemberMapping<T>> getMappings(ClassMapping destClassMapping);
262 Set<T> filterEntries(Collection<T> obfEntries, T obfSourceEntry, ClassMatches classMatches);
263 void setUpdateObfMember(ClassMapping classMapping, MemberMapping<T> memberMapping, T newEntry);
264 boolean hasObfMember(ClassMapping classMapping, T obfEntry);
265 void removeMemberByObf(ClassMapping classMapping, T obfEntry);
266 }
267
268 public static Doer<FieldEntry> getFieldDoer() {
269 return new Doer<FieldEntry>() {
270
271 @Override
272 public Collection<FieldEntry> getDroppedEntries(MappingsChecker checker) {
273 return checker.getDroppedFieldMappings().keySet();
274 }
275
276 @Override
277 public Collection<FieldEntry> getObfEntries(JarIndex jarIndex) {
278 return jarIndex.getObfFieldEntries();
279 }
280
281 @Override
282 public Collection<? extends MemberMapping<FieldEntry>> getMappings(ClassMapping destClassMapping) {
283 return (Collection<? extends MemberMapping<FieldEntry>>)destClassMapping.fields();
284 }
285
286 @Override
287 public Set<FieldEntry> filterEntries(Collection<FieldEntry> obfDestFields, FieldEntry obfSourceField, ClassMatches classMatches) {
288 Set<FieldEntry> out = Sets.newHashSet();
289 for (FieldEntry obfDestField : obfDestFields) {
290 Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse());
291 if (translatedDestType.equals(obfSourceField.getType())) {
292 out.add(obfDestField);
293 }
294 }
295 return out;
296 }
297
298 @Override
299 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<FieldEntry> memberMapping, FieldEntry newField) {
300 FieldMapping fieldMapping = (FieldMapping)memberMapping;
301 classMapping.setFieldObfNameAndType(fieldMapping.getObfName(), fieldMapping.getObfType(), newField.getName(), newField.getType());
302 }
303
304 @Override
305 public boolean hasObfMember(ClassMapping classMapping, FieldEntry obfField) {
306 return classMapping.getFieldByObf(obfField.getName(), obfField.getType()) != null;
307 }
308
309 @Override
310 public void removeMemberByObf(ClassMapping classMapping, FieldEntry obfField) {
311 classMapping.removeFieldMapping(classMapping.getFieldByObf(obfField.getName(), obfField.getType()));
312 }
313 };
314 }
315
316 public static Doer<BehaviorEntry> getMethodDoer() {
317 return new Doer<BehaviorEntry>() {
318
319 @Override
320 public Collection<BehaviorEntry> getDroppedEntries(MappingsChecker checker) {
321 return checker.getDroppedMethodMappings().keySet();
322 }
323
324 @Override
325 public Collection<BehaviorEntry> getObfEntries(JarIndex jarIndex) {
326 return jarIndex.getObfBehaviorEntries();
327 }
328
329 @Override
330 public Collection<? extends MemberMapping<BehaviorEntry>> getMappings(ClassMapping destClassMapping) {
331 return (Collection<? extends MemberMapping<BehaviorEntry>>)destClassMapping.methods();
332 }
333
334 @Override
335 public Set<BehaviorEntry> filterEntries(Collection<BehaviorEntry> obfDestFields, BehaviorEntry obfSourceField, ClassMatches classMatches) {
336 Set<BehaviorEntry> out = Sets.newHashSet();
337 for (BehaviorEntry obfDestField : obfDestFields) {
338 Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse());
339 if (translatedDestSignature == null && obfSourceField.getSignature() == null) {
340 out.add(obfDestField);
341 } else if (translatedDestSignature == null || obfSourceField.getSignature() == null) {
342 // skip it
343 } else if (translatedDestSignature.equals(obfSourceField.getSignature())) {
344 out.add(obfDestField);
345 }
346 }
347 return out;
348 }
349
350 @Override
351 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<BehaviorEntry> memberMapping, BehaviorEntry newBehavior) {
352 MethodMapping methodMapping = (MethodMapping)memberMapping;
353 classMapping.setMethodObfNameAndSignature(methodMapping.getObfName(), methodMapping.getObfSignature(), newBehavior.getName(), newBehavior.getSignature());
354 }
355
356 @Override
357 public boolean hasObfMember(ClassMapping classMapping, BehaviorEntry obfBehavior) {
358 return classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()) != null;
359 }
360
361 @Override
362 public void removeMemberByObf(ClassMapping classMapping, BehaviorEntry obfBehavior) {
363 classMapping.removeMethodMapping(classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()));
364 }
365 };
366 }
367
368 public static <T extends Entry> MemberMatches<T> computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer<T> doer) {
369
370 MemberMatches<T> memberMatches = new MemberMatches<T>();
371
372 // unmatched source fields are easy
373 MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex());
374 checker.dropBrokenMappings(destMappings);
375 for (T destObfEntry : doer.getDroppedEntries(checker)) {
376 T srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse());
377 memberMatches.addUnmatchedSourceEntry(srcObfEntry);
378 }
379
380 // get matched fields (anything that's left after the checks/drops is matched(
381 for (ClassMapping classMapping : destMappings.classes()) {
382 collectMatchedFields(memberMatches, classMapping, classMatches, doer);
383 }
384
385 // get unmatched dest fields
386 for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) {
387 if (!memberMatches.isMatchedDestEntry(destEntry)) {
388 memberMatches.addUnmatchedDestEntry(destEntry);
389 }
390 }
391
392 System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries...");
393
394 // go through the unmatched source fields and try to pick out the easy matches
395 for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) {
396 for (T obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) {
397
398 // get the possible dest matches
399 ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass);
400
401 // filter by type/signature
402 Set<T> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches);
403
404 if (obfDestEntries.size() == 1) {
405 // make the easy match
406 memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next());
407 } else if (obfDestEntries.isEmpty()) {
408 // no match is possible =(
409 memberMatches.makeSourceUnmatchable(obfSourceEntry);
410 }
411 }
412 }
413
414 System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries",
415 memberMatches.getUnmatchedSourceEntries().size(),
416 memberMatches.getUnmatchableSourceEntries().size()
417 ));
418
419 return memberMatches;
420 }
421
422 private static <T extends Entry> void collectMatchedFields(MemberMatches<T> memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer<T> doer) {
423
424 // get the fields for this class
425 for (MemberMapping<T> destEntryMapping : doer.getMappings(destClassMapping)) {
426 T destObfField = destEntryMapping.getObfEntry(destClassMapping.getObfEntry());
427 T srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse());
428 memberMatches.addMatch(srcObfField, destObfField);
429 }
430
431 // recurse
432 for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) {
433 collectMatchedFields(memberMatches, destInnerClassMapping, classMatches, doer);
434 }
435 }
436
437 @SuppressWarnings("unchecked")
438 private static <T extends Entry> T translate(T in, BiMap<ClassEntry,ClassEntry> map) {
439 if (in instanceof FieldEntry) {
440 return (T)new FieldEntry(
441 map.get(in.getClassEntry()),
442 in.getName(),
443 translate(((FieldEntry)in).getType(), map)
444 );
445 } else if (in instanceof MethodEntry) {
446 return (T)new MethodEntry(
447 map.get(in.getClassEntry()),
448 in.getName(),
449 translate(((MethodEntry)in).getSignature(), map)
450 );
451 } else if (in instanceof ConstructorEntry) {
452 return (T)new ConstructorEntry(
453 map.get(in.getClassEntry()),
454 translate(((ConstructorEntry)in).getSignature(), map)
455 );
456 }
457 throw new Error("Unhandled entry type: " + in.getClass());
458 }
459
460 private static Type translate(Type type, final BiMap<ClassEntry,ClassEntry> map) {
461 return new Type(type, new ClassNameReplacer() {
462 @Override
463 public String replace(String inClassName) {
464 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName));
465 if (outClassEntry == null) {
466 return null;
467 }
468 return outClassEntry.getName();
469 }
470 });
471 }
472
473 private static Signature translate(Signature signature, final BiMap<ClassEntry,ClassEntry> map) {
474 if (signature == null) {
475 return null;
476 }
477 return new Signature(signature, new ClassNameReplacer() {
478 @Override
479 public String replace(String inClassName) {
480 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName));
481 if (outClassEntry == null) {
482 return null;
483 }
484 return outClassEntry.getName();
485 }
486 });
487 }
488
489 public static <T extends Entry> void applyMemberMatches(Mappings mappings, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) {
490 for (ClassMapping classMapping : mappings.classes()) {
491 applyMemberMatches(classMapping, classMatches, memberMatches, doer);
492 }
493 }
494
495 private static <T extends Entry> void applyMemberMatches(ClassMapping classMapping, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) {
496
497 // get the classes
498 ClassEntry obfDestClass = classMapping.getObfEntry();
499
500 // make a map of all the renames we need to make
501 Map<T,T> renames = Maps.newHashMap();
502 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) {
503 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass);
504 T obfSourceEntry = getSourceEntryFromDestMapping(memberMapping, obfDestClass, classMatches);
505
506 // but drop the unmatchable things
507 if (memberMatches.isUnmatchableSourceEntry(obfSourceEntry)) {
508 doer.removeMemberByObf(classMapping, obfOldDestEntry);
509 continue;
510 }
511
512 T obfNewDestEntry = memberMatches.matches().get(obfSourceEntry);
513 if (obfNewDestEntry != null && !obfOldDestEntry.getName().equals(obfNewDestEntry.getName())) {
514 renames.put(obfOldDestEntry, obfNewDestEntry);
515 }
516 }
517
518 if (!renames.isEmpty()) {
519
520 // apply to this class (should never need more than n passes)
521 int numRenamesAppliedThisRound;
522 do {
523 numRenamesAppliedThisRound = 0;
524
525 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) {
526 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass);
527 T obfNewDestEntry = renames.get(obfOldDestEntry);
528 if (obfNewDestEntry != null) {
529 // make sure this rename won't cause a collision
530 // otherwise, save it for the next round and try again next time
531 if (!doer.hasObfMember(classMapping, obfNewDestEntry)) {
532 doer.setUpdateObfMember(classMapping, memberMapping, obfNewDestEntry);
533 renames.remove(obfOldDestEntry);
534 numRenamesAppliedThisRound++;
535 }
536 }
537 }
538 } while(numRenamesAppliedThisRound > 0);
539
540 if (!renames.isEmpty()) {
541 System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d renames left.",
542 classMapping.getObfFullName(), renames.size()
543 ));
544 for (Map.Entry<T,T> entry : renames.entrySet()) {
545 System.err.println(String.format("\t%s -> %s", entry.getKey().getName(), entry.getValue().getName()));
546 }
547 }
548 }
549
550 // recurse
551 for (ClassMapping innerClassMapping : classMapping.innerClasses()) {
552 applyMemberMatches(innerClassMapping, classMatches, memberMatches, doer);
553 }
554 }
555
556 private static <T extends Entry> T getSourceEntryFromDestMapping(MemberMapping<T> destMemberMapping, ClassEntry obfDestClass, ClassMatches classMatches) {
557 return translate(destMemberMapping.getObfEntry(obfDestClass), classMatches.getUniqueMatches().inverse());
558 }
559}
diff --git a/src/cuchaz/enigma/convert/MatchesReader.java b/src/cuchaz/enigma/convert/MatchesReader.java
new file mode 100644
index 00000000..7514e2a9
--- /dev/null
+++ b/src/cuchaz/enigma/convert/MatchesReader.java
@@ -0,0 +1,113 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.io.BufferedReader;
14import java.io.File;
15import java.io.FileReader;
16import java.io.IOException;
17import java.util.Collection;
18import java.util.List;
19
20import com.google.common.collect.Lists;
21
22import cuchaz.enigma.mapping.ClassEntry;
23import cuchaz.enigma.mapping.Entry;
24import cuchaz.enigma.mapping.EntryFactory;
25import cuchaz.enigma.mapping.FieldEntry;
26import cuchaz.enigma.mapping.Type;
27
28
29public class MatchesReader {
30
31 public static ClassMatches readClasses(File file)
32 throws IOException {
33 try (BufferedReader in = new BufferedReader(new FileReader(file))) {
34 ClassMatches matches = new ClassMatches();
35 String line = null;
36 while ((line = in.readLine()) != null) {
37 matches.add(readClassMatch(line));
38 }
39 return matches;
40 }
41 }
42
43 private static ClassMatch readClassMatch(String line)
44 throws IOException {
45 String[] sides = line.split(":", 2);
46 return new ClassMatch(readClasses(sides[0]), readClasses(sides[1]));
47 }
48
49 private static Collection<ClassEntry> readClasses(String in) {
50 List<ClassEntry> entries = Lists.newArrayList();
51 for (String className : in.split(",")) {
52 className = className.trim();
53 if (className.length() > 0) {
54 entries.add(new ClassEntry(className));
55 }
56 }
57 return entries;
58 }
59
60 public static <T extends Entry> MemberMatches<T> readMembers(File file)
61 throws IOException {
62 try (BufferedReader in = new BufferedReader(new FileReader(file))) {
63 MemberMatches<T> matches = new MemberMatches<T>();
64 String line = null;
65 while ((line = in.readLine()) != null) {
66 readMemberMatch(matches, line);
67 }
68 return matches;
69 }
70 }
71
72 private static <T extends Entry> void readMemberMatch(MemberMatches<T> matches, String line) {
73 if (line.startsWith("!")) {
74 T source = readEntry(line.substring(1));
75 matches.addUnmatchableSourceEntry(source);
76 } else {
77 String[] parts = line.split(":", 2);
78 T source = readEntry(parts[0]);
79 T dest = readEntry(parts[1]);
80 if (source != null && dest != null) {
81 matches.addMatch(source, dest);
82 } else if (source != null) {
83 matches.addUnmatchedSourceEntry(source);
84 } else if (dest != null) {
85 matches.addUnmatchedDestEntry(dest);
86 }
87 }
88 }
89
90 @SuppressWarnings("unchecked")
91 private static <T extends Entry> T readEntry(String in) {
92 if (in.length() <= 0) {
93 return null;
94 }
95 String[] parts = in.split(" ");
96 if (parts.length == 3 && parts[2].indexOf('(') < 0) {
97 return (T)new FieldEntry(
98 new ClassEntry(parts[0]),
99 parts[1],
100 new Type(parts[2])
101 );
102 } else {
103 assert(parts.length == 2 || parts.length == 3);
104 if (parts.length == 2) {
105 return (T)EntryFactory.getBehaviorEntry(parts[0], parts[1]);
106 } else if (parts.length == 3) {
107 return (T)EntryFactory.getBehaviorEntry(parts[0], parts[1], parts[2]);
108 } else {
109 throw new Error("Malformed behavior entry: " + in);
110 }
111 }
112 }
113}
diff --git a/src/cuchaz/enigma/convert/MatchesWriter.java b/src/cuchaz/enigma/convert/MatchesWriter.java
new file mode 100644
index 00000000..42c6b61b
--- /dev/null
+++ b/src/cuchaz/enigma/convert/MatchesWriter.java
@@ -0,0 +1,121 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.io.File;
14import java.io.FileWriter;
15import java.io.IOException;
16import java.util.Map;
17
18import cuchaz.enigma.mapping.BehaviorEntry;
19import cuchaz.enigma.mapping.ClassEntry;
20import cuchaz.enigma.mapping.Entry;
21import cuchaz.enigma.mapping.FieldEntry;
22
23
24public class MatchesWriter {
25
26 public static void writeClasses(ClassMatches matches, File file)
27 throws IOException {
28 try (FileWriter out = new FileWriter(file)) {
29 for (ClassMatch match : matches) {
30 writeClassMatch(out, match);
31 }
32 }
33 }
34
35 private static void writeClassMatch(FileWriter out, ClassMatch match)
36 throws IOException {
37 writeClasses(out, match.sourceClasses);
38 out.write(":");
39 writeClasses(out, match.destClasses);
40 out.write("\n");
41 }
42
43 private static void writeClasses(FileWriter out, Iterable<ClassEntry> classes)
44 throws IOException {
45 boolean isFirst = true;
46 for (ClassEntry entry : classes) {
47 if (isFirst) {
48 isFirst = false;
49 } else {
50 out.write(",");
51 }
52 out.write(entry.toString());
53 }
54 }
55
56 public static <T extends Entry> void writeMembers(MemberMatches<T> matches, File file)
57 throws IOException {
58 try (FileWriter out = new FileWriter(file)) {
59 for (Map.Entry<T,T> match : matches.matches().entrySet()) {
60 writeMemberMatch(out, match.getKey(), match.getValue());
61 }
62 for (T entry : matches.getUnmatchedSourceEntries()) {
63 writeMemberMatch(out, entry, null);
64 }
65 for (T entry : matches.getUnmatchedDestEntries()) {
66 writeMemberMatch(out, null, entry);
67 }
68 for (T entry : matches.getUnmatchableSourceEntries()) {
69 writeUnmatchableEntry(out, entry);
70 }
71 }
72 }
73
74 private static <T extends Entry> void writeMemberMatch(FileWriter out, T source, T dest)
75 throws IOException {
76 if (source != null) {
77 writeEntry(out, source);
78 }
79 out.write(":");
80 if (dest != null) {
81 writeEntry(out, dest);
82 }
83 out.write("\n");
84 }
85
86 private static <T extends Entry> void writeUnmatchableEntry(FileWriter out, T entry)
87 throws IOException {
88 out.write("!");
89 writeEntry(out, entry);
90 out.write("\n");
91 }
92
93 private static <T extends Entry> void writeEntry(FileWriter out, T entry)
94 throws IOException {
95 if (entry instanceof FieldEntry) {
96 writeField(out, (FieldEntry)entry);
97 } else if (entry instanceof BehaviorEntry) {
98 writeBehavior(out, (BehaviorEntry)entry);
99 }
100 }
101
102 private static void writeField(FileWriter out, FieldEntry fieldEntry)
103 throws IOException {
104 out.write(fieldEntry.getClassName());
105 out.write(" ");
106 out.write(fieldEntry.getName());
107 out.write(" ");
108 out.write(fieldEntry.getType().toString());
109 }
110
111 private static void writeBehavior(FileWriter out, BehaviorEntry behaviorEntry)
112 throws IOException {
113 out.write(behaviorEntry.getClassName());
114 out.write(" ");
115 out.write(behaviorEntry.getName());
116 out.write(" ");
117 if (behaviorEntry.getSignature() != null) {
118 out.write(behaviorEntry.getSignature().toString());
119 }
120 }
121}
diff --git a/src/cuchaz/enigma/convert/MemberMatches.java b/src/cuchaz/enigma/convert/MemberMatches.java
new file mode 100644
index 00000000..29def159
--- /dev/null
+++ b/src/cuchaz/enigma/convert/MemberMatches.java
@@ -0,0 +1,159 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.convert;
12
13import java.util.Collection;
14import java.util.Set;
15
16import com.google.common.collect.BiMap;
17import com.google.common.collect.HashBiMap;
18import com.google.common.collect.HashMultimap;
19import com.google.common.collect.Multimap;
20import com.google.common.collect.Sets;
21
22import cuchaz.enigma.mapping.ClassEntry;
23import cuchaz.enigma.mapping.Entry;
24
25
26public class MemberMatches<T extends Entry> {
27
28 private BiMap<T,T> m_matches;
29 private Multimap<ClassEntry,T> m_matchedSourceEntries;
30 private Multimap<ClassEntry,T> m_unmatchedSourceEntries;
31 private Multimap<ClassEntry,T> m_unmatchedDestEntries;
32 private Multimap<ClassEntry,T> m_unmatchableSourceEntries;
33
34 public MemberMatches() {
35 m_matches = HashBiMap.create();
36 m_matchedSourceEntries = HashMultimap.create();
37 m_unmatchedSourceEntries = HashMultimap.create();
38 m_unmatchedDestEntries = HashMultimap.create();
39 m_unmatchableSourceEntries = HashMultimap.create();
40 }
41
42 public void addMatch(T srcEntry, T destEntry) {
43 boolean wasAdded = m_matches.put(srcEntry, destEntry) == null;
44 assert (wasAdded);
45 wasAdded = m_matchedSourceEntries.put(srcEntry.getClassEntry(), srcEntry);
46 assert (wasAdded);
47 }
48
49 public void addUnmatchedSourceEntry(T sourceEntry) {
50 boolean wasAdded = m_unmatchedSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry);
51 assert (wasAdded);
52 }
53
54 public void addUnmatchedSourceEntries(Iterable<T> sourceEntries) {
55 for (T sourceEntry : sourceEntries) {
56 addUnmatchedSourceEntry(sourceEntry);
57 }
58 }
59
60 public void addUnmatchedDestEntry(T destEntry) {
61 boolean wasAdded = m_unmatchedDestEntries.put(destEntry.getClassEntry(), destEntry);
62 assert (wasAdded);
63 }
64
65 public void addUnmatchedDestEntries(Iterable<T> destEntriesntries) {
66 for (T entry : destEntriesntries) {
67 addUnmatchedDestEntry(entry);
68 }
69 }
70
71 public void addUnmatchableSourceEntry(T sourceEntry) {
72 boolean wasAdded = m_unmatchableSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry);
73 assert (wasAdded);
74 }
75
76 public Set<ClassEntry> getSourceClassesWithUnmatchedEntries() {
77 return m_unmatchedSourceEntries.keySet();
78 }
79
80 public Collection<ClassEntry> getSourceClassesWithoutUnmatchedEntries() {
81 Set<ClassEntry> out = Sets.newHashSet();
82 out.addAll(m_matchedSourceEntries.keySet());
83 out.removeAll(m_unmatchedSourceEntries.keySet());
84 return out;
85 }
86
87 public Collection<T> getUnmatchedSourceEntries() {
88 return m_unmatchedSourceEntries.values();
89 }
90
91 public Collection<T> getUnmatchedSourceEntries(ClassEntry sourceClass) {
92 return m_unmatchedSourceEntries.get(sourceClass);
93 }
94
95 public Collection<T> getUnmatchedDestEntries() {
96 return m_unmatchedDestEntries.values();
97 }
98
99 public Collection<T> getUnmatchedDestEntries(ClassEntry destClass) {
100 return m_unmatchedDestEntries.get(destClass);
101 }
102
103 public Collection<T> getUnmatchableSourceEntries() {
104 return m_unmatchableSourceEntries.values();
105 }
106
107 public boolean hasSource(T sourceEntry) {
108 return m_matches.containsKey(sourceEntry) || m_unmatchedSourceEntries.containsValue(sourceEntry);
109 }
110
111 public boolean hasDest(T destEntry) {
112 return m_matches.containsValue(destEntry) || m_unmatchedDestEntries.containsValue(destEntry);
113 }
114
115 public BiMap<T,T> matches() {
116 return m_matches;
117 }
118
119 public boolean isMatchedSourceEntry(T sourceEntry) {
120 return m_matches.containsKey(sourceEntry);
121 }
122
123 public boolean isMatchedDestEntry(T destEntry) {
124 return m_matches.containsValue(destEntry);
125 }
126
127 public boolean isUnmatchableSourceEntry(T sourceEntry) {
128 return m_unmatchableSourceEntries.containsEntry(sourceEntry.getClassEntry(), sourceEntry);
129 }
130
131 public void makeMatch(T sourceEntry, T destEntry) {
132 boolean wasRemoved = m_unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
133 assert (wasRemoved);
134 wasRemoved = m_unmatchedDestEntries.remove(destEntry.getClassEntry(), destEntry);
135 assert (wasRemoved);
136 addMatch(sourceEntry, destEntry);
137 }
138
139 public boolean isMatched(T sourceEntry, T destEntry) {
140 T match = m_matches.get(sourceEntry);
141 return match != null && match.equals(destEntry);
142 }
143
144 public void unmakeMatch(T sourceEntry, T destEntry) {
145 boolean wasRemoved = m_matches.remove(sourceEntry) != null;
146 assert (wasRemoved);
147 wasRemoved = m_matchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
148 assert (wasRemoved);
149 addUnmatchedSourceEntry(sourceEntry);
150 addUnmatchedDestEntry(destEntry);
151 }
152
153 public void makeSourceUnmatchable(T sourceEntry) {
154 assert(!isMatchedSourceEntry(sourceEntry));
155 boolean wasRemoved = m_unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
156 assert (wasRemoved);
157 addUnmatchableSourceEntry(sourceEntry);
158 }
159}
diff --git a/src/cuchaz/enigma/gui/AboutDialog.java b/src/cuchaz/enigma/gui/AboutDialog.java
new file mode 100644
index 00000000..3eba1e50
--- /dev/null
+++ b/src/cuchaz/enigma/gui/AboutDialog.java
@@ -0,0 +1,86 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..e5e05571
--- /dev/null
+++ b/src/cuchaz/enigma/gui/BoxHighlightPainter.java
@@ -0,0 +1,64 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..6af4d248
--- /dev/null
+++ b/src/cuchaz/enigma/gui/BrowserCaret.java
@@ -0,0 +1,45 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..cde3e4ca
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassListCellRenderer.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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/ClassMatchingGui.java b/src/cuchaz/enigma/gui/ClassMatchingGui.java
new file mode 100644
index 00000000..89b19c3a
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassMatchingGui.java
@@ -0,0 +1,589 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.Dimension;
16import java.awt.FlowLayout;
17import java.awt.event.ActionEvent;
18import java.awt.event.ActionListener;
19import java.util.Collection;
20import java.util.List;
21import java.util.Map;
22
23import javax.swing.BoxLayout;
24import javax.swing.ButtonGroup;
25import javax.swing.JButton;
26import javax.swing.JCheckBox;
27import javax.swing.JFrame;
28import javax.swing.JLabel;
29import javax.swing.JPanel;
30import javax.swing.JRadioButton;
31import javax.swing.JScrollPane;
32import javax.swing.JSplitPane;
33import javax.swing.SwingConstants;
34import javax.swing.WindowConstants;
35
36import com.google.common.collect.BiMap;
37import com.google.common.collect.Lists;
38import com.google.common.collect.Maps;
39
40import cuchaz.enigma.Constants;
41import cuchaz.enigma.Deobfuscator;
42import cuchaz.enigma.convert.ClassIdentifier;
43import cuchaz.enigma.convert.ClassIdentity;
44import cuchaz.enigma.convert.ClassMatch;
45import cuchaz.enigma.convert.ClassMatches;
46import cuchaz.enigma.convert.ClassMatching;
47import cuchaz.enigma.convert.ClassNamer;
48import cuchaz.enigma.convert.MappingsConverter;
49import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
50import cuchaz.enigma.mapping.ClassEntry;
51import cuchaz.enigma.mapping.Mappings;
52import cuchaz.enigma.mapping.MappingsChecker;
53import de.sciss.syntaxpane.DefaultSyntaxKit;
54
55
56public class ClassMatchingGui {
57
58 private static enum SourceType {
59 Matched {
60
61 @Override
62 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
63 return matches.getUniqueMatches().keySet();
64 }
65 },
66 Unmatched {
67
68 @Override
69 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
70 return matches.getUnmatchedSourceClasses();
71 }
72 },
73 Ambiguous {
74
75 @Override
76 public Collection<ClassEntry> getSourceClasses(ClassMatches matches) {
77 return matches.getAmbiguouslyMatchedSourceClasses();
78 }
79 };
80
81 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
82 JRadioButton button = new JRadioButton(name(), this == getDefault());
83 button.setActionCommand(name());
84 button.addActionListener(listener);
85 group.add(button);
86 return button;
87 }
88
89 public abstract Collection<ClassEntry> getSourceClasses(ClassMatches matches);
90
91 public static SourceType getDefault() {
92 return values()[0];
93 }
94 }
95
96 public static interface SaveListener {
97 public void save(ClassMatches matches);
98 }
99
100 // controls
101 private JFrame m_frame;
102 private ClassSelector m_sourceClasses;
103 private ClassSelector m_destClasses;
104 private CodeReader m_sourceReader;
105 private CodeReader m_destReader;
106 private JLabel m_sourceClassLabel;
107 private JLabel m_destClassLabel;
108 private JButton m_matchButton;
109 private Map<SourceType,JRadioButton> m_sourceTypeButtons;
110 private JCheckBox m_advanceCheck;
111
112 private ClassMatches m_classMatches;
113 private Deobfuscator m_sourceDeobfuscator;
114 private Deobfuscator m_destDeobfuscator;
115 private ClassEntry m_sourceClass;
116 private ClassEntry m_destClass;
117 private SourceType m_sourceType;
118 private SaveListener m_saveListener;
119
120 public ClassMatchingGui(ClassMatches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
121
122 m_classMatches = matches;
123 m_sourceDeobfuscator = sourceDeobfuscator;
124 m_destDeobfuscator = destDeobfuscator;
125
126 // init frame
127 m_frame = new JFrame(Constants.Name + " - Class Matcher");
128 final Container pane = m_frame.getContentPane();
129 pane.setLayout(new BorderLayout());
130
131 // init source side
132 JPanel sourcePanel = new JPanel();
133 sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS));
134 sourcePanel.setPreferredSize(new Dimension(200, 0));
135 pane.add(sourcePanel, BorderLayout.WEST);
136 sourcePanel.add(new JLabel("Source Classes"));
137
138 // init source type radios
139 JPanel sourceTypePanel = new JPanel();
140 sourcePanel.add(sourceTypePanel);
141 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
142 ActionListener sourceTypeListener = new ActionListener() {
143 @Override
144 public void actionPerformed(ActionEvent event) {
145 setSourceType(SourceType.valueOf(event.getActionCommand()));
146 }
147 };
148 ButtonGroup sourceTypeButtons = new ButtonGroup();
149 m_sourceTypeButtons = Maps.newHashMap();
150 for (SourceType sourceType : SourceType.values()) {
151 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
152 m_sourceTypeButtons.put(sourceType, button);
153 sourceTypePanel.add(button);
154 }
155
156 m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
157 m_sourceClasses.setListener(new ClassSelectionListener() {
158 @Override
159 public void onSelectClass(ClassEntry classEntry) {
160 setSourceClass(classEntry);
161 }
162 });
163 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
164 sourcePanel.add(sourceScroller);
165
166 // init dest side
167 JPanel destPanel = new JPanel();
168 destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS));
169 destPanel.setPreferredSize(new Dimension(200, 0));
170 pane.add(destPanel, BorderLayout.WEST);
171 destPanel.add(new JLabel("Destination Classes"));
172
173 m_destClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
174 m_destClasses.setListener(new ClassSelectionListener() {
175 @Override
176 public void onSelectClass(ClassEntry classEntry) {
177 setDestClass(classEntry);
178 }
179 });
180 JScrollPane destScroller = new JScrollPane(m_destClasses);
181 destPanel.add(destScroller);
182
183 JButton autoMatchButton = new JButton("AutoMatch");
184 autoMatchButton.addActionListener(new ActionListener() {
185 @Override
186 public void actionPerformed(ActionEvent event) {
187 autoMatch();
188 }
189 });
190 destPanel.add(autoMatchButton);
191
192 // init source panels
193 DefaultSyntaxKit.initKit();
194 m_sourceReader = new CodeReader();
195 m_destReader = new CodeReader();
196
197 // init all the splits
198 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(m_sourceReader));
199 splitLeft.setResizeWeight(0); // let the right side take all the slack
200 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_destReader), destPanel);
201 splitRight.setResizeWeight(1); // let the left side take all the slack
202 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight);
203 splitCenter.setResizeWeight(0.5); // resize 50:50
204 pane.add(splitCenter, BorderLayout.CENTER);
205 splitCenter.resetToPreferredSizes();
206
207 // init bottom panel
208 JPanel bottomPanel = new JPanel();
209 bottomPanel.setLayout(new FlowLayout());
210
211 m_sourceClassLabel = new JLabel();
212 m_sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT);
213 m_destClassLabel = new JLabel();
214 m_destClassLabel.setHorizontalAlignment(SwingConstants.LEFT);
215
216 m_matchButton = new JButton();
217
218 m_advanceCheck = new JCheckBox("Advance to next likely match");
219 m_advanceCheck.addActionListener(new ActionListener() {
220 @Override
221 public void actionPerformed(ActionEvent event) {
222 if (m_advanceCheck.isSelected()) {
223 advance();
224 }
225 }
226 });
227
228 bottomPanel.add(m_sourceClassLabel);
229 bottomPanel.add(m_matchButton);
230 bottomPanel.add(m_destClassLabel);
231 bottomPanel.add(m_advanceCheck);
232 pane.add(bottomPanel, BorderLayout.SOUTH);
233
234 // show the frame
235 pane.doLayout();
236 m_frame.setSize(1024, 576);
237 m_frame.setMinimumSize(new Dimension(640, 480));
238 m_frame.setVisible(true);
239 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
240
241 // init state
242 updateDestMappings();
243 setSourceType(SourceType.getDefault());
244 updateMatchButton();
245 m_saveListener = null;
246 }
247
248 public void setSaveListener(SaveListener val) {
249 m_saveListener = val;
250 }
251
252 private void updateDestMappings() {
253
254 Mappings newMappings = MappingsConverter.newMappings(
255 m_classMatches,
256 m_sourceDeobfuscator.getMappings(),
257 m_sourceDeobfuscator,
258 m_destDeobfuscator
259 );
260
261 // look for dropped mappings
262 MappingsChecker checker = new MappingsChecker(m_destDeobfuscator.getJarIndex());
263 checker.dropBrokenMappings(newMappings);
264
265 // count them
266 int numDroppedFields = checker.getDroppedFieldMappings().size();
267 int numDroppedMethods = checker.getDroppedMethodMappings().size();
268 System.out.println(String.format(
269 "%d mappings from matched classes don't match the dest jar:\n\t%5d fields\n\t%5d methods",
270 numDroppedFields + numDroppedMethods,
271 numDroppedFields,
272 numDroppedMethods
273 ));
274
275 m_destDeobfuscator.setMappings(newMappings);
276 }
277
278 protected void setSourceType(SourceType val) {
279
280 // show the source classes
281 m_sourceType = val;
282 m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_classMatches), m_sourceDeobfuscator));
283
284 // update counts
285 for (SourceType sourceType : SourceType.values()) {
286 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
287 sourceType.name(),
288 sourceType.getSourceClasses(m_classMatches).size()
289 ));
290 }
291 }
292
293 private Collection<ClassEntry> deobfuscateClasses(Collection<ClassEntry> in, Deobfuscator deobfuscator) {
294 List<ClassEntry> out = Lists.newArrayList();
295 for (ClassEntry entry : in) {
296
297 ClassEntry deobf = deobfuscator.deobfuscateEntry(entry);
298
299 // make sure we preserve any scores
300 if (entry instanceof ScoredClassEntry) {
301 deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry)entry).getScore());
302 }
303
304 out.add(deobf);
305 }
306 return out;
307 }
308
309 protected void setSourceClass(ClassEntry classEntry) {
310
311 Runnable onGetDestClasses = null;
312 if (m_advanceCheck.isSelected()) {
313 onGetDestClasses = new Runnable() {
314 @Override
315 public void run() {
316 pickBestDestClass();
317 }
318 };
319 }
320
321 setSourceClass(classEntry, onGetDestClasses);
322 }
323
324 protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) {
325
326 // update the current source class
327 m_sourceClass = classEntry;
328 m_sourceClassLabel.setText(m_sourceClass != null ? m_sourceClass.getName() : "");
329
330 if (m_sourceClass != null) {
331
332 // show the dest class(es)
333 ClassMatch match = m_classMatches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass));
334 assert(match != null);
335 if (match.destClasses.isEmpty()) {
336
337 m_destClasses.setClasses(null);
338
339 // run in a separate thread to keep ui responsive
340 new Thread() {
341 @Override
342 public void run() {
343 m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator));
344 m_destClasses.expandAll();
345
346 if (onGetDestClasses != null) {
347 onGetDestClasses.run();
348 }
349 }
350 }.start();
351
352 } else {
353
354 m_destClasses.setClasses(deobfuscateClasses(match.destClasses, m_destDeobfuscator));
355 m_destClasses.expandAll();
356
357 if (onGetDestClasses != null) {
358 onGetDestClasses.run();
359 }
360 }
361 }
362
363 setDestClass(null);
364 m_sourceReader.decompileClass(m_sourceClass, m_sourceDeobfuscator, new Runnable() {
365 @Override
366 public void run() {
367 m_sourceReader.navigateToClassDeclaration(m_sourceClass);
368 }
369 });
370
371 updateMatchButton();
372 }
373
374 private Collection<ClassEntry> getLikelyMatches(ClassEntry sourceClass) {
375
376 ClassEntry obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
377
378 // set up identifiers
379 ClassNamer namer = new ClassNamer(m_classMatches.getUniqueMatches());
380 ClassIdentifier sourceIdentifier = new ClassIdentifier(
381 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
382 namer.getSourceNamer(), true
383 );
384 ClassIdentifier destIdentifier = new ClassIdentifier(
385 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
386 namer.getDestNamer(), true
387 );
388
389 try {
390
391 // rank all the unmatched dest classes against the source class
392 ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass);
393 List<ClassEntry> scoredDestClasses = Lists.newArrayList();
394 for (ClassEntry unmatchedDestClass : m_classMatches.getUnmatchedDestClasses()) {
395 ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass);
396 float score = 100.0f*(sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity))
397 /(sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore());
398 scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score));
399 }
400 return scoredDestClasses;
401
402 } catch (ClassNotFoundException ex) {
403 throw new Error("Unable to find class " + ex.getMessage());
404 }
405 }
406
407 protected void setDestClass(ClassEntry classEntry) {
408
409 // update the current source class
410 m_destClass = classEntry;
411 m_destClassLabel.setText(m_destClass != null ? m_destClass.getName() : "");
412
413 m_destReader.decompileClass(m_destClass, m_destDeobfuscator, new Runnable() {
414 @Override
415 public void run() {
416 m_destReader.navigateToClassDeclaration(m_destClass);
417 }
418 });
419
420 updateMatchButton();
421 }
422
423 private void updateMatchButton() {
424
425 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
426 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
427
428 BiMap<ClassEntry,ClassEntry> uniqueMatches = m_classMatches.getUniqueMatches();
429 boolean twoSelected = m_sourceClass != null && m_destClass != null;
430 boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest);
431 boolean canMatch = !uniqueMatches.containsKey(obfSource) && ! uniqueMatches.containsValue(obfDest);
432
433 GuiTricks.deactivateButton(m_matchButton);
434 if (twoSelected) {
435 if (isMatched) {
436 GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() {
437 @Override
438 public void actionPerformed(ActionEvent event) {
439 onUnmatchClick();
440 }
441 });
442 } else if (canMatch) {
443 GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() {
444 @Override
445 public void actionPerformed(ActionEvent event) {
446 onMatchClick();
447 }
448 });
449 }
450 }
451 }
452
453 private void onMatchClick() {
454 // precondition: source and dest classes are set correctly
455
456 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
457 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
458
459 // remove the classes from their match
460 m_classMatches.removeSource(obfSource);
461 m_classMatches.removeDest(obfDest);
462
463 // add them as matched classes
464 m_classMatches.add(new ClassMatch(obfSource, obfDest));
465
466 ClassEntry nextClass = null;
467 if (m_advanceCheck.isSelected()) {
468 nextClass = m_sourceClasses.getNextClass(m_sourceClass);
469 }
470
471 save();
472 updateMatches();
473
474 if (nextClass != null) {
475 advance(nextClass);
476 }
477 }
478
479 private void onUnmatchClick() {
480 // precondition: source and dest classes are set to a unique match
481
482 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
483
484 // remove the source to break the match, then add the source back as unmatched
485 m_classMatches.removeSource(obfSource);
486 m_classMatches.add(new ClassMatch(obfSource, null));
487
488 save();
489 updateMatches();
490 }
491
492 private void updateMatches() {
493 updateDestMappings();
494 setDestClass(null);
495 m_destClasses.setClasses(null);
496 updateMatchButton();
497
498 // remember where we were in the source tree
499 String packageName = m_sourceClasses.getSelectedPackage();
500
501 setSourceType(m_sourceType);
502
503 m_sourceClasses.expandPackage(packageName);
504 }
505
506 private void save() {
507 if (m_saveListener != null) {
508 m_saveListener.save(m_classMatches);
509 }
510 }
511
512 private void autoMatch() {
513
514 System.out.println("Automatching...");
515
516 // compute a new matching
517 ClassMatching matching = MappingsConverter.computeMatching(
518 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
519 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
520 m_classMatches.getUniqueMatches()
521 );
522 ClassMatches newMatches = new ClassMatches(matching.matches());
523 System.out.println(String.format("Automatch found %d new matches",
524 newMatches.getUniqueMatches().size() - m_classMatches.getUniqueMatches().size()
525 ));
526
527 // update the current matches
528 m_classMatches = newMatches;
529 save();
530 updateMatches();
531 }
532
533 private void advance() {
534 advance(null);
535 }
536
537 private void advance(ClassEntry sourceClass) {
538
539 // make sure we have a source class
540 if (sourceClass == null) {
541 sourceClass = m_sourceClasses.getSelectedClass();
542 if (sourceClass != null) {
543 sourceClass = m_sourceClasses.getNextClass(sourceClass);
544 } else {
545 sourceClass = m_sourceClasses.getFirstClass();
546 }
547 }
548
549 // set the source class
550 setSourceClass(sourceClass, new Runnable() {
551 @Override
552 public void run() {
553 pickBestDestClass();
554 }
555 });
556 m_sourceClasses.setSelectionClass(sourceClass);
557 }
558
559 private void pickBestDestClass() {
560
561 // then, pick the best dest class
562 ClassEntry firstClass = null;
563 ScoredClassEntry bestDestClass = null;
564 for (ClassSelectorPackageNode packageNode : m_destClasses.packageNodes()) {
565 for (ClassSelectorClassNode classNode : m_destClasses.classNodes(packageNode)) {
566 if (firstClass == null) {
567 firstClass = classNode.getClassEntry();
568 }
569 if (classNode.getClassEntry() instanceof ScoredClassEntry) {
570 ScoredClassEntry scoredClass = (ScoredClassEntry)classNode.getClassEntry();
571 if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) {
572 bestDestClass = scoredClass;
573 }
574 }
575 }
576 }
577
578 // pick the entry to show
579 ClassEntry destClass = null;
580 if (bestDestClass != null) {
581 destClass = bestDestClass;
582 } else if (firstClass != null) {
583 destClass = firstClass;
584 }
585
586 setDestClass(destClass);
587 m_destClasses.setSelectionClass(destClass);
588 }
589}
diff --git a/src/cuchaz/enigma/gui/ClassSelector.java b/src/cuchaz/enigma/gui/ClassSelector.java
new file mode 100644
index 00000000..11333a96
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassSelector.java
@@ -0,0 +1,293 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.event.MouseAdapter;
14import java.awt.event.MouseEvent;
15import java.util.Collection;
16import java.util.Collections;
17import java.util.Comparator;
18import java.util.Enumeration;
19import java.util.List;
20import java.util.Map;
21
22import javax.swing.JTree;
23import javax.swing.tree.DefaultMutableTreeNode;
24import javax.swing.tree.DefaultTreeModel;
25import javax.swing.tree.TreePath;
26
27import com.google.common.collect.ArrayListMultimap;
28import com.google.common.collect.Lists;
29import com.google.common.collect.Maps;
30import com.google.common.collect.Multimap;
31
32import cuchaz.enigma.mapping.ClassEntry;
33
34public class ClassSelector extends JTree {
35
36 private static final long serialVersionUID = -7632046902384775977L;
37
38 public interface ClassSelectionListener {
39 void onSelectClass(ClassEntry classEntry);
40 }
41
42 public static Comparator<ClassEntry> ObfuscatedClassEntryComparator;
43 public static Comparator<ClassEntry> DeobfuscatedClassEntryComparator;
44
45 static {
46 ObfuscatedClassEntryComparator = new Comparator<ClassEntry>() {
47 @Override
48 public int compare(ClassEntry a, ClassEntry b) {
49 String aname = a.getName();
50 String bname = a.getName();
51 if (aname.length() != bname.length()) {
52 return aname.length() - bname.length();
53 }
54 return aname.compareTo(bname);
55 }
56 };
57
58 DeobfuscatedClassEntryComparator = new Comparator<ClassEntry>() {
59 @Override
60 public int compare(ClassEntry a, ClassEntry b) {
61 if (a instanceof ScoredClassEntry && b instanceof ScoredClassEntry) {
62 return Float.compare(
63 ((ScoredClassEntry)b).getScore(),
64 ((ScoredClassEntry)a).getScore()
65 );
66 }
67 return a.getName().compareTo(b.getName());
68 }
69 };
70 }
71
72 private ClassSelectionListener m_listener;
73 private Comparator<ClassEntry> m_comparator;
74
75 public ClassSelector(Comparator<ClassEntry> comparator) {
76 m_comparator = comparator;
77
78 // configure the tree control
79 setRootVisible(false);
80 setShowsRootHandles(false);
81 setModel(null);
82
83 // hook events
84 addMouseListener(new MouseAdapter() {
85 @Override
86 public void mouseClicked(MouseEvent event) {
87 if (m_listener != null && event.getClickCount() == 2) {
88 // get the selected node
89 TreePath path = getSelectionPath();
90 if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) {
91 ClassSelectorClassNode node = (ClassSelectorClassNode)path.getLastPathComponent();
92 m_listener.onSelectClass(node.getClassEntry());
93 }
94 }
95 }
96 });
97
98 // init defaults
99 m_listener = null;
100 }
101
102 public void setListener(ClassSelectionListener val) {
103 m_listener = val;
104 }
105
106 public void setClasses(Collection<ClassEntry> classEntries) {
107 if (classEntries == null) {
108 setModel(null);
109 return;
110 }
111
112 // build the package names
113 Map<String,ClassSelectorPackageNode> packages = Maps.newHashMap();
114 for (ClassEntry classEntry : classEntries) {
115 packages.put(classEntry.getPackageName(), null);
116 }
117
118 // sort the packages
119 List<String> sortedPackageNames = Lists.newArrayList(packages.keySet());
120 Collections.sort(sortedPackageNames, new Comparator<String>() {
121 @Override
122 public int compare(String a, String b) {
123 // I can never keep this rule straight when writing these damn things...
124 // a < b => -1, a == b => 0, a > b => +1
125
126 String[] aparts = a.split("/");
127 String[] bparts = b.split("/");
128 for (int i = 0; true; i++) {
129 if (i >= aparts.length) {
130 return -1;
131 } else if (i >= bparts.length) {
132 return 1;
133 }
134
135 int result = aparts[i].compareTo(bparts[i]);
136 if (result != 0) {
137 return result;
138 }
139 }
140 }
141 });
142
143 // create the root node and the package nodes
144 DefaultMutableTreeNode root = new DefaultMutableTreeNode();
145 for (String packageName : sortedPackageNames) {
146 ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName);
147 packages.put(packageName, node);
148 root.add(node);
149 }
150
151 // put the classes into packages
152 Multimap<String,ClassEntry> packagedClassEntries = ArrayListMultimap.create();
153 for (ClassEntry classEntry : classEntries) {
154 packagedClassEntries.put(classEntry.getPackageName(), classEntry);
155 }
156
157 // build the class nodes
158 for (String packageName : packagedClassEntries.keySet()) {
159 // sort the class entries
160 List<ClassEntry> classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName));
161 Collections.sort(classEntriesInPackage, m_comparator);
162
163 // create the nodes in order
164 for (ClassEntry classEntry : classEntriesInPackage) {
165 ClassSelectorPackageNode node = packages.get(packageName);
166 node.add(new ClassSelectorClassNode(classEntry));
167 }
168 }
169
170 // finally, update the tree control
171 setModel(new DefaultTreeModel(root));
172 }
173
174 public ClassEntry getSelectedClass() {
175 if (!isSelectionEmpty()) {
176 Object selectedNode = getSelectionPath().getLastPathComponent();
177 if (selectedNode instanceof ClassSelectorClassNode) {
178 ClassSelectorClassNode classNode = (ClassSelectorClassNode)selectedNode;
179 return classNode.getClassEntry();
180 }
181 }
182 return null;
183 }
184
185 public String getSelectedPackage() {
186 if (!isSelectionEmpty()) {
187 Object selectedNode = getSelectionPath().getLastPathComponent();
188 if (selectedNode instanceof ClassSelectorPackageNode) {
189 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode)selectedNode;
190 return packageNode.getPackageName();
191 } else if (selectedNode instanceof ClassSelectorClassNode) {
192 ClassSelectorClassNode classNode = (ClassSelectorClassNode)selectedNode;
193 return classNode.getClassEntry().getPackageName();
194 }
195 }
196 return null;
197 }
198
199 public Iterable<ClassSelectorPackageNode> packageNodes() {
200 List<ClassSelectorPackageNode> nodes = Lists.newArrayList();
201 DefaultMutableTreeNode root = (DefaultMutableTreeNode)getModel().getRoot();
202 Enumeration<?> children = root.children();
203 while (children.hasMoreElements()) {
204 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode)children.nextElement();
205 nodes.add(packageNode);
206 }
207 return nodes;
208 }
209
210 public Iterable<ClassSelectorClassNode> classNodes(ClassSelectorPackageNode packageNode) {
211 List<ClassSelectorClassNode> nodes = Lists.newArrayList();
212 Enumeration<?> children = packageNode.children();
213 while (children.hasMoreElements()) {
214 ClassSelectorClassNode classNode = (ClassSelectorClassNode)children.nextElement();
215 nodes.add(classNode);
216 }
217 return nodes;
218 }
219
220 public void expandPackage(String packageName) {
221 if (packageName == null) {
222 return;
223 }
224 for (ClassSelectorPackageNode packageNode : packageNodes()) {
225 if (packageNode.getPackageName().equals(packageName)) {
226 expandPath(new TreePath(new Object[] {getModel().getRoot(), packageNode}));
227 return;
228 }
229 }
230 }
231
232 public void expandAll() {
233 for (ClassSelectorPackageNode packageNode : packageNodes()) {
234 expandPath(new TreePath(new Object[] {getModel().getRoot(), packageNode}));
235 }
236 }
237
238 public ClassEntry getFirstClass() {
239 for (ClassSelectorPackageNode packageNode : packageNodes()) {
240 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
241 return classNode.getClassEntry();
242 }
243 }
244 return null;
245 }
246
247 public ClassSelectorPackageNode getPackageNode(ClassEntry entry) {
248 for (ClassSelectorPackageNode packageNode : packageNodes()) {
249 if (packageNode.getPackageName().equals(entry.getPackageName())) {
250 return packageNode;
251 }
252 }
253 return null;
254 }
255
256 public ClassEntry getNextClass(ClassEntry entry) {
257 boolean foundIt = false;
258 for (ClassSelectorPackageNode packageNode : packageNodes()) {
259 if (!foundIt) {
260 // skip to the package with our target in it
261 if (packageNode.getPackageName().equals(entry.getPackageName())) {
262 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
263 if (!foundIt) {
264 if (classNode.getClassEntry().equals(entry)) {
265 foundIt = true;
266 }
267 } else {
268 // return the next class
269 return classNode.getClassEntry();
270 }
271 }
272 }
273 } else {
274 // return the next class
275 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
276 return classNode.getClassEntry();
277 }
278 }
279 }
280 return null;
281 }
282
283 public void setSelectionClass(ClassEntry classEntry) {
284 expandPackage(classEntry.getPackageName());
285 for (ClassSelectorPackageNode packageNode : packageNodes()) {
286 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
287 if (classNode.getClassEntry().equals(classEntry)) {
288 setSelectionPath(new TreePath(new Object[] {getModel().getRoot(), packageNode, classNode}));
289 }
290 }
291 }
292 }
293}
diff --git a/src/cuchaz/enigma/gui/ClassSelectorClassNode.java b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java
new file mode 100644
index 00000000..1219e890
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java
@@ -0,0 +1,50 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import javax.swing.tree.DefaultMutableTreeNode;
14
15import cuchaz.enigma.mapping.ClassEntry;
16
17public class ClassSelectorClassNode extends DefaultMutableTreeNode {
18
19 private static final long serialVersionUID = -8956754339813257380L;
20
21 private ClassEntry m_classEntry;
22
23 public ClassSelectorClassNode(ClassEntry classEntry) {
24 m_classEntry = classEntry;
25 }
26
27 public ClassEntry getClassEntry() {
28 return m_classEntry;
29 }
30
31 @Override
32 public String toString() {
33 if (m_classEntry instanceof ScoredClassEntry) {
34 return String.format("%d%% %s", (int)((ScoredClassEntry)m_classEntry).getScore(), m_classEntry.getSimpleName());
35 }
36 return m_classEntry.getSimpleName();
37 }
38
39 @Override
40 public boolean equals(Object other) {
41 if (other instanceof ClassSelectorClassNode) {
42 return equals((ClassSelectorClassNode)other);
43 }
44 return false;
45 }
46
47 public boolean equals(ClassSelectorClassNode other) {
48 return m_classEntry.equals(other.m_classEntry);
49 }
50}
diff --git a/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java
new file mode 100644
index 00000000..7259f54d
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java
@@ -0,0 +1,45 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import javax.swing.tree.DefaultMutableTreeNode;
14
15public class ClassSelectorPackageNode extends DefaultMutableTreeNode {
16
17 private static final long serialVersionUID = -3730868701219548043L;
18
19 private String m_packageName;
20
21 public ClassSelectorPackageNode(String packageName) {
22 m_packageName = packageName;
23 }
24
25 public String getPackageName() {
26 return m_packageName;
27 }
28
29 @Override
30 public String toString() {
31 return m_packageName;
32 }
33
34 @Override
35 public boolean equals(Object other) {
36 if (other instanceof ClassSelectorPackageNode) {
37 return equals((ClassSelectorPackageNode)other);
38 }
39 return false;
40 }
41
42 public boolean equals(ClassSelectorPackageNode other) {
43 return m_packageName.equals(other.m_packageName);
44 }
45}
diff --git a/src/cuchaz/enigma/gui/CodeReader.java b/src/cuchaz/enigma/gui/CodeReader.java
new file mode 100644
index 00000000..5033a2cd
--- /dev/null
+++ b/src/cuchaz/enigma/gui/CodeReader.java
@@ -0,0 +1,222 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Rectangle;
14import java.awt.event.ActionEvent;
15import java.awt.event.ActionListener;
16
17import javax.swing.JEditorPane;
18import javax.swing.SwingUtilities;
19import javax.swing.Timer;
20import javax.swing.event.CaretEvent;
21import javax.swing.event.CaretListener;
22import javax.swing.text.BadLocationException;
23import javax.swing.text.Highlighter.HighlightPainter;
24
25import com.strobel.decompiler.languages.java.ast.CompilationUnit;
26
27import cuchaz.enigma.Deobfuscator;
28import cuchaz.enigma.analysis.EntryReference;
29import cuchaz.enigma.analysis.SourceIndex;
30import cuchaz.enigma.analysis.Token;
31import cuchaz.enigma.mapping.ClassEntry;
32import cuchaz.enigma.mapping.Entry;
33import de.sciss.syntaxpane.DefaultSyntaxKit;
34
35
36public class CodeReader extends JEditorPane {
37
38 private static final long serialVersionUID = 3673180950485748810L;
39
40 private static final Object m_lock = new Object();
41
42 public static interface SelectionListener {
43 void onSelect(EntryReference<Entry,Entry> reference);
44 }
45
46 private SelectionHighlightPainter m_selectionHighlightPainter;
47 private SourceIndex m_sourceIndex;
48 private SelectionListener m_selectionListener;
49
50 public CodeReader() {
51
52 setEditable(false);
53 setContentType("text/java");
54
55 // turn off token highlighting (it's wrong most of the time anyway...)
56 DefaultSyntaxKit kit = (DefaultSyntaxKit)getEditorKit();
57 kit.toggleComponent(this, "de.sciss.syntaxpane.components.TokenMarker");
58
59 // hook events
60 addCaretListener(new CaretListener() {
61 @Override
62 public void caretUpdate(CaretEvent event) {
63 if (m_selectionListener != null && m_sourceIndex != null) {
64 Token token = m_sourceIndex.getReferenceToken(event.getDot());
65 if (token != null) {
66 m_selectionListener.onSelect(m_sourceIndex.getDeobfReference(token));
67 } else {
68 m_selectionListener.onSelect(null);
69 }
70 }
71 }
72 });
73
74 m_selectionHighlightPainter = new SelectionHighlightPainter();
75 m_sourceIndex = null;
76 m_selectionListener = null;
77 }
78
79 public void setSelectionListener(SelectionListener val) {
80 m_selectionListener = val;
81 }
82
83 public void setCode(String code) {
84 // sadly, the java lexer is not thread safe, so we have to serialize all these calls
85 synchronized (m_lock) {
86 setText(code);
87 }
88 }
89
90 public SourceIndex getSourceIndex() {
91 return m_sourceIndex;
92 }
93
94 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator) {
95 decompileClass(classEntry, deobfuscator, null);
96 }
97
98 public void decompileClass(ClassEntry classEntry, Deobfuscator deobfuscator, Runnable callback) {
99 decompileClass(classEntry, deobfuscator, null, callback);
100 }
101
102 public void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final Boolean ignoreBadTokens, final Runnable callback) {
103
104 if (classEntry == null) {
105 setCode(null);
106 return;
107 }
108
109 setCode("(decompiling...)");
110
111 // run decompilation in a separate thread to keep ui responsive
112 new Thread() {
113 @Override
114 public void run() {
115
116 // decompile it
117 CompilationUnit sourceTree = deobfuscator.getSourceTree(classEntry.getOutermostClassName());
118 String source = deobfuscator.getSource(sourceTree);
119 setCode(source);
120 m_sourceIndex = deobfuscator.getSourceIndex(sourceTree, source, ignoreBadTokens);
121
122 if (callback != null) {
123 callback.run();
124 }
125 }
126 }.start();
127 }
128
129 public void navigateToClassDeclaration(ClassEntry classEntry) {
130
131 // navigate to the class declaration
132 Token token = m_sourceIndex.getDeclarationToken(classEntry);
133 if (token == null) {
134 // couldn't find the class declaration token, might be an anonymous class
135 // look for any declaration in that class instead
136 for (Entry entry : m_sourceIndex.declarations()) {
137 if (entry.getClassEntry().equals(classEntry)) {
138 token = m_sourceIndex.getDeclarationToken(entry);
139 break;
140 }
141 }
142 }
143
144 if (token != null) {
145 navigateToToken(token);
146 } else {
147 // couldn't find anything =(
148 System.out.println("Unable to find declaration in source for " + classEntry);
149 }
150 }
151
152 public void navigateToToken(final Token token) {
153 navigateToToken(this, token, m_selectionHighlightPainter);
154 }
155
156 // HACKHACK: someday we can update the main GUI to use this code reader
157 public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) {
158
159 // set the caret position to the token
160 editor.setCaretPosition(token.start);
161 editor.grabFocus();
162
163 try {
164 // make sure the token is visible in the scroll window
165 Rectangle start = editor.modelToView(token.start);
166 Rectangle end = editor.modelToView(token.end);
167 final Rectangle show = start.union(end);
168 show.grow(start.width * 10, start.height * 6);
169 SwingUtilities.invokeLater(new Runnable() {
170 @Override
171 public void run() {
172 editor.scrollRectToVisible(show);
173 }
174 });
175 } catch (BadLocationException ex) {
176 throw new Error(ex);
177 }
178
179 // highlight the token momentarily
180 final Timer timer = new Timer(200, new ActionListener() {
181 private int m_counter = 0;
182 private Object m_highlight = null;
183
184 @Override
185 public void actionPerformed(ActionEvent event) {
186 if (m_counter % 2 == 0) {
187 try {
188 m_highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter);
189 } catch (BadLocationException ex) {
190 // don't care
191 }
192 } else if (m_highlight != null) {
193 editor.getHighlighter().removeHighlight(m_highlight);
194 }
195
196 if (m_counter++ > 6) {
197 Timer timer = (Timer)event.getSource();
198 timer.stop();
199 }
200 }
201 });
202 timer.start();
203 }
204
205 public void setHighlightedTokens(Iterable<Token> tokens, HighlightPainter painter) {
206 for (Token token : tokens) {
207 setHighlightedToken(token, painter);
208 }
209 }
210
211 public void setHighlightedToken(Token token, HighlightPainter painter) {
212 try {
213 getHighlighter().addHighlight(token.start, token.end, painter);
214 } catch (BadLocationException ex) {
215 throw new IllegalArgumentException(ex);
216 }
217 }
218
219 public void clearHighlights() {
220 getHighlighter().removeAllHighlights();
221 }
222}
diff --git a/src/cuchaz/enigma/gui/CrashDialog.java b/src/cuchaz/enigma/gui/CrashDialog.java
new file mode 100644
index 00000000..904273c1
--- /dev/null
+++ b/src/cuchaz/enigma/gui/CrashDialog.java
@@ -0,0 +1,101 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..57210a84
--- /dev/null
+++ b/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.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 00000000..f9192d31
--- /dev/null
+++ b/src/cuchaz/enigma/gui/Gui.java
@@ -0,0 +1,1122 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Color;
15import java.awt.Container;
16import java.awt.Dimension;
17import java.awt.FlowLayout;
18import java.awt.GridLayout;
19import java.awt.event.ActionEvent;
20import java.awt.event.ActionListener;
21import java.awt.event.InputEvent;
22import java.awt.event.KeyAdapter;
23import java.awt.event.KeyEvent;
24import java.awt.event.MouseAdapter;
25import java.awt.event.MouseEvent;
26import java.awt.event.WindowAdapter;
27import java.awt.event.WindowEvent;
28import java.io.File;
29import java.io.IOException;
30import java.lang.Thread.UncaughtExceptionHandler;
31import java.util.Collection;
32import java.util.Collections;
33import java.util.List;
34import java.util.Vector;
35import java.util.jar.JarFile;
36
37import javax.swing.BorderFactory;
38import javax.swing.JEditorPane;
39import javax.swing.JFileChooser;
40import javax.swing.JFrame;
41import javax.swing.JLabel;
42import javax.swing.JList;
43import javax.swing.JMenu;
44import javax.swing.JMenuBar;
45import javax.swing.JMenuItem;
46import javax.swing.JOptionPane;
47import javax.swing.JPanel;
48import javax.swing.JPopupMenu;
49import javax.swing.JScrollPane;
50import javax.swing.JSplitPane;
51import javax.swing.JTabbedPane;
52import javax.swing.JTextField;
53import javax.swing.JTree;
54import javax.swing.KeyStroke;
55import javax.swing.ListSelectionModel;
56import javax.swing.WindowConstants;
57import javax.swing.event.CaretEvent;
58import javax.swing.event.CaretListener;
59import javax.swing.text.BadLocationException;
60import javax.swing.text.Highlighter;
61import javax.swing.tree.DefaultTreeModel;
62import javax.swing.tree.TreeNode;
63import javax.swing.tree.TreePath;
64
65import com.google.common.collect.Lists;
66
67import cuchaz.enigma.Constants;
68import cuchaz.enigma.ExceptionIgnorer;
69import cuchaz.enigma.analysis.BehaviorReferenceTreeNode;
70import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
71import cuchaz.enigma.analysis.ClassInheritanceTreeNode;
72import cuchaz.enigma.analysis.EntryReference;
73import cuchaz.enigma.analysis.FieldReferenceTreeNode;
74import cuchaz.enigma.analysis.MethodImplementationsTreeNode;
75import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
76import cuchaz.enigma.analysis.ReferenceTreeNode;
77import cuchaz.enigma.analysis.Token;
78import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
79import cuchaz.enigma.mapping.ArgumentEntry;
80import cuchaz.enigma.mapping.ClassEntry;
81import cuchaz.enigma.mapping.ConstructorEntry;
82import cuchaz.enigma.mapping.Entry;
83import cuchaz.enigma.mapping.FieldEntry;
84import cuchaz.enigma.mapping.IllegalNameException;
85import cuchaz.enigma.mapping.MappingParseException;
86import cuchaz.enigma.mapping.MethodEntry;
87import cuchaz.enigma.mapping.Signature;
88import de.sciss.syntaxpane.DefaultSyntaxKit;
89
90public class Gui {
91
92 private GuiController m_controller;
93
94 // controls
95 private JFrame m_frame;
96 private ClassSelector m_obfClasses;
97 private ClassSelector m_deobfClasses;
98 private JEditorPane m_editor;
99 private JPanel m_classesPanel;
100 private JSplitPane m_splitClasses;
101 private JPanel m_infoPanel;
102 private ObfuscatedHighlightPainter m_obfuscatedHighlightPainter;
103 private DeobfuscatedHighlightPainter m_deobfuscatedHighlightPainter;
104 private OtherHighlightPainter m_otherHighlightPainter;
105 private SelectionHighlightPainter m_selectionHighlightPainter;
106 private JTree m_inheritanceTree;
107 private JTree m_implementationsTree;
108 private JTree m_callsTree;
109 private JList<Token> m_tokens;
110 private JTabbedPane m_tabs;
111
112 // dynamic menu items
113 private JMenuItem m_closeJarMenu;
114 private JMenuItem m_openMappingsMenu;
115 private JMenuItem m_saveMappingsMenu;
116 private JMenuItem m_saveMappingsAsMenu;
117 private JMenuItem m_closeMappingsMenu;
118 private JMenuItem m_renameMenu;
119 private JMenuItem m_showInheritanceMenu;
120 private JMenuItem m_openEntryMenu;
121 private JMenuItem m_openPreviousMenu;
122 private JMenuItem m_showCallsMenu;
123 private JMenuItem m_showImplementationsMenu;
124 private JMenuItem m_toggleMappingMenu;
125 private JMenuItem m_exportSourceMenu;
126 private JMenuItem m_exportJarMenu;
127
128 // state
129 private EntryReference<Entry,Entry> m_reference;
130 private JFileChooser m_jarFileChooser;
131 private JFileChooser m_mappingsFileChooser;
132 private JFileChooser m_exportSourceFileChooser;
133 private JFileChooser m_exportJarFileChooser;
134
135 public Gui() {
136
137 // init frame
138 m_frame = new JFrame(Constants.Name);
139 final Container pane = m_frame.getContentPane();
140 pane.setLayout(new BorderLayout());
141
142 if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) {
143 // install a global exception handler to the event thread
144 CrashDialog.init(m_frame);
145 Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
146 @Override
147 public void uncaughtException(Thread thread, Throwable t) {
148 t.printStackTrace(System.err);
149 if (!ExceptionIgnorer.shouldIgnore(t)) {
150 CrashDialog.show(t);
151 }
152 }
153 });
154 }
155
156 m_controller = new GuiController(this);
157
158 // init file choosers
159 m_jarFileChooser = new JFileChooser();
160 m_mappingsFileChooser = new JFileChooser();
161 m_exportSourceFileChooser = new JFileChooser();
162 m_exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
163 m_exportJarFileChooser = new JFileChooser();
164
165 // init obfuscated classes list
166 m_obfClasses = new ClassSelector(ClassSelector.ObfuscatedClassEntryComparator);
167 m_obfClasses.setListener(new ClassSelectionListener() {
168 @Override
169 public void onSelectClass(ClassEntry classEntry) {
170 navigateTo(classEntry);
171 }
172 });
173 JScrollPane obfScroller = new JScrollPane(m_obfClasses);
174 JPanel obfPanel = new JPanel();
175 obfPanel.setLayout(new BorderLayout());
176 obfPanel.add(new JLabel("Obfuscated Classes"), BorderLayout.NORTH);
177 obfPanel.add(obfScroller, BorderLayout.CENTER);
178
179 // init deobfuscated classes list
180 m_deobfClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
181 m_deobfClasses.setListener(new ClassSelectionListener() {
182 @Override
183 public void onSelectClass(ClassEntry classEntry) {
184 navigateTo(classEntry);
185 }
186 });
187 JScrollPane deobfScroller = new JScrollPane(m_deobfClasses);
188 JPanel deobfPanel = new JPanel();
189 deobfPanel.setLayout(new BorderLayout());
190 deobfPanel.add(new JLabel("De-obfuscated Classes"), BorderLayout.NORTH);
191 deobfPanel.add(deobfScroller, BorderLayout.CENTER);
192
193 // set up classes panel (don't add the splitter yet)
194 m_splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, obfPanel, deobfPanel);
195 m_splitClasses.setResizeWeight(0.3);
196 m_classesPanel = new JPanel();
197 m_classesPanel.setLayout(new BorderLayout());
198 m_classesPanel.setPreferredSize(new Dimension(250, 0));
199
200 // init info panel
201 m_infoPanel = new JPanel();
202 m_infoPanel.setLayout(new GridLayout(4, 1, 0, 0));
203 m_infoPanel.setPreferredSize(new Dimension(0, 100));
204 m_infoPanel.setBorder(BorderFactory.createTitledBorder("Identifier Info"));
205 clearReference();
206
207 // init editor
208 DefaultSyntaxKit.initKit();
209 m_obfuscatedHighlightPainter = new ObfuscatedHighlightPainter();
210 m_deobfuscatedHighlightPainter = new DeobfuscatedHighlightPainter();
211 m_otherHighlightPainter = new OtherHighlightPainter();
212 m_selectionHighlightPainter = new SelectionHighlightPainter();
213 m_editor = new JEditorPane();
214 m_editor.setEditable(false);
215 m_editor.setCaret(new BrowserCaret());
216 JScrollPane sourceScroller = new JScrollPane(m_editor);
217 m_editor.setContentType("text/java");
218 m_editor.addCaretListener(new CaretListener() {
219 @Override
220 public void caretUpdate(CaretEvent event) {
221 onCaretMove(event.getDot());
222 }
223 });
224 m_editor.addKeyListener(new KeyAdapter() {
225 @Override
226 public void keyPressed(KeyEvent event) {
227 switch (event.getKeyCode()) {
228 case KeyEvent.VK_R:
229 m_renameMenu.doClick();
230 break;
231
232 case KeyEvent.VK_I:
233 m_showInheritanceMenu.doClick();
234 break;
235
236 case KeyEvent.VK_M:
237 m_showImplementationsMenu.doClick();
238 break;
239
240 case KeyEvent.VK_N:
241 m_openEntryMenu.doClick();
242 break;
243
244 case KeyEvent.VK_P:
245 m_openPreviousMenu.doClick();
246 break;
247
248 case KeyEvent.VK_C:
249 m_showCallsMenu.doClick();
250 break;
251
252 case KeyEvent.VK_T:
253 m_toggleMappingMenu.doClick();
254 break;
255 }
256 }
257 });
258
259 // turn off token highlighting (it's wrong most of the time anyway...)
260 DefaultSyntaxKit kit = (DefaultSyntaxKit)m_editor.getEditorKit();
261 kit.toggleComponent(m_editor, "de.sciss.syntaxpane.components.TokenMarker");
262
263 // init editor popup menu
264 JPopupMenu popupMenu = new JPopupMenu();
265 m_editor.setComponentPopupMenu(popupMenu);
266 {
267 JMenuItem menu = new JMenuItem("Rename");
268 menu.addActionListener(new ActionListener() {
269 @Override
270 public void actionPerformed(ActionEvent event) {
271 startRename();
272 }
273 });
274 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0));
275 menu.setEnabled(false);
276 popupMenu.add(menu);
277 m_renameMenu = menu;
278 }
279 {
280 JMenuItem menu = new JMenuItem("Show Inheritance");
281 menu.addActionListener(new ActionListener() {
282 @Override
283 public void actionPerformed(ActionEvent event) {
284 showInheritance();
285 }
286 });
287 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, 0));
288 menu.setEnabled(false);
289 popupMenu.add(menu);
290 m_showInheritanceMenu = menu;
291 }
292 {
293 JMenuItem menu = new JMenuItem("Show Implementations");
294 menu.addActionListener(new ActionListener() {
295 @Override
296 public void actionPerformed(ActionEvent event) {
297 showImplementations();
298 }
299 });
300 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0));
301 menu.setEnabled(false);
302 popupMenu.add(menu);
303 m_showImplementationsMenu = menu;
304 }
305 {
306 JMenuItem menu = new JMenuItem("Show Calls");
307 menu.addActionListener(new ActionListener() {
308 @Override
309 public void actionPerformed(ActionEvent event) {
310 showCalls();
311 }
312 });
313 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0));
314 menu.setEnabled(false);
315 popupMenu.add(menu);
316 m_showCallsMenu = menu;
317 }
318 {
319 JMenuItem menu = new JMenuItem("Go to Declaration");
320 menu.addActionListener(new ActionListener() {
321 @Override
322 public void actionPerformed(ActionEvent event) {
323 navigateTo(m_reference.entry);
324 }
325 });
326 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0));
327 menu.setEnabled(false);
328 popupMenu.add(menu);
329 m_openEntryMenu = menu;
330 }
331 {
332 JMenuItem menu = new JMenuItem("Go to previous");
333 menu.addActionListener(new ActionListener() {
334 @Override
335 public void actionPerformed(ActionEvent event) {
336 m_controller.openPreviousReference();
337 }
338 });
339 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0));
340 menu.setEnabled(false);
341 popupMenu.add(menu);
342 m_openPreviousMenu = menu;
343 }
344 {
345 JMenuItem menu = new JMenuItem("Mark as deobfuscated");
346 menu.addActionListener(new ActionListener() {
347 @Override
348 public void actionPerformed(ActionEvent event) {
349 toggleMapping();
350 }
351 });
352 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0));
353 menu.setEnabled(false);
354 popupMenu.add(menu);
355 m_toggleMappingMenu = menu;
356 }
357
358 // init inheritance panel
359 m_inheritanceTree = new JTree();
360 m_inheritanceTree.setModel(null);
361 m_inheritanceTree.addMouseListener(new MouseAdapter() {
362 @Override
363 public void mouseClicked(MouseEvent event) {
364 if (event.getClickCount() == 2) {
365 // get the selected node
366 TreePath path = m_inheritanceTree.getSelectionPath();
367 if (path == null) {
368 return;
369 }
370
371 Object node = path.getLastPathComponent();
372 if (node instanceof ClassInheritanceTreeNode) {
373 ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode)node;
374 navigateTo(new ClassEntry(classNode.getObfClassName()));
375 } else if (node instanceof MethodInheritanceTreeNode) {
376 MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode)node;
377 if (methodNode.isImplemented()) {
378 navigateTo(methodNode.getMethodEntry());
379 }
380 }
381 }
382 }
383 });
384 JPanel inheritancePanel = new JPanel();
385 inheritancePanel.setLayout(new BorderLayout());
386 inheritancePanel.add(new JScrollPane(m_inheritanceTree));
387
388 // init implementations panel
389 m_implementationsTree = new JTree();
390 m_implementationsTree.setModel(null);
391 m_implementationsTree.addMouseListener(new MouseAdapter() {
392 @Override
393 public void mouseClicked(MouseEvent event) {
394 if (event.getClickCount() == 2) {
395 // get the selected node
396 TreePath path = m_implementationsTree.getSelectionPath();
397 if (path == null) {
398 return;
399 }
400
401 Object node = path.getLastPathComponent();
402 if (node instanceof ClassImplementationsTreeNode) {
403 ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode)node;
404 navigateTo(classNode.getClassEntry());
405 } else if (node instanceof MethodImplementationsTreeNode) {
406 MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode)node;
407 navigateTo(methodNode.getMethodEntry());
408 }
409 }
410 }
411 });
412 JPanel implementationsPanel = new JPanel();
413 implementationsPanel.setLayout(new BorderLayout());
414 implementationsPanel.add(new JScrollPane(m_implementationsTree));
415
416 // init call panel
417 m_callsTree = new JTree();
418 m_callsTree.setModel(null);
419 m_callsTree.addMouseListener(new MouseAdapter() {
420 @SuppressWarnings("unchecked")
421 @Override
422 public void mouseClicked(MouseEvent event) {
423 if (event.getClickCount() == 2) {
424 // get the selected node
425 TreePath path = m_callsTree.getSelectionPath();
426 if (path == null) {
427 return;
428 }
429
430 Object node = path.getLastPathComponent();
431 if (node instanceof ReferenceTreeNode) {
432 ReferenceTreeNode<Entry,Entry> referenceNode = ((ReferenceTreeNode<Entry,Entry>)node);
433 if (referenceNode.getReference() != null) {
434 navigateTo(referenceNode.getReference());
435 } else {
436 navigateTo(referenceNode.getEntry());
437 }
438 }
439 }
440 }
441 });
442 m_tokens = new JList<Token>();
443 m_tokens.setCellRenderer(new TokenListCellRenderer(m_controller));
444 m_tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
445 m_tokens.setLayoutOrientation(JList.VERTICAL);
446 m_tokens.addMouseListener(new MouseAdapter() {
447 @Override
448 public void mouseClicked(MouseEvent event) {
449 if (event.getClickCount() == 2) {
450 Token selected = m_tokens.getSelectedValue();
451 if (selected != null) {
452 showToken(selected);
453 }
454 }
455 }
456 });
457 m_tokens.setPreferredSize(new Dimension(0, 200));
458 m_tokens.setMinimumSize(new Dimension(0, 200));
459 JSplitPane callPanel = new JSplitPane(
460 JSplitPane.VERTICAL_SPLIT,
461 true,
462 new JScrollPane(m_callsTree),
463 new JScrollPane(m_tokens)
464 );
465 callPanel.setResizeWeight(1); // let the top side take all the slack
466 callPanel.resetToPreferredSizes();
467
468 // layout controls
469 JPanel centerPanel = new JPanel();
470 centerPanel.setLayout(new BorderLayout());
471 centerPanel.add(m_infoPanel, BorderLayout.NORTH);
472 centerPanel.add(sourceScroller, BorderLayout.CENTER);
473 m_tabs = new JTabbedPane();
474 m_tabs.setPreferredSize(new Dimension(250, 0));
475 m_tabs.addTab("Inheritance", inheritancePanel);
476 m_tabs.addTab("Implementations", implementationsPanel);
477 m_tabs.addTab("Call Graph", callPanel);
478 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, m_tabs);
479 splitRight.setResizeWeight(1); // let the left side take all the slack
480 splitRight.resetToPreferredSizes();
481 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, m_classesPanel, splitRight);
482 splitCenter.setResizeWeight(0); // let the right side take all the slack
483 pane.add(splitCenter, BorderLayout.CENTER);
484
485 // init menus
486 JMenuBar menuBar = new JMenuBar();
487 m_frame.setJMenuBar(menuBar);
488 {
489 JMenu menu = new JMenu("File");
490 menuBar.add(menu);
491 {
492 JMenuItem item = new JMenuItem("Open Jar...");
493 menu.add(item);
494 item.addActionListener(new ActionListener() {
495 @Override
496 public void actionPerformed(ActionEvent event) {
497 if (m_jarFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
498 // load the jar in a separate thread
499 new Thread() {
500 @Override
501 public void run() {
502 try {
503 m_controller.openJar(new JarFile(m_jarFileChooser.getSelectedFile()));
504 } catch (IOException ex) {
505 throw new Error(ex);
506 }
507 }
508 }.start();
509 }
510 }
511 });
512 }
513 {
514 JMenuItem item = new JMenuItem("Close Jar");
515 menu.add(item);
516 item.addActionListener(new ActionListener() {
517 @Override
518 public void actionPerformed(ActionEvent event) {
519 m_controller.closeJar();
520 }
521 });
522 m_closeJarMenu = item;
523 }
524 menu.addSeparator();
525 {
526 JMenuItem item = new JMenuItem("Open Mappings...");
527 menu.add(item);
528 item.addActionListener(new ActionListener() {
529 @Override
530 public void actionPerformed(ActionEvent event) {
531 if (m_mappingsFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
532 try {
533 m_controller.openMappings(m_mappingsFileChooser.getSelectedFile());
534 } catch (IOException ex) {
535 throw new Error(ex);
536 } catch (MappingParseException ex) {
537 JOptionPane.showMessageDialog(m_frame, ex.getMessage());
538 }
539 }
540 }
541 });
542 m_openMappingsMenu = item;
543 }
544 {
545 JMenuItem item = new JMenuItem("Save Mappings");
546 menu.add(item);
547 item.addActionListener(new ActionListener() {
548 @Override
549 public void actionPerformed(ActionEvent event) {
550 try {
551 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
552 } catch (IOException ex) {
553 throw new Error(ex);
554 }
555 }
556 });
557 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK));
558 m_saveMappingsMenu = item;
559 }
560 {
561 JMenuItem item = new JMenuItem("Save Mappings As...");
562 menu.add(item);
563 item.addActionListener(new ActionListener() {
564 @Override
565 public void actionPerformed(ActionEvent event) {
566 if (m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
567 try {
568 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
569 m_saveMappingsMenu.setEnabled(true);
570 } catch (IOException ex) {
571 throw new Error(ex);
572 }
573 }
574 }
575 });
576 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
577 m_saveMappingsAsMenu = item;
578 }
579 {
580 JMenuItem item = new JMenuItem("Close Mappings");
581 menu.add(item);
582 item.addActionListener(new ActionListener() {
583 @Override
584 public void actionPerformed(ActionEvent event) {
585 m_controller.closeMappings();
586 }
587 });
588 m_closeMappingsMenu = item;
589 }
590 menu.addSeparator();
591 {
592 JMenuItem item = new JMenuItem("Export Source...");
593 menu.add(item);
594 item.addActionListener(new ActionListener() {
595 @Override
596 public void actionPerformed(ActionEvent event) {
597 if (m_exportSourceFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
598 m_controller.exportSource(m_exportSourceFileChooser.getSelectedFile());
599 }
600 }
601 });
602 m_exportSourceMenu = item;
603 }
604 {
605 JMenuItem item = new JMenuItem("Export Jar...");
606 menu.add(item);
607 item.addActionListener(new ActionListener() {
608 @Override
609 public void actionPerformed(ActionEvent event) {
610 if (m_exportJarFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
611 m_controller.exportJar(m_exportJarFileChooser.getSelectedFile());
612 }
613 }
614 });
615 m_exportJarMenu = item;
616 }
617 menu.addSeparator();
618 {
619 JMenuItem item = new JMenuItem("Exit");
620 menu.add(item);
621 item.addActionListener(new ActionListener() {
622 @Override
623 public void actionPerformed(ActionEvent event) {
624 close();
625 }
626 });
627 }
628 }
629 {
630 JMenu menu = new JMenu("Help");
631 menuBar.add(menu);
632 {
633 JMenuItem item = new JMenuItem("About");
634 menu.add(item);
635 item.addActionListener(new ActionListener() {
636 @Override
637 public void actionPerformed(ActionEvent event) {
638 AboutDialog.show(m_frame);
639 }
640 });
641 }
642 }
643
644 // init state
645 onCloseJar();
646
647 m_frame.addWindowListener(new WindowAdapter() {
648 @Override
649 public void windowClosing(WindowEvent event) {
650 close();
651 }
652 });
653
654 // show the frame
655 pane.doLayout();
656 m_frame.setSize(1024, 576);
657 m_frame.setMinimumSize(new Dimension(640, 480));
658 m_frame.setVisible(true);
659 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
660 }
661
662 public JFrame getFrame() {
663 return m_frame;
664 }
665
666 public GuiController getController() {
667 return m_controller;
668 }
669
670 public void onStartOpenJar() {
671 m_classesPanel.removeAll();
672 JPanel panel = new JPanel();
673 panel.setLayout(new FlowLayout());
674 panel.add(new JLabel("Loading..."));
675 m_classesPanel.add(panel);
676 redraw();
677 }
678
679 public void onFinishOpenJar(String jarName) {
680 // update gui
681 m_frame.setTitle(Constants.Name + " - " + jarName);
682 m_classesPanel.removeAll();
683 m_classesPanel.add(m_splitClasses);
684 setSource(null);
685
686 // update menu
687 m_closeJarMenu.setEnabled(true);
688 m_openMappingsMenu.setEnabled(true);
689 m_saveMappingsMenu.setEnabled(false);
690 m_saveMappingsAsMenu.setEnabled(true);
691 m_closeMappingsMenu.setEnabled(true);
692 m_exportSourceMenu.setEnabled(true);
693 m_exportJarMenu.setEnabled(true);
694
695 redraw();
696 }
697
698 public void onCloseJar() {
699 // update gui
700 m_frame.setTitle(Constants.Name);
701 setObfClasses(null);
702 setDeobfClasses(null);
703 setSource(null);
704 m_classesPanel.removeAll();
705
706 // update menu
707 m_closeJarMenu.setEnabled(false);
708 m_openMappingsMenu.setEnabled(false);
709 m_saveMappingsMenu.setEnabled(false);
710 m_saveMappingsAsMenu.setEnabled(false);
711 m_closeMappingsMenu.setEnabled(false);
712 m_exportSourceMenu.setEnabled(false);
713 m_exportJarMenu.setEnabled(false);
714
715 redraw();
716 }
717
718 public void setObfClasses(Collection<ClassEntry> obfClasses) {
719 m_obfClasses.setClasses(obfClasses);
720 }
721
722 public void setDeobfClasses(Collection<ClassEntry> deobfClasses) {
723 m_deobfClasses.setClasses(deobfClasses);
724 }
725
726 public void setMappingsFile(File file) {
727 m_mappingsFileChooser.setSelectedFile(file);
728 m_saveMappingsMenu.setEnabled(file != null);
729 }
730
731 public void setSource(String source) {
732 m_editor.getHighlighter().removeAllHighlights();
733 m_editor.setText(source);
734 }
735
736 public void showToken(final Token token) {
737 if (token == null) {
738 throw new IllegalArgumentException("Token cannot be null!");
739 }
740 CodeReader.navigateToToken(m_editor, token, m_selectionHighlightPainter);
741 redraw();
742 }
743
744 public void showTokens(Collection<Token> tokens) {
745 Vector<Token> sortedTokens = new Vector<Token>(tokens);
746 Collections.sort(sortedTokens);
747 if (sortedTokens.size() > 1) {
748 // sort the tokens and update the tokens panel
749 m_tokens.setListData(sortedTokens);
750 m_tokens.setSelectedIndex(0);
751 } else {
752 m_tokens.setListData(new Vector<Token>());
753 }
754
755 // show the first token
756 showToken(sortedTokens.get(0));
757 }
758
759 public void setHighlightedTokens(Iterable<Token> obfuscatedTokens, Iterable<Token> deobfuscatedTokens, Iterable<Token> otherTokens) {
760
761 // remove any old highlighters
762 m_editor.getHighlighter().removeAllHighlights();
763
764 // color things based on the index
765 if (obfuscatedTokens != null) {
766 setHighlightedTokens(obfuscatedTokens, m_obfuscatedHighlightPainter);
767 }
768 if (deobfuscatedTokens != null) {
769 setHighlightedTokens(deobfuscatedTokens, m_deobfuscatedHighlightPainter);
770 }
771 if (otherTokens != null) {
772 setHighlightedTokens(otherTokens, m_otherHighlightPainter);
773 }
774
775 redraw();
776 }
777
778 private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) {
779 for (Token token : tokens) {
780 try {
781 m_editor.getHighlighter().addHighlight(token.start, token.end, painter);
782 } catch (BadLocationException ex) {
783 throw new IllegalArgumentException(ex);
784 }
785 }
786 }
787
788 private void clearReference() {
789 m_infoPanel.removeAll();
790 JLabel label = new JLabel("No identifier selected");
791 GuiTricks.unboldLabel(label);
792 label.setHorizontalAlignment(JLabel.CENTER);
793 m_infoPanel.add(label);
794
795 redraw();
796 }
797
798 private void showReference(EntryReference<Entry,Entry> reference) {
799 if (reference == null) {
800 clearReference();
801 return;
802 }
803
804 m_reference = reference;
805
806 m_infoPanel.removeAll();
807 if (reference.entry instanceof ClassEntry) {
808 showClassEntry((ClassEntry)m_reference.entry);
809 } else if (m_reference.entry instanceof FieldEntry) {
810 showFieldEntry((FieldEntry)m_reference.entry);
811 } else if (m_reference.entry instanceof MethodEntry) {
812 showMethodEntry((MethodEntry)m_reference.entry);
813 } else if (m_reference.entry instanceof ConstructorEntry) {
814 showConstructorEntry((ConstructorEntry)m_reference.entry);
815 } else if (m_reference.entry instanceof ArgumentEntry) {
816 showArgumentEntry((ArgumentEntry)m_reference.entry);
817 } else {
818 throw new Error("Unknown entry type: " + m_reference.entry.getClass().getName());
819 }
820
821 redraw();
822 }
823
824 private void showClassEntry(ClassEntry entry) {
825 addNameValue(m_infoPanel, "Class", entry.getName());
826 }
827
828 private void showFieldEntry(FieldEntry entry) {
829 addNameValue(m_infoPanel, "Field", entry.getName());
830 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
831 addNameValue(m_infoPanel, "Type", entry.getType().toString());
832 }
833
834 private void showMethodEntry(MethodEntry entry) {
835 addNameValue(m_infoPanel, "Method", entry.getName());
836 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
837 addNameValue(m_infoPanel, "Signature", entry.getSignature().toString());
838 }
839
840 private void showConstructorEntry(ConstructorEntry entry) {
841 addNameValue(m_infoPanel, "Constructor", entry.getClassEntry().getName());
842 if (!entry.isStatic()) {
843 addNameValue(m_infoPanel, "Signature", entry.getSignature().toString());
844 }
845 }
846
847 private void showArgumentEntry(ArgumentEntry entry) {
848 addNameValue(m_infoPanel, "Argument", entry.getName());
849 addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName());
850 addNameValue(m_infoPanel, "Method", entry.getBehaviorEntry().getName());
851 addNameValue(m_infoPanel, "Index", Integer.toString(entry.getIndex()));
852 }
853
854 private void addNameValue(JPanel container, String name, String value) {
855 JPanel panel = new JPanel();
856 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
857 container.add(panel);
858
859 JLabel label = new JLabel(name + ":", JLabel.RIGHT);
860 label.setPreferredSize(new Dimension(100, label.getPreferredSize().height));
861 panel.add(label);
862
863 panel.add(GuiTricks.unboldLabel(new JLabel(value, JLabel.LEFT)));
864 }
865
866 private void onCaretMove(int pos) {
867
868 Token token = m_controller.getToken(pos);
869 boolean isToken = token != null;
870
871 m_reference = m_controller.getDeobfReference(token);
872 boolean isClassEntry = isToken && m_reference.entry instanceof ClassEntry;
873 boolean isFieldEntry = isToken && m_reference.entry instanceof FieldEntry;
874 boolean isMethodEntry = isToken && m_reference.entry instanceof MethodEntry;
875 boolean isConstructorEntry = isToken && m_reference.entry instanceof ConstructorEntry;
876 boolean isInJar = isToken && m_controller.entryIsInJar(m_reference.entry);
877 boolean isRenameable = isToken && m_controller.referenceIsRenameable(m_reference);
878
879 if (isToken) {
880 showReference(m_reference);
881 } else {
882 clearReference();
883 }
884
885 m_renameMenu.setEnabled(isRenameable && isToken);
886 m_showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry);
887 m_showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry);
888 m_showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry);
889 m_openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry));
890 m_openPreviousMenu.setEnabled(m_controller.hasPreviousLocation());
891 m_toggleMappingMenu.setEnabled(isRenameable && isToken);
892
893 if (isToken && m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
894 m_toggleMappingMenu.setText("Reset to obfuscated");
895 } else {
896 m_toggleMappingMenu.setText("Mark as deobfuscated");
897 }
898 }
899
900 private void navigateTo(Entry entry) {
901 if (!m_controller.entryIsInJar(entry)) {
902 // entry is not in the jar. Ignore it
903 return;
904 }
905 if (m_reference != null) {
906 m_controller.savePreviousReference(m_reference);
907 }
908 m_controller.openDeclaration(entry);
909 }
910
911 private void navigateTo(EntryReference<Entry,Entry> reference) {
912 if (!m_controller.entryIsInJar(reference.getLocationClassEntry())) {
913 // reference is not in the jar. Ignore it
914 return;
915 }
916 if (m_reference != null) {
917 m_controller.savePreviousReference(m_reference);
918 }
919 m_controller.openReference(reference);
920 }
921
922 private void startRename() {
923
924 // init the text box
925 final JTextField text = new JTextField();
926 text.setText(m_reference.getNamableName());
927 text.setPreferredSize(new Dimension(360, text.getPreferredSize().height));
928 text.addKeyListener(new KeyAdapter() {
929 @Override
930 public void keyPressed(KeyEvent event) {
931 switch (event.getKeyCode()) {
932 case KeyEvent.VK_ENTER:
933 finishRename(text, true);
934 break;
935
936 case KeyEvent.VK_ESCAPE:
937 finishRename(text, false);
938 break;
939 }
940 }
941 });
942
943 // find the label with the name and replace it with the text box
944 JPanel panel = (JPanel)m_infoPanel.getComponent(0);
945 panel.remove(panel.getComponentCount() - 1);
946 panel.add(text);
947 text.grabFocus();
948 text.selectAll();
949
950 redraw();
951 }
952
953 private void finishRename(JTextField text, boolean saveName) {
954 String newName = text.getText();
955 if (saveName && newName != null && newName.length() > 0) {
956 try {
957 m_controller.rename(m_reference, newName);
958 } catch (IllegalNameException ex) {
959 text.setBorder(BorderFactory.createLineBorder(Color.red, 1));
960 text.setToolTipText(ex.getReason());
961 GuiTricks.showToolTipNow(text);
962 }
963 return;
964 }
965
966 // abort the rename
967 JPanel panel = (JPanel)m_infoPanel.getComponent(0);
968 panel.remove(panel.getComponentCount() - 1);
969 panel.add(GuiTricks.unboldLabel(new JLabel(m_reference.getNamableName(), JLabel.LEFT)));
970
971 m_editor.grabFocus();
972
973 redraw();
974 }
975
976 private void showInheritance() {
977
978 if (m_reference == null) {
979 return;
980 }
981
982 m_inheritanceTree.setModel(null);
983
984 if (m_reference.entry instanceof ClassEntry) {
985 // get the class inheritance
986 ClassInheritanceTreeNode classNode = m_controller.getClassInheritance((ClassEntry)m_reference.entry);
987
988 // show the tree at the root
989 TreePath path = getPathToRoot(classNode);
990 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
991 m_inheritanceTree.expandPath(path);
992 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
993 } else if (m_reference.entry instanceof MethodEntry) {
994 // get the method inheritance
995 MethodInheritanceTreeNode classNode = m_controller.getMethodInheritance((MethodEntry)m_reference.entry);
996
997 // show the tree at the root
998 TreePath path = getPathToRoot(classNode);
999 m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1000 m_inheritanceTree.expandPath(path);
1001 m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path));
1002 }
1003
1004 m_tabs.setSelectedIndex(0);
1005 redraw();
1006 }
1007
1008 private void showImplementations() {
1009
1010 if (m_reference == null) {
1011 return;
1012 }
1013
1014 m_implementationsTree.setModel(null);
1015
1016 if (m_reference.entry instanceof ClassEntry) {
1017 // get the class implementations
1018 ClassImplementationsTreeNode node = m_controller.getClassImplementations((ClassEntry)m_reference.entry);
1019 if (node != null) {
1020 // show the tree at the root
1021 TreePath path = getPathToRoot(node);
1022 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1023 m_implementationsTree.expandPath(path);
1024 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1025 }
1026 } else if (m_reference.entry instanceof MethodEntry) {
1027 // get the method implementations
1028 MethodImplementationsTreeNode node = m_controller.getMethodImplementations((MethodEntry)m_reference.entry);
1029 if (node != null) {
1030 // show the tree at the root
1031 TreePath path = getPathToRoot(node);
1032 m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0)));
1033 m_implementationsTree.expandPath(path);
1034 m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path));
1035 }
1036 }
1037
1038 m_tabs.setSelectedIndex(1);
1039 redraw();
1040 }
1041
1042 private void showCalls() {
1043
1044 if (m_reference == null) {
1045 return;
1046 }
1047
1048 if (m_reference.entry instanceof ClassEntry) {
1049 // look for calls to the default constructor
1050 // TODO: get a list of all the constructors and find calls to all of them
1051 BehaviorReferenceTreeNode node = m_controller.getMethodReferences(new ConstructorEntry((ClassEntry)m_reference.entry, new Signature("()V")));
1052 m_callsTree.setModel(new DefaultTreeModel(node));
1053 } else if (m_reference.entry instanceof FieldEntry) {
1054 FieldReferenceTreeNode node = m_controller.getFieldReferences((FieldEntry)m_reference.entry);
1055 m_callsTree.setModel(new DefaultTreeModel(node));
1056 } else if (m_reference.entry instanceof MethodEntry) {
1057 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((MethodEntry)m_reference.entry);
1058 m_callsTree.setModel(new DefaultTreeModel(node));
1059 } else if (m_reference.entry instanceof ConstructorEntry) {
1060 BehaviorReferenceTreeNode node = m_controller.getMethodReferences((ConstructorEntry)m_reference.entry);
1061 m_callsTree.setModel(new DefaultTreeModel(node));
1062 }
1063
1064 m_tabs.setSelectedIndex(2);
1065 redraw();
1066 }
1067
1068 private void toggleMapping() {
1069 if (m_controller.entryHasDeobfuscatedName(m_reference.entry)) {
1070 m_controller.removeMapping(m_reference);
1071 } else {
1072 m_controller.markAsDeobfuscated(m_reference);
1073 }
1074 }
1075
1076 private TreePath getPathToRoot(TreeNode node) {
1077 List<TreeNode> nodes = Lists.newArrayList();
1078 TreeNode n = node;
1079 do {
1080 nodes.add(n);
1081 n = n.getParent();
1082 } while (n != null);
1083 Collections.reverse(nodes);
1084 return new TreePath(nodes.toArray());
1085 }
1086
1087 private void close() {
1088 if (!m_controller.isDirty()) {
1089 // everything is saved, we can exit safely
1090 m_frame.dispose();
1091 } else {
1092 // ask to save before closing
1093 String[] options = { "Save and exit", "Discard changes", "Cancel" };
1094 int response = JOptionPane.showOptionDialog(m_frame, "Your mappings have not been saved yet. Do you want to save?", "Save your changes?", JOptionPane.YES_NO_CANCEL_OPTION,
1095 JOptionPane.QUESTION_MESSAGE, null, options, options[2]);
1096 switch (response) {
1097 case JOptionPane.YES_OPTION: // save and exit
1098 if (m_mappingsFileChooser.getSelectedFile() != null || m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) {
1099 try {
1100 m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile());
1101 m_frame.dispose();
1102 } catch (IOException ex) {
1103 throw new Error(ex);
1104 }
1105 }
1106 break;
1107
1108 case JOptionPane.NO_OPTION:
1109 // don't save, exit
1110 m_frame.dispose();
1111 break;
1112
1113 // cancel means do nothing
1114 }
1115 }
1116 }
1117
1118 private void redraw() {
1119 m_frame.validate();
1120 m_frame.repaint();
1121 }
1122}
diff --git a/src/cuchaz/enigma/gui/GuiController.java b/src/cuchaz/enigma/gui/GuiController.java
new file mode 100644
index 00000000..66906227
--- /dev/null
+++ b/src/cuchaz/enigma/gui/GuiController.java
@@ -0,0 +1,358 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.io.File;
14import java.io.FileReader;
15import java.io.FileWriter;
16import java.io.IOException;
17import java.util.Collection;
18import java.util.Deque;
19import java.util.List;
20import java.util.jar.JarFile;
21
22import com.google.common.collect.Lists;
23import com.google.common.collect.Queues;
24import com.strobel.decompiler.languages.java.ast.CompilationUnit;
25
26import cuchaz.enigma.Deobfuscator;
27import cuchaz.enigma.Deobfuscator.ProgressListener;
28import cuchaz.enigma.analysis.BehaviorReferenceTreeNode;
29import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
30import cuchaz.enigma.analysis.ClassInheritanceTreeNode;
31import cuchaz.enigma.analysis.EntryReference;
32import cuchaz.enigma.analysis.FieldReferenceTreeNode;
33import cuchaz.enigma.analysis.MethodImplementationsTreeNode;
34import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
35import cuchaz.enigma.analysis.SourceIndex;
36import cuchaz.enigma.analysis.Token;
37import cuchaz.enigma.gui.ProgressDialog.ProgressRunnable;
38import cuchaz.enigma.mapping.BehaviorEntry;
39import cuchaz.enigma.mapping.ClassEntry;
40import cuchaz.enigma.mapping.Entry;
41import cuchaz.enigma.mapping.FieldEntry;
42import cuchaz.enigma.mapping.MappingParseException;
43import cuchaz.enigma.mapping.MappingsReader;
44import cuchaz.enigma.mapping.MappingsWriter;
45import cuchaz.enigma.mapping.MethodEntry;
46import cuchaz.enigma.mapping.TranslationDirection;
47
48public class GuiController {
49
50 private Deobfuscator m_deobfuscator;
51 private Gui m_gui;
52 private SourceIndex m_index;
53 private ClassEntry m_currentObfClass;
54 private boolean m_isDirty;
55 private Deque<EntryReference<Entry,Entry>> m_referenceStack;
56
57 public GuiController(Gui gui) {
58 m_gui = gui;
59 m_deobfuscator = null;
60 m_index = null;
61 m_currentObfClass = null;
62 m_isDirty = false;
63 m_referenceStack = Queues.newArrayDeque();
64 }
65
66 public boolean isDirty() {
67 return m_isDirty;
68 }
69
70 public void openJar(final JarFile jar) throws IOException {
71 m_gui.onStartOpenJar();
72 m_deobfuscator = new Deobfuscator(jar);
73 m_gui.onFinishOpenJar(m_deobfuscator.getJarName());
74 refreshClasses();
75 }
76
77 public void closeJar() {
78 m_deobfuscator = null;
79 m_gui.onCloseJar();
80 }
81
82 public void openMappings(File file) throws IOException, MappingParseException {
83 FileReader in = new FileReader(file);
84 m_deobfuscator.setMappings(new MappingsReader().read(in));
85 in.close();
86 m_isDirty = false;
87 m_gui.setMappingsFile(file);
88 refreshClasses();
89 refreshCurrentClass();
90 }
91
92 public void saveMappings(File file) throws IOException {
93 FileWriter out = new FileWriter(file);
94 new MappingsWriter().write(out, m_deobfuscator.getMappings());
95 out.close();
96 m_isDirty = false;
97 }
98
99 public void closeMappings() {
100 m_deobfuscator.setMappings(null);
101 m_gui.setMappingsFile(null);
102 refreshClasses();
103 refreshCurrentClass();
104 }
105
106 public void exportSource(final File dirOut) {
107 ProgressDialog.runInThread(m_gui.getFrame(), new ProgressRunnable() {
108 @Override
109 public void run(ProgressListener progress) throws Exception {
110 m_deobfuscator.writeSources(dirOut, progress);
111 }
112 });
113 }
114
115 public void exportJar(final File fileOut) {
116 ProgressDialog.runInThread(m_gui.getFrame(), new ProgressRunnable() {
117 @Override
118 public void run(ProgressListener progress) {
119 m_deobfuscator.writeJar(fileOut, progress);
120 }
121 });
122 }
123
124 public Token getToken(int pos) {
125 if (m_index == null) {
126 return null;
127 }
128 return m_index.getReferenceToken(pos);
129 }
130
131 public EntryReference<Entry,Entry> getDeobfReference(Token token) {
132 if (m_index == null) {
133 return null;
134 }
135 return m_index.getDeobfReference(token);
136 }
137
138 public ReadableToken getReadableToken(Token token) {
139 if (m_index == null) {
140 return null;
141 }
142 return new ReadableToken(
143 m_index.getLineNumber(token.start),
144 m_index.getColumnNumber(token.start),
145 m_index.getColumnNumber(token.end)
146 );
147 }
148
149 public boolean entryHasDeobfuscatedName(Entry deobfEntry) {
150 return m_deobfuscator.hasDeobfuscatedName(m_deobfuscator.obfuscateEntry(deobfEntry));
151 }
152
153 public boolean entryIsInJar(Entry deobfEntry) {
154 return m_deobfuscator.isObfuscatedIdentifier(m_deobfuscator.obfuscateEntry(deobfEntry));
155 }
156
157 public boolean referenceIsRenameable(EntryReference<Entry,Entry> deobfReference) {
158 return m_deobfuscator.isRenameable(m_deobfuscator.obfuscateReference(deobfReference));
159 }
160
161 public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) {
162 ClassEntry obfClassEntry = m_deobfuscator.obfuscateEntry(deobfClassEntry);
163 ClassInheritanceTreeNode rootNode = m_deobfuscator.getJarIndex().getClassInheritance(
164 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
165 obfClassEntry
166 );
167 return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry);
168 }
169
170 public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) {
171 ClassEntry obfClassEntry = m_deobfuscator.obfuscateEntry(deobfClassEntry);
172 return m_deobfuscator.getJarIndex().getClassImplementations(
173 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
174 obfClassEntry
175 );
176 }
177
178 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) {
179 MethodEntry obfMethodEntry = m_deobfuscator.obfuscateEntry(deobfMethodEntry);
180 MethodInheritanceTreeNode rootNode = m_deobfuscator.getJarIndex().getMethodInheritance(
181 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
182 obfMethodEntry
183 );
184 return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry);
185 }
186
187 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) {
188 MethodEntry obfMethodEntry = m_deobfuscator.obfuscateEntry(deobfMethodEntry);
189 List<MethodImplementationsTreeNode> rootNodes = m_deobfuscator.getJarIndex().getMethodImplementations(
190 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
191 obfMethodEntry
192 );
193 if (rootNodes.isEmpty()) {
194 return null;
195 }
196 if (rootNodes.size() > 1) {
197 System.err.println("WARNING: Method " + deobfMethodEntry + " implements multiple interfaces. Only showing first one.");
198 }
199 return MethodImplementationsTreeNode.findNode(rootNodes.get(0), obfMethodEntry);
200 }
201
202 public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) {
203 FieldEntry obfFieldEntry = m_deobfuscator.obfuscateEntry(deobfFieldEntry);
204 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(
205 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
206 obfFieldEntry
207 );
208 rootNode.load(m_deobfuscator.getJarIndex(), true);
209 return rootNode;
210 }
211
212 public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) {
213 BehaviorEntry obfBehaviorEntry = m_deobfuscator.obfuscateEntry(deobfBehaviorEntry);
214 BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode(
215 m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating),
216 obfBehaviorEntry
217 );
218 rootNode.load(m_deobfuscator.getJarIndex(), true);
219 return rootNode;
220 }
221
222 public void rename(EntryReference<Entry,Entry> deobfReference, String newName) {
223 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
224 m_deobfuscator.rename(obfReference.getNameableEntry(), newName);
225 m_isDirty = true;
226 refreshClasses();
227 refreshCurrentClass(obfReference);
228 }
229
230 public void removeMapping(EntryReference<Entry,Entry> deobfReference) {
231 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
232 m_deobfuscator.removeMapping(obfReference.getNameableEntry());
233 m_isDirty = true;
234 refreshClasses();
235 refreshCurrentClass(obfReference);
236 }
237
238 public void markAsDeobfuscated(EntryReference<Entry,Entry> deobfReference) {
239 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
240 m_deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry());
241 m_isDirty = true;
242 refreshClasses();
243 refreshCurrentClass(obfReference);
244 }
245
246 public void openDeclaration(Entry deobfEntry) {
247 if (deobfEntry == null) {
248 throw new IllegalArgumentException("Entry cannot be null!");
249 }
250 openReference(new EntryReference<Entry,Entry>(deobfEntry, deobfEntry.getName()));
251 }
252
253 public void openReference(EntryReference<Entry,Entry> deobfReference) {
254 if (deobfReference == null) {
255 throw new IllegalArgumentException("Reference cannot be null!");
256 }
257
258 // get the reference target class
259 EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference);
260 ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOutermostClassEntry();
261 if (!m_deobfuscator.isObfuscatedIdentifier(obfClassEntry)) {
262 throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!");
263 }
264 if (m_currentObfClass == null || !m_currentObfClass.equals(obfClassEntry)) {
265 // deobfuscate the class, then navigate to the reference
266 m_currentObfClass = obfClassEntry;
267 deobfuscate(m_currentObfClass, obfReference);
268 } else {
269 showReference(obfReference);
270 }
271 }
272
273 private void showReference(EntryReference<Entry,Entry> obfReference) {
274 EntryReference<Entry,Entry> deobfReference = m_deobfuscator.deobfuscateReference(obfReference);
275 Collection<Token> tokens = m_index.getReferenceTokens(deobfReference);
276 if (tokens.isEmpty()) {
277 // DEBUG
278 System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, m_currentObfClass));
279 } else {
280 m_gui.showTokens(tokens);
281 }
282 }
283
284 public void savePreviousReference(EntryReference<Entry,Entry> deobfReference) {
285 m_referenceStack.push(m_deobfuscator.obfuscateReference(deobfReference));
286 }
287
288 public void openPreviousReference() {
289 if (hasPreviousLocation()) {
290 openReference(m_deobfuscator.deobfuscateReference(m_referenceStack.pop()));
291 }
292 }
293
294 public boolean hasPreviousLocation() {
295 return !m_referenceStack.isEmpty();
296 }
297
298 private void refreshClasses() {
299 List<ClassEntry> obfClasses = Lists.newArrayList();
300 List<ClassEntry> deobfClasses = Lists.newArrayList();
301 m_deobfuscator.getSeparatedClasses(obfClasses, deobfClasses);
302 m_gui.setObfClasses(obfClasses);
303 m_gui.setDeobfClasses(deobfClasses);
304 }
305
306 private void refreshCurrentClass() {
307 refreshCurrentClass(null);
308 }
309
310 private void refreshCurrentClass(EntryReference<Entry,Entry> obfReference) {
311 if (m_currentObfClass != null) {
312 deobfuscate(m_currentObfClass, obfReference);
313 }
314 }
315
316 private void deobfuscate(final ClassEntry classEntry, final EntryReference<Entry,Entry> obfReference) {
317
318 m_gui.setSource("(deobfuscating...)");
319
320 // run the deobfuscator in a separate thread so we don't block the GUI event queue
321 new Thread() {
322 @Override
323 public void run() {
324 // decompile,deobfuscate the bytecode
325 CompilationUnit sourceTree = m_deobfuscator.getSourceTree(classEntry.getClassName());
326 if (sourceTree == null) {
327 // decompilation of this class is not supported
328 m_gui.setSource("Unable to find class: " + classEntry);
329 return;
330 }
331 String source = m_deobfuscator.getSource(sourceTree);
332 m_index = m_deobfuscator.getSourceIndex(sourceTree, source);
333 m_gui.setSource(m_index.getSource());
334 if (obfReference != null) {
335 showReference(obfReference);
336 }
337
338 // set the highlighted tokens
339 List<Token> obfuscatedTokens = Lists.newArrayList();
340 List<Token> deobfuscatedTokens = Lists.newArrayList();
341 List<Token> otherTokens = Lists.newArrayList();
342 for (Token token : m_index.referenceTokens()) {
343 EntryReference<Entry,Entry> reference = m_index.getDeobfReference(token);
344 if (referenceIsRenameable(reference)) {
345 if (entryHasDeobfuscatedName(reference.getNameableEntry())) {
346 deobfuscatedTokens.add(token);
347 } else {
348 obfuscatedTokens.add(token);
349 }
350 } else {
351 otherTokens.add(token);
352 }
353 }
354 m_gui.setHighlightedTokens(obfuscatedTokens, deobfuscatedTokens, otherTokens);
355 }
356 }.start();
357 }
358}
diff --git a/src/cuchaz/enigma/gui/GuiTricks.java b/src/cuchaz/enigma/gui/GuiTricks.java
new file mode 100644
index 00000000..5dc3ffb3
--- /dev/null
+++ b/src/cuchaz/enigma/gui/GuiTricks.java
@@ -0,0 +1,56 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.Font;
14import java.awt.event.ActionListener;
15import java.awt.event.MouseEvent;
16import java.util.Arrays;
17
18import javax.swing.JButton;
19import javax.swing.JComponent;
20import javax.swing.JLabel;
21import javax.swing.ToolTipManager;
22
23public class GuiTricks {
24
25 public static JLabel unboldLabel(JLabel label) {
26 Font font = label.getFont();
27 label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD));
28 return label;
29 }
30
31 public static void showToolTipNow(JComponent component) {
32 // HACKHACK: trick the tooltip manager into showing the tooltip right now
33 ToolTipManager manager = ToolTipManager.sharedInstance();
34 int oldDelay = manager.getInitialDelay();
35 manager.setInitialDelay(0);
36 manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false));
37 manager.setInitialDelay(oldDelay);
38 }
39
40 public static void deactivateButton(JButton button) {
41 button.setEnabled(false);
42 button.setText("");
43 for (ActionListener listener : Arrays.asList(button.getActionListeners())) {
44 button.removeActionListener(listener);
45 }
46 }
47
48 public static void activateButton(JButton button, String text, ActionListener newListener) {
49 button.setText(text);
50 button.setEnabled(true);
51 for (ActionListener listener : Arrays.asList(button.getActionListeners())) {
52 button.removeActionListener(listener);
53 }
54 button.addActionListener(newListener);
55 }
56}
diff --git a/src/cuchaz/enigma/gui/MemberMatchingGui.java b/src/cuchaz/enigma/gui/MemberMatchingGui.java
new file mode 100644
index 00000000..150eaadb
--- /dev/null
+++ b/src/cuchaz/enigma/gui/MemberMatchingGui.java
@@ -0,0 +1,499 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.Dimension;
16import java.awt.FlowLayout;
17import java.awt.event.ActionEvent;
18import java.awt.event.ActionListener;
19import java.awt.event.KeyAdapter;
20import java.awt.event.KeyEvent;
21import java.util.Collection;
22import java.util.List;
23import java.util.Map;
24
25import javax.swing.BoxLayout;
26import javax.swing.ButtonGroup;
27import javax.swing.JButton;
28import javax.swing.JFrame;
29import javax.swing.JLabel;
30import javax.swing.JPanel;
31import javax.swing.JRadioButton;
32import javax.swing.JScrollPane;
33import javax.swing.JSplitPane;
34import javax.swing.WindowConstants;
35import javax.swing.text.Highlighter.HighlightPainter;
36
37import com.google.common.collect.Lists;
38import com.google.common.collect.Maps;
39
40import cuchaz.enigma.Constants;
41import cuchaz.enigma.Deobfuscator;
42import cuchaz.enigma.analysis.EntryReference;
43import cuchaz.enigma.analysis.SourceIndex;
44import cuchaz.enigma.analysis.Token;
45import cuchaz.enigma.convert.ClassMatches;
46import cuchaz.enigma.convert.MemberMatches;
47import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
48import cuchaz.enigma.mapping.ClassEntry;
49import cuchaz.enigma.mapping.Entry;
50import de.sciss.syntaxpane.DefaultSyntaxKit;
51
52
53public class MemberMatchingGui<T extends Entry> {
54
55 private static enum SourceType {
56 Matched {
57
58 @Override
59 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
60 return matches.getSourceClassesWithoutUnmatchedEntries();
61 }
62 },
63 Unmatched {
64
65 @Override
66 public <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches) {
67 return matches.getSourceClassesWithUnmatchedEntries();
68 }
69 };
70
71 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
72 JRadioButton button = new JRadioButton(name(), this == getDefault());
73 button.setActionCommand(name());
74 button.addActionListener(listener);
75 group.add(button);
76 return button;
77 }
78
79 public abstract <T extends Entry> Collection<ClassEntry> getObfSourceClasses(MemberMatches<T> matches);
80
81 public static SourceType getDefault() {
82 return values()[0];
83 }
84 }
85
86 public static interface SaveListener<T extends Entry> {
87 public void save(MemberMatches<T> matches);
88 }
89
90 // controls
91 private JFrame m_frame;
92 private Map<SourceType,JRadioButton> m_sourceTypeButtons;
93 private ClassSelector m_sourceClasses;
94 private CodeReader m_sourceReader;
95 private CodeReader m_destReader;
96 private JButton m_matchButton;
97 private JButton m_unmatchableButton;
98 private JLabel m_sourceLabel;
99 private JLabel m_destLabel;
100 private HighlightPainter m_unmatchedHighlightPainter;
101 private HighlightPainter m_matchedHighlightPainter;
102
103 private ClassMatches m_classMatches;
104 private MemberMatches<T> m_memberMatches;
105 private Deobfuscator m_sourceDeobfuscator;
106 private Deobfuscator m_destDeobfuscator;
107 private SaveListener<T> m_saveListener;
108 private SourceType m_sourceType;
109 private ClassEntry m_obfSourceClass;
110 private ClassEntry m_obfDestClass;
111 private T m_obfSourceEntry;
112 private T m_obfDestEntry;
113
114 public MemberMatchingGui(ClassMatches classMatches, MemberMatches<T> fieldMatches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
115
116 m_classMatches = classMatches;
117 m_memberMatches = fieldMatches;
118 m_sourceDeobfuscator = sourceDeobfuscator;
119 m_destDeobfuscator = destDeobfuscator;
120
121 // init frame
122 m_frame = new JFrame(Constants.Name + " - Member Matcher");
123 final Container pane = m_frame.getContentPane();
124 pane.setLayout(new BorderLayout());
125
126 // init classes side
127 JPanel classesPanel = new JPanel();
128 classesPanel.setLayout(new BoxLayout(classesPanel, BoxLayout.PAGE_AXIS));
129 classesPanel.setPreferredSize(new Dimension(200, 0));
130 pane.add(classesPanel, BorderLayout.WEST);
131 classesPanel.add(new JLabel("Classes"));
132
133 // init source type radios
134 JPanel sourceTypePanel = new JPanel();
135 classesPanel.add(sourceTypePanel);
136 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
137 ActionListener sourceTypeListener = new ActionListener() {
138 @Override
139 public void actionPerformed(ActionEvent event) {
140 setSourceType(SourceType.valueOf(event.getActionCommand()));
141 }
142 };
143 ButtonGroup sourceTypeButtons = new ButtonGroup();
144 m_sourceTypeButtons = Maps.newHashMap();
145 for (SourceType sourceType : SourceType.values()) {
146 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
147 m_sourceTypeButtons.put(sourceType, button);
148 sourceTypePanel.add(button);
149 }
150
151 m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
152 m_sourceClasses.setListener(new ClassSelectionListener() {
153 @Override
154 public void onSelectClass(ClassEntry classEntry) {
155 setSourceClass(classEntry);
156 }
157 });
158 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
159 classesPanel.add(sourceScroller);
160
161 // init readers
162 DefaultSyntaxKit.initKit();
163 m_sourceReader = new CodeReader();
164 m_sourceReader.setSelectionListener(new CodeReader.SelectionListener() {
165 @Override
166 public void onSelect(EntryReference<Entry,Entry> reference) {
167 if (reference != null) {
168 onSelectSource(reference.entry);
169 } else {
170 onSelectSource(null);
171 }
172 }
173 });
174 m_destReader = new CodeReader();
175 m_destReader.setSelectionListener(new CodeReader.SelectionListener() {
176 @Override
177 public void onSelect(EntryReference<Entry,Entry> reference) {
178 if (reference != null) {
179 onSelectDest(reference.entry);
180 } else {
181 onSelectDest(null);
182 }
183 }
184 });
185
186 // add key bindings
187 KeyAdapter keyListener = new KeyAdapter() {
188 @Override
189 public void keyPressed(KeyEvent event) {
190 switch (event.getKeyCode()) {
191 case KeyEvent.VK_M:
192 m_matchButton.doClick();
193 break;
194 }
195 }
196 };
197 m_sourceReader.addKeyListener(keyListener);
198 m_destReader.addKeyListener(keyListener);
199
200 // init all the splits
201 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_sourceReader), new JScrollPane(m_destReader));
202 splitRight.setResizeWeight(0.5); // resize 50:50
203 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, classesPanel, splitRight);
204 splitLeft.setResizeWeight(0); // let the right side take all the slack
205 pane.add(splitLeft, BorderLayout.CENTER);
206 splitLeft.resetToPreferredSizes();
207
208 // init bottom panel
209 JPanel bottomPanel = new JPanel();
210 bottomPanel.setLayout(new FlowLayout());
211 pane.add(bottomPanel, BorderLayout.SOUTH);
212
213 m_matchButton = new JButton();
214 m_unmatchableButton = new JButton();
215
216 m_sourceLabel = new JLabel();
217 bottomPanel.add(m_sourceLabel);
218 bottomPanel.add(m_matchButton);
219 bottomPanel.add(m_unmatchableButton);
220 m_destLabel = new JLabel();
221 bottomPanel.add(m_destLabel);
222
223 // show the frame
224 pane.doLayout();
225 m_frame.setSize(1024, 576);
226 m_frame.setMinimumSize(new Dimension(640, 480));
227 m_frame.setVisible(true);
228 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
229
230 m_unmatchedHighlightPainter = new ObfuscatedHighlightPainter();
231 m_matchedHighlightPainter = new DeobfuscatedHighlightPainter();
232
233 // init state
234 m_saveListener = null;
235 m_obfSourceClass = null;
236 m_obfDestClass = null;
237 m_obfSourceEntry = null;
238 m_obfDestEntry = null;
239 setSourceType(SourceType.getDefault());
240 updateButtons();
241 }
242
243 protected void setSourceType(SourceType val) {
244 m_sourceType = val;
245 updateSourceClasses();
246 }
247
248 public void setSaveListener(SaveListener<T> val) {
249 m_saveListener = val;
250 }
251
252 private void updateSourceClasses() {
253
254 String selectedPackage = m_sourceClasses.getSelectedPackage();
255
256 List<ClassEntry> deobfClassEntries = Lists.newArrayList();
257 for (ClassEntry entry : m_sourceType.getObfSourceClasses(m_memberMatches)) {
258 deobfClassEntries.add(m_sourceDeobfuscator.deobfuscateEntry(entry));
259 }
260 m_sourceClasses.setClasses(deobfClassEntries);
261
262 if (selectedPackage != null) {
263 m_sourceClasses.expandPackage(selectedPackage);
264 }
265
266 for (SourceType sourceType : SourceType.values()) {
267 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
268 sourceType.name(), sourceType.getObfSourceClasses(m_memberMatches).size()
269 ));
270 }
271 }
272
273 protected void setSourceClass(ClassEntry sourceClass) {
274
275 m_obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
276 m_obfDestClass = m_classMatches.getUniqueMatches().get(m_obfSourceClass);
277 if (m_obfDestClass == null) {
278 throw new Error("No matching dest class for source class: " + m_obfSourceClass);
279 }
280
281 m_sourceReader.decompileClass(m_obfSourceClass, m_sourceDeobfuscator, false, new Runnable() {
282 @Override
283 public void run() {
284 updateSourceHighlights();
285 }
286 });
287 m_destReader.decompileClass(m_obfDestClass, m_destDeobfuscator, false, new Runnable() {
288 @Override
289 public void run() {
290 updateDestHighlights();
291 }
292 });
293 }
294
295 protected void updateSourceHighlights() {
296 highlightEntries(m_sourceReader, m_sourceDeobfuscator, m_memberMatches.matches().keySet(), m_memberMatches.getUnmatchedSourceEntries());
297 }
298
299 protected void updateDestHighlights() {
300 highlightEntries(m_destReader, m_destDeobfuscator, m_memberMatches.matches().values(), m_memberMatches.getUnmatchedDestEntries());
301 }
302
303 private void highlightEntries(CodeReader reader, Deobfuscator deobfuscator, Collection<T> obfMatchedEntries, Collection<T> obfUnmatchedEntries) {
304 reader.clearHighlights();
305 SourceIndex index = reader.getSourceIndex();
306
307 // matched fields
308 for (T obfT : obfMatchedEntries) {
309 T deobfT = deobfuscator.deobfuscateEntry(obfT);
310 Token token = index.getDeclarationToken(deobfT);
311 if (token != null) {
312 reader.setHighlightedToken(token, m_matchedHighlightPainter);
313 }
314 }
315
316 // unmatched fields
317 for (T obfT : obfUnmatchedEntries) {
318 T deobfT = deobfuscator.deobfuscateEntry(obfT);
319 Token token = index.getDeclarationToken(deobfT);
320 if (token != null) {
321 reader.setHighlightedToken(token, m_unmatchedHighlightPainter);
322 }
323 }
324 }
325
326 private boolean isSelectionMatched() {
327 return m_obfSourceEntry != null && m_obfDestEntry != null
328 && m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry);
329 }
330
331 protected void onSelectSource(Entry source) {
332
333 // start with no selection
334 if (isSelectionMatched()) {
335 setDest(null);
336 }
337 setSource(null);
338
339 // then look for a valid source selection
340 if (source != null) {
341
342 // this looks really scary, but it's actually ok
343 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
344 // and MemberMatches.hasSource() will only pass entries that actually match T
345 @SuppressWarnings("unchecked")
346 T sourceEntry = (T)source;
347
348 T obfSourceEntry = m_sourceDeobfuscator.obfuscateEntry(sourceEntry);
349 if (m_memberMatches.hasSource(obfSourceEntry)) {
350 setSource(obfSourceEntry);
351
352 // look for a matched dest too
353 T obfDestEntry = m_memberMatches.matches().get(obfSourceEntry);
354 if (obfDestEntry != null) {
355 setDest(obfDestEntry);
356 }
357 }
358 }
359
360 updateButtons();
361 }
362
363 protected void onSelectDest(Entry dest) {
364
365 // start with no selection
366 if (isSelectionMatched()) {
367 setSource(null);
368 }
369 setDest(null);
370
371 // then look for a valid dest selection
372 if (dest != null) {
373
374 // this looks really scary, but it's actually ok
375 // Deobfuscator.obfuscateEntry can handle all implementations of Entry
376 // and MemberMatches.hasSource() will only pass entries that actually match T
377 @SuppressWarnings("unchecked")
378 T destEntry = (T)dest;
379
380 T obfDestEntry = m_destDeobfuscator.obfuscateEntry(destEntry);
381 if (m_memberMatches.hasDest(obfDestEntry)) {
382 setDest(obfDestEntry);
383
384 // look for a matched source too
385 T obfSourceEntry = m_memberMatches.matches().inverse().get(obfDestEntry);
386 if (obfSourceEntry != null) {
387 setSource(obfSourceEntry);
388 }
389 }
390 }
391
392 updateButtons();
393 }
394
395 private void setSource(T obfEntry) {
396 if (obfEntry == null) {
397 m_obfSourceEntry = obfEntry;
398 m_sourceLabel.setText("");
399 } else {
400 m_obfSourceEntry = obfEntry;
401 m_sourceLabel.setText(getEntryLabel(obfEntry, m_sourceDeobfuscator));
402 }
403 }
404
405 private void setDest(T obfEntry) {
406 if (obfEntry == null) {
407 m_obfDestEntry = obfEntry;
408 m_destLabel.setText("");
409 } else {
410 m_obfDestEntry = obfEntry;
411 m_destLabel.setText(getEntryLabel(obfEntry, m_destDeobfuscator));
412 }
413 }
414
415 private String getEntryLabel(T obfEntry, Deobfuscator deobfuscator) {
416 // show obfuscated and deobfuscated names, but no types/signatures
417 T deobfEntry = deobfuscator.deobfuscateEntry(obfEntry);
418 return String.format("%s (%s)", deobfEntry.getName(), obfEntry.getName());
419 }
420
421 private void updateButtons() {
422
423 GuiTricks.deactivateButton(m_matchButton);
424 GuiTricks.deactivateButton(m_unmatchableButton);
425
426 if (m_obfSourceEntry != null && m_obfDestEntry != null) {
427 if (m_memberMatches.isMatched(m_obfSourceEntry, m_obfDestEntry)) {
428 GuiTricks.activateButton(m_matchButton, "Unmatch", new ActionListener() {
429 @Override
430 public void actionPerformed(ActionEvent event) {
431 unmatch();
432 }
433 });
434 } else if (!m_memberMatches.isMatchedSourceEntry(m_obfSourceEntry) && !m_memberMatches.isMatchedDestEntry(m_obfDestEntry)) {
435 GuiTricks.activateButton(m_matchButton, "Match", new ActionListener() {
436 @Override
437 public void actionPerformed(ActionEvent event) {
438 match();
439 }
440 });
441 }
442 } else if (m_obfSourceEntry != null) {
443 GuiTricks.activateButton(m_unmatchableButton, "Set Unmatchable", new ActionListener() {
444 @Override
445 public void actionPerformed(ActionEvent event) {
446 unmatchable();
447 }
448 });
449 }
450 }
451
452 protected void match() {
453
454 // update the field matches
455 m_memberMatches.makeMatch(m_obfSourceEntry, m_obfDestEntry);
456 save();
457
458 // update the ui
459 onSelectSource(null);
460 onSelectDest(null);
461 updateSourceHighlights();
462 updateDestHighlights();
463 updateSourceClasses();
464 }
465
466 protected void unmatch() {
467
468 // update the field matches
469 m_memberMatches.unmakeMatch(m_obfSourceEntry, m_obfDestEntry);
470 save();
471
472 // update the ui
473 onSelectSource(null);
474 onSelectDest(null);
475 updateSourceHighlights();
476 updateDestHighlights();
477 updateSourceClasses();
478 }
479
480 protected void unmatchable() {
481
482 // update the field matches
483 m_memberMatches.makeSourceUnmatchable(m_obfSourceEntry);
484 save();
485
486 // update the ui
487 onSelectSource(null);
488 onSelectDest(null);
489 updateSourceHighlights();
490 updateDestHighlights();
491 updateSourceClasses();
492 }
493
494 private void save() {
495 if (m_saveListener != null) {
496 m_saveListener.save(m_memberMatches);
497 }
498 }
499}
diff --git a/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java b/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java
new file mode 100644
index 00000000..4c3714a9
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.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 00000000..8d3fbe86
--- /dev/null
+++ b/src/cuchaz/enigma/gui/OtherHighlightPainter.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.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 00000000..1c20f10b
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ProgressDialog.java
@@ -0,0 +1,105 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import java.awt.BorderLayout;
14import java.awt.Container;
15import java.awt.Dimension;
16import java.awt.FlowLayout;
17
18import javax.swing.BorderFactory;
19import javax.swing.JFrame;
20import javax.swing.JLabel;
21import javax.swing.JPanel;
22import javax.swing.JProgressBar;
23import javax.swing.WindowConstants;
24
25import cuchaz.enigma.Constants;
26import cuchaz.enigma.Deobfuscator.ProgressListener;
27
28public class ProgressDialog implements ProgressListener, AutoCloseable {
29
30 private JFrame m_frame;
31 private JLabel m_title;
32 private JLabel m_text;
33 private JProgressBar m_progress;
34
35 public ProgressDialog(JFrame parent) {
36
37 // init frame
38 m_frame = new JFrame(Constants.Name + " - Operation in progress");
39 final Container pane = m_frame.getContentPane();
40 FlowLayout layout = new FlowLayout();
41 layout.setAlignment(FlowLayout.LEFT);
42 pane.setLayout(layout);
43
44 m_title = new JLabel();
45 pane.add(m_title);
46
47 // set up the progress bar
48 JPanel panel = new JPanel();
49 pane.add(panel);
50 panel.setLayout(new BorderLayout());
51 m_text = GuiTricks.unboldLabel(new JLabel());
52 m_progress = new JProgressBar();
53 m_text.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
54 panel.add(m_text, BorderLayout.NORTH);
55 panel.add(m_progress, BorderLayout.CENTER);
56 panel.setPreferredSize(new Dimension(360, 50));
57
58 // show the frame
59 pane.doLayout();
60 m_frame.setSize(400, 120);
61 m_frame.setResizable(false);
62 m_frame.setLocationRelativeTo(parent);
63 m_frame.setVisible(true);
64 m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
65 }
66
67 public void close() {
68 m_frame.dispose();
69 }
70
71 @Override
72 public void init(int totalWork, String title) {
73 m_title.setText(title);
74 m_progress.setMinimum(0);
75 m_progress.setMaximum(totalWork);
76 m_progress.setValue(0);
77 }
78
79 @Override
80 public void onProgress(int numDone, String message) {
81 m_text.setText(message);
82 m_progress.setValue(numDone);
83
84 // update the frame
85 m_frame.validate();
86 m_frame.repaint();
87 }
88
89 public static interface ProgressRunnable {
90 void run(ProgressListener listener) throws Exception;
91 }
92
93 public static void runInThread(final JFrame parent, final ProgressRunnable runnable) {
94 new Thread() {
95 @Override
96 public void run() {
97 try (ProgressDialog progress = new ProgressDialog(parent)) {
98 runnable.run(progress);
99 } catch (Exception ex) {
100 throw new Error(ex);
101 }
102 }
103 }.start();
104 }
105}
diff --git a/src/cuchaz/enigma/gui/ReadableToken.java b/src/cuchaz/enigma/gui/ReadableToken.java
new file mode 100644
index 00000000..0741af39
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ReadableToken.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..8b515bbd
--- /dev/null
+++ b/src/cuchaz/enigma/gui/RenameListener.java
@@ -0,0 +1,17 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.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/ScoredClassEntry.java b/src/cuchaz/enigma/gui/ScoredClassEntry.java
new file mode 100644
index 00000000..60704528
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ScoredClassEntry.java
@@ -0,0 +1,30 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.gui;
12
13import cuchaz.enigma.mapping.ClassEntry;
14
15
16public class ScoredClassEntry extends ClassEntry {
17
18 private static final long serialVersionUID = -8798725308554217105L;
19
20 private float m_score;
21
22 public ScoredClassEntry(ClassEntry other, float score) {
23 super(other);
24 m_score = score;
25 }
26
27 public float getScore() {
28 return m_score;
29 }
30}
diff --git a/src/cuchaz/enigma/gui/SelectionHighlightPainter.java b/src/cuchaz/enigma/gui/SelectionHighlightPainter.java
new file mode 100644
index 00000000..4165da4a
--- /dev/null
+++ b/src/cuchaz/enigma/gui/SelectionHighlightPainter.java
@@ -0,0 +1,34 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..e4f7c873
--- /dev/null
+++ b/src/cuchaz/enigma/gui/TokenListCellRenderer.java
@@ -0,0 +1,38 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.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 00000000..9d99016e
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ArgumentEntry.java
@@ -0,0 +1,116 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..a0055a63
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ArgumentMapping.java
@@ -0,0 +1,49 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15public class ArgumentMapping implements Serializable, Comparable<ArgumentMapping> {
16
17 private static final long serialVersionUID = 8610742471440861315L;
18
19 private int m_index;
20 private String m_name;
21
22 // NOTE: this argument order is important for the MethodReader/MethodWriter
23 public ArgumentMapping(int index, String name) {
24 m_index = index;
25 m_name = NameValidator.validateArgumentName(name);
26 }
27
28 public ArgumentMapping(ArgumentMapping other) {
29 m_index = other.m_index;
30 m_name = other.m_name;
31 }
32
33 public int getIndex() {
34 return m_index;
35 }
36
37 public String getName() {
38 return m_name;
39 }
40
41 public void setName(String val) {
42 m_name = NameValidator.validateArgumentName(val);
43 }
44
45 @Override
46 public int compareTo(ArgumentMapping other) {
47 return Integer.compare(m_index, other.m_index);
48 }
49}
diff --git a/src/cuchaz/enigma/mapping/BehaviorEntry.java b/src/cuchaz/enigma/mapping/BehaviorEntry.java
new file mode 100644
index 00000000..031d2670
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/BehaviorEntry.java
@@ -0,0 +1,15 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public interface BehaviorEntry extends Entry {
14 Signature getSignature();
15}
diff --git a/src/cuchaz/enigma/mapping/ClassEntry.java b/src/cuchaz/enigma/mapping/ClassEntry.java
new file mode 100644
index 00000000..373203f0
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ClassEntry.java
@@ -0,0 +1,172 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.List;
15
16import com.google.common.collect.Lists;
17
18public class ClassEntry implements Entry, Serializable {
19
20 private static final long serialVersionUID = 4235460580973955811L;
21
22 private String m_name;
23
24 public ClassEntry(String className) {
25 if (className == null) {
26 throw new IllegalArgumentException("Class name cannot be null!");
27 }
28 if (className.indexOf('.') >= 0) {
29 throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className);
30 }
31
32 m_name = className;
33
34 if (isInnerClass() && getInnermostClassName().indexOf('/') >= 0) {
35 throw new IllegalArgumentException("Inner class must not have a package: " + className);
36 }
37 }
38
39 public ClassEntry(ClassEntry other) {
40 m_name = other.m_name;
41 }
42
43 @Override
44 public String getName() {
45 return m_name;
46 }
47
48 @Override
49 public String getClassName() {
50 return m_name;
51 }
52
53 @Override
54 public ClassEntry getClassEntry() {
55 return this;
56 }
57
58 @Override
59 public ClassEntry cloneToNewClass(ClassEntry classEntry) {
60 return classEntry;
61 }
62
63 @Override
64 public int hashCode() {
65 return m_name.hashCode();
66 }
67
68 @Override
69 public boolean equals(Object other) {
70 if (other instanceof ClassEntry) {
71 return equals((ClassEntry)other);
72 }
73 return false;
74 }
75
76 public boolean equals(ClassEntry other) {
77 return m_name.equals(other.m_name);
78 }
79
80 @Override
81 public String toString() {
82 return m_name;
83 }
84
85 public boolean isInnerClass() {
86 return m_name.lastIndexOf('$') >= 0;
87 }
88
89 public List<String> getClassChainNames() {
90 return Lists.newArrayList(m_name.split("\\$"));
91 }
92
93 public List<ClassEntry> getClassChain() {
94 List<ClassEntry> entries = Lists.newArrayList();
95 StringBuilder buf = new StringBuilder();
96 for (String name : getClassChainNames()) {
97 if (buf.length() > 0) {
98 buf.append("$");
99 }
100 buf.append(name);
101 entries.add(new ClassEntry(buf.toString()));
102 }
103 return entries;
104 }
105
106 public String getOutermostClassName() {
107 if (isInnerClass()) {
108 return m_name.substring(0, m_name.indexOf('$'));
109 }
110 return m_name;
111 }
112
113 public ClassEntry getOutermostClassEntry() {
114 return new ClassEntry(getOutermostClassName());
115 }
116
117 public String getOuterClassName() {
118 if (!isInnerClass()) {
119 throw new Error("This is not an inner class!");
120 }
121 return m_name.substring(0, m_name.lastIndexOf('$'));
122 }
123
124 public ClassEntry getOuterClassEntry() {
125 return new ClassEntry(getOuterClassName());
126 }
127
128 public String getInnermostClassName() {
129 if (!isInnerClass()) {
130 throw new Error("This is not an inner class!");
131 }
132 return m_name.substring(m_name.lastIndexOf('$') + 1);
133 }
134
135 public boolean isInDefaultPackage() {
136 return m_name.indexOf('/') < 0;
137 }
138
139 public String getPackageName() {
140 int pos = m_name.lastIndexOf('/');
141 if (pos > 0) {
142 return m_name.substring(0, pos);
143 }
144 return null;
145 }
146
147 public String getSimpleName() {
148 int pos = m_name.lastIndexOf('/');
149 if (pos > 0) {
150 return m_name.substring(pos + 1);
151 }
152 return m_name;
153 }
154
155 public ClassEntry buildClassEntry(List<ClassEntry> classChain) {
156 assert(classChain.contains(this));
157 StringBuilder buf = new StringBuilder();
158 for (ClassEntry chainEntry : classChain) {
159 if (buf.length() == 0) {
160 buf.append(chainEntry.getName());
161 } else {
162 buf.append("$");
163 buf.append(chainEntry.isInnerClass() ? chainEntry.getInnermostClassName() : chainEntry.getSimpleName());
164 }
165
166 if (chainEntry == this) {
167 break;
168 }
169 }
170 return new ClassEntry(buf.toString());
171 }
172}
diff --git a/src/cuchaz/enigma/mapping/ClassMapping.java b/src/cuchaz/enigma/mapping/ClassMapping.java
new file mode 100644
index 00000000..0b0105ec
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ClassMapping.java
@@ -0,0 +1,460 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.ArrayList;
15import java.util.Map;
16
17import com.google.common.collect.Maps;
18
19public class ClassMapping implements Serializable, Comparable<ClassMapping> {
20
21 private static final long serialVersionUID = -5148491146902340107L;
22
23 private String m_obfFullName;
24 private String m_obfSimpleName;
25 private String m_deobfName;
26 private Map<String,ClassMapping> m_innerClassesByObfSimple;
27 private Map<String,ClassMapping> m_innerClassesByDeobf;
28 private Map<String,FieldMapping> m_fieldsByObf;
29 private Map<String,FieldMapping> m_fieldsByDeobf;
30 private Map<String,MethodMapping> m_methodsByObf;
31 private Map<String,MethodMapping> m_methodsByDeobf;
32
33 public ClassMapping(String obfFullName) {
34 this(obfFullName, null);
35 }
36
37 public ClassMapping(String obfFullName, String deobfName) {
38 m_obfFullName = obfFullName;
39 ClassEntry classEntry = new ClassEntry(obfFullName);
40 m_obfSimpleName = classEntry.isInnerClass() ? classEntry.getInnermostClassName() : classEntry.getSimpleName();
41 m_deobfName = NameValidator.validateClassName(deobfName, false);
42 m_innerClassesByObfSimple = Maps.newHashMap();
43 m_innerClassesByDeobf = Maps.newHashMap();
44 m_fieldsByObf = Maps.newHashMap();
45 m_fieldsByDeobf = Maps.newHashMap();
46 m_methodsByObf = Maps.newHashMap();
47 m_methodsByDeobf = Maps.newHashMap();
48 }
49
50 public String getObfFullName() {
51 return m_obfFullName;
52 }
53
54 public String getObfSimpleName() {
55 return m_obfSimpleName;
56 }
57
58 public String getDeobfName() {
59 return m_deobfName;
60 }
61
62 public void setDeobfName(String val) {
63 m_deobfName = NameValidator.validateClassName(val, false);
64 }
65
66 //// INNER CLASSES ////////
67
68 public Iterable<ClassMapping> innerClasses() {
69 assert (m_innerClassesByObfSimple.size() >= m_innerClassesByDeobf.size());
70 return m_innerClassesByObfSimple.values();
71 }
72
73 public void addInnerClassMapping(ClassMapping classMapping) {
74 boolean obfWasAdded = m_innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping) == null;
75 assert (obfWasAdded);
76 if (classMapping.getDeobfName() != null) {
77 assert (isSimpleClassName(classMapping.getDeobfName()));
78 boolean deobfWasAdded = m_innerClassesByDeobf.put(classMapping.getDeobfName(), classMapping) == null;
79 assert (deobfWasAdded);
80 }
81 }
82
83 public void removeInnerClassMapping(ClassMapping classMapping) {
84 boolean obfWasRemoved = m_innerClassesByObfSimple.remove(classMapping.getObfSimpleName()) != null;
85 assert (obfWasRemoved);
86 if (classMapping.getDeobfName() != null) {
87 boolean deobfWasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null;
88 assert (deobfWasRemoved);
89 }
90 }
91
92 public ClassMapping getOrCreateInnerClass(ClassEntry obfInnerClass) {
93 ClassMapping classMapping = m_innerClassesByObfSimple.get(obfInnerClass.getInnermostClassName());
94 if (classMapping == null) {
95 classMapping = new ClassMapping(obfInnerClass.getName());
96 boolean wasAdded = m_innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping) == null;
97 assert (wasAdded);
98 }
99 return classMapping;
100 }
101
102 public ClassMapping getInnerClassByObfSimple(String obfSimpleName) {
103 assert (isSimpleClassName(obfSimpleName));
104 return m_innerClassesByObfSimple.get(obfSimpleName);
105 }
106
107 public ClassMapping getInnerClassByDeobf(String deobfName) {
108 assert (isSimpleClassName(deobfName));
109 return m_innerClassesByDeobf.get(deobfName);
110 }
111
112 public ClassMapping getInnerClassByDeobfThenObfSimple(String name) {
113 ClassMapping classMapping = getInnerClassByDeobf(name);
114 if (classMapping == null) {
115 classMapping = getInnerClassByObfSimple(name);
116 }
117 return classMapping;
118 }
119
120 public String getDeobfInnerClassName(String obfSimpleName) {
121 assert (isSimpleClassName(obfSimpleName));
122 ClassMapping classMapping = m_innerClassesByObfSimple.get(obfSimpleName);
123 if (classMapping != null) {
124 return classMapping.getDeobfName();
125 }
126 return null;
127 }
128
129 public void setInnerClassName(ClassEntry obfInnerClass, String deobfName) {
130 ClassMapping classMapping = getOrCreateInnerClass(obfInnerClass);
131 if (classMapping.getDeobfName() != null) {
132 boolean wasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null;
133 assert (wasRemoved);
134 }
135 classMapping.setDeobfName(deobfName);
136 if (deobfName != null) {
137 assert (isSimpleClassName(deobfName));
138 boolean wasAdded = m_innerClassesByDeobf.put(deobfName, classMapping) == null;
139 assert (wasAdded);
140 }
141 }
142
143 public boolean hasInnerClassByObfSimple(String obfSimpleName) {
144 return m_innerClassesByObfSimple.containsKey(obfSimpleName);
145 }
146
147 public boolean hasInnerClassByDeobf(String deobfName) {
148 return m_innerClassesByDeobf.containsKey(deobfName);
149 }
150
151
152 //// FIELDS ////////
153
154 public Iterable<FieldMapping> fields() {
155 assert (m_fieldsByObf.size() == m_fieldsByDeobf.size());
156 return m_fieldsByObf.values();
157 }
158
159 public boolean containsObfField(String obfName, Type obfType) {
160 return m_fieldsByObf.containsKey(getFieldKey(obfName, obfType));
161 }
162
163 public boolean containsDeobfField(String deobfName, Type deobfType) {
164 return m_fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfType));
165 }
166
167 public void addFieldMapping(FieldMapping fieldMapping) {
168 String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType());
169 if (m_fieldsByObf.containsKey(obfKey)) {
170 throw new Error("Already have mapping for " + m_obfFullName + "." + obfKey);
171 }
172 String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType());
173 if (m_fieldsByDeobf.containsKey(deobfKey)) {
174 throw new Error("Already have mapping for " + m_deobfName + "." + deobfKey);
175 }
176 boolean obfWasAdded = m_fieldsByObf.put(obfKey, fieldMapping) == null;
177 assert (obfWasAdded);
178 boolean deobfWasAdded = m_fieldsByDeobf.put(deobfKey, fieldMapping) == null;
179 assert (deobfWasAdded);
180 assert (m_fieldsByObf.size() == m_fieldsByDeobf.size());
181 }
182
183 public void removeFieldMapping(FieldMapping fieldMapping) {
184 boolean obfWasRemoved = m_fieldsByObf.remove(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType())) != null;
185 assert (obfWasRemoved);
186 if (fieldMapping.getDeobfName() != null) {
187 boolean deobfWasRemoved = m_fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType())) != null;
188 assert (deobfWasRemoved);
189 }
190 }
191
192 public FieldMapping getFieldByObf(String obfName, Type obfType) {
193 return m_fieldsByObf.get(getFieldKey(obfName, obfType));
194 }
195
196 public FieldMapping getFieldByDeobf(String deobfName, Type obfType) {
197 return m_fieldsByDeobf.get(getFieldKey(deobfName, obfType));
198 }
199
200 public String getObfFieldName(String deobfName, Type obfType) {
201 FieldMapping fieldMapping = m_fieldsByDeobf.get(getFieldKey(deobfName, obfType));
202 if (fieldMapping != null) {
203 return fieldMapping.getObfName();
204 }
205 return null;
206 }
207
208 public String getDeobfFieldName(String obfName, Type obfType) {
209 FieldMapping fieldMapping = m_fieldsByObf.get(getFieldKey(obfName, obfType));
210 if (fieldMapping != null) {
211 return fieldMapping.getDeobfName();
212 }
213 return null;
214 }
215
216 private String getFieldKey(String name, Type type) {
217 if (name == null) {
218 throw new IllegalArgumentException("name cannot be null!");
219 }
220 if (type == null) {
221 throw new IllegalArgumentException("type cannot be null!");
222 }
223 return name + ":" + type;
224 }
225
226
227 public void setFieldName(String obfName, Type obfType, String deobfName) {
228 assert(deobfName != null);
229 FieldMapping fieldMapping = m_fieldsByObf.get(getFieldKey(obfName, obfType));
230 if (fieldMapping == null) {
231 fieldMapping = new FieldMapping(obfName, obfType, deobfName);
232 boolean obfWasAdded = m_fieldsByObf.put(getFieldKey(obfName, obfType), fieldMapping) == null;
233 assert (obfWasAdded);
234 } else {
235 boolean wasRemoved = m_fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), obfType)) != null;
236 assert (wasRemoved);
237 }
238 fieldMapping.setDeobfName(deobfName);
239 if (deobfName != null) {
240 boolean wasAdded = m_fieldsByDeobf.put(getFieldKey(deobfName, obfType), fieldMapping) == null;
241 assert (wasAdded);
242 }
243 }
244
245 public void setFieldObfNameAndType(String oldObfName, Type obfType, String newObfName, Type newObfType) {
246 assert(newObfName != null);
247 FieldMapping fieldMapping = m_fieldsByObf.remove(getFieldKey(oldObfName, obfType));
248 assert(fieldMapping != null);
249 fieldMapping.setObfName(newObfName);
250 fieldMapping.setObfType(newObfType);
251 boolean obfWasAdded = m_fieldsByObf.put(getFieldKey(newObfName, newObfType), fieldMapping) == null;
252 assert(obfWasAdded);
253 }
254
255
256 //// METHODS ////////
257
258 public Iterable<MethodMapping> methods() {
259 assert (m_methodsByObf.size() >= m_methodsByDeobf.size());
260 return m_methodsByObf.values();
261 }
262
263 public boolean containsObfMethod(String obfName, Signature obfSignature) {
264 return m_methodsByObf.containsKey(getMethodKey(obfName, obfSignature));
265 }
266
267 public boolean containsDeobfMethod(String deobfName, Signature obfSignature) {
268 return m_methodsByDeobf.containsKey(getMethodKey(deobfName, obfSignature));
269 }
270
271 public void addMethodMapping(MethodMapping methodMapping) {
272 String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature());
273 if (m_methodsByObf.containsKey(obfKey)) {
274 throw new Error("Already have mapping for " + m_obfFullName + "." + obfKey);
275 }
276 boolean wasAdded = m_methodsByObf.put(obfKey, methodMapping) == null;
277 assert (wasAdded);
278 if (methodMapping.getDeobfName() != null) {
279 String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature());
280 if (m_methodsByDeobf.containsKey(deobfKey)) {
281 throw new Error("Already have mapping for " + m_deobfName + "." + deobfKey);
282 }
283 boolean deobfWasAdded = m_methodsByDeobf.put(deobfKey, methodMapping) == null;
284 assert (deobfWasAdded);
285 }
286 assert (m_methodsByObf.size() >= m_methodsByDeobf.size());
287 }
288
289 public void removeMethodMapping(MethodMapping methodMapping) {
290 boolean obfWasRemoved = m_methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature())) != null;
291 assert (obfWasRemoved);
292 if (methodMapping.getDeobfName() != null) {
293 boolean deobfWasRemoved = m_methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null;
294 assert (deobfWasRemoved);
295 }
296 }
297
298 public MethodMapping getMethodByObf(String obfName, Signature obfSignature) {
299 return m_methodsByObf.get(getMethodKey(obfName, obfSignature));
300 }
301
302 public MethodMapping getMethodByDeobf(String deobfName, Signature obfSignature) {
303 return m_methodsByDeobf.get(getMethodKey(deobfName, obfSignature));
304 }
305
306 private String getMethodKey(String name, Signature signature) {
307 if (name == null) {
308 throw new IllegalArgumentException("name cannot be null!");
309 }
310 if (signature == null) {
311 throw new IllegalArgumentException("signature cannot be null!");
312 }
313 return name + signature;
314 }
315
316 public void setMethodName(String obfName, Signature obfSignature, String deobfName) {
317 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfName, obfSignature));
318 if (methodMapping == null) {
319 methodMapping = createMethodMapping(obfName, obfSignature);
320 } else if (methodMapping.getDeobfName() != null) {
321 boolean wasRemoved = m_methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null;
322 assert (wasRemoved);
323 }
324 methodMapping.setDeobfName(deobfName);
325 if (deobfName != null) {
326 boolean wasAdded = m_methodsByDeobf.put(getMethodKey(deobfName, obfSignature), methodMapping) == null;
327 assert (wasAdded);
328 }
329 }
330
331 public void setMethodObfNameAndSignature(String oldObfName, Signature obfSignature, String newObfName, Signature newObfSignature) {
332 assert(newObfName != null);
333 MethodMapping methodMapping = m_methodsByObf.remove(getMethodKey(oldObfName, obfSignature));
334 assert(methodMapping != null);
335 methodMapping.setObfName(newObfName);
336 methodMapping.setObfSignature(newObfSignature);
337 boolean obfWasAdded = m_methodsByObf.put(getMethodKey(newObfName, newObfSignature), methodMapping) == null;
338 assert(obfWasAdded);
339 }
340
341 //// ARGUMENTS ////////
342
343 public void setArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex, String argumentName) {
344 assert(argumentName != null);
345 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature));
346 if (methodMapping == null) {
347 methodMapping = createMethodMapping(obfMethodName, obfMethodSignature);
348 }
349 methodMapping.setArgumentName(argumentIndex, argumentName);
350 }
351
352 public void removeArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex) {
353 m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)).removeArgumentName(argumentIndex);
354 }
355
356 private MethodMapping createMethodMapping(String obfName, Signature obfSignature) {
357 MethodMapping methodMapping = new MethodMapping(obfName, obfSignature);
358 boolean wasAdded = m_methodsByObf.put(getMethodKey(obfName, obfSignature), methodMapping) == null;
359 assert (wasAdded);
360 return methodMapping;
361 }
362
363 @Override
364 public String toString() {
365 StringBuilder buf = new StringBuilder();
366 buf.append(m_obfFullName);
367 buf.append(" <-> ");
368 buf.append(m_deobfName);
369 buf.append("\n");
370 buf.append("Fields:\n");
371 for (FieldMapping fieldMapping : fields()) {
372 buf.append("\t");
373 buf.append(fieldMapping.getObfName());
374 buf.append(" <-> ");
375 buf.append(fieldMapping.getDeobfName());
376 buf.append("\n");
377 }
378 buf.append("Methods:\n");
379 for (MethodMapping methodMapping : m_methodsByObf.values()) {
380 buf.append(methodMapping.toString());
381 buf.append("\n");
382 }
383 buf.append("Inner Classes:\n");
384 for (ClassMapping classMapping : m_innerClassesByObfSimple.values()) {
385 buf.append("\t");
386 buf.append(classMapping.getObfSimpleName());
387 buf.append(" <-> ");
388 buf.append(classMapping.getDeobfName());
389 buf.append("\n");
390 }
391 return buf.toString();
392 }
393
394 @Override
395 public int compareTo(ClassMapping other) {
396 // sort by a, b, c, ... aa, ab, etc
397 if (m_obfFullName.length() != other.m_obfFullName.length()) {
398 return m_obfFullName.length() - other.m_obfFullName.length();
399 }
400 return m_obfFullName.compareTo(other.m_obfFullName);
401 }
402
403 public boolean renameObfClass(String oldObfClassName, String newObfClassName) {
404
405 // rename inner classes
406 for (ClassMapping innerClassMapping : new ArrayList<ClassMapping>(m_innerClassesByObfSimple.values())) {
407 if (innerClassMapping.renameObfClass(oldObfClassName, newObfClassName)) {
408 boolean wasRemoved = m_innerClassesByObfSimple.remove(oldObfClassName) != null;
409 assert (wasRemoved);
410 boolean wasAdded = m_innerClassesByObfSimple.put(newObfClassName, innerClassMapping) == null;
411 assert (wasAdded);
412 }
413 }
414
415 // rename field types
416 for (FieldMapping fieldMapping : new ArrayList<FieldMapping>(m_fieldsByObf.values())) {
417 String oldFieldKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType());
418 if (fieldMapping.renameObfClass(oldObfClassName, newObfClassName)) {
419 boolean wasRemoved = m_fieldsByObf.remove(oldFieldKey) != null;
420 assert (wasRemoved);
421 boolean wasAdded = m_fieldsByObf.put(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()), fieldMapping) == null;
422 assert (wasAdded);
423 }
424 }
425
426 // rename method signatures
427 for (MethodMapping methodMapping : new ArrayList<MethodMapping>(m_methodsByObf.values())) {
428 String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature());
429 if (methodMapping.renameObfClass(oldObfClassName, newObfClassName)) {
430 boolean wasRemoved = m_methodsByObf.remove(oldMethodKey) != null;
431 assert (wasRemoved);
432 boolean wasAdded = m_methodsByObf.put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()), methodMapping) == null;
433 assert (wasAdded);
434 }
435 }
436
437 if (m_obfFullName.equals(oldObfClassName)) {
438 // rename this class
439 m_obfFullName = newObfClassName;
440 return true;
441 }
442 return false;
443 }
444
445 public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) {
446 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfBehaviorEntry.getName(), obfBehaviorEntry.getSignature()));
447 if (methodMapping != null) {
448 return methodMapping.containsArgument(name);
449 }
450 return false;
451 }
452
453 public static boolean isSimpleClassName(String name) {
454 return name.indexOf('/') < 0 && name.indexOf('$') < 0;
455 }
456
457 public ClassEntry getObfEntry() {
458 return new ClassEntry(m_obfFullName);
459 }
460}
diff --git a/src/cuchaz/enigma/mapping/ClassNameReplacer.java b/src/cuchaz/enigma/mapping/ClassNameReplacer.java
new file mode 100644
index 00000000..f00d811e
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ClassNameReplacer.java
@@ -0,0 +1,15 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public interface ClassNameReplacer {
14 String replace(String className);
15}
diff --git a/src/cuchaz/enigma/mapping/ConstructorEntry.java b/src/cuchaz/enigma/mapping/ConstructorEntry.java
new file mode 100644
index 00000000..7cde8f65
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ConstructorEntry.java
@@ -0,0 +1,116 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..3c94a95a
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Entry.java
@@ -0,0 +1,18 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.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/EntryFactory.java b/src/cuchaz/enigma/mapping/EntryFactory.java
new file mode 100644
index 00000000..03d97ba1
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/EntryFactory.java
@@ -0,0 +1,166 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.CtConstructor;
16import javassist.CtField;
17import javassist.CtMethod;
18import javassist.bytecode.Descriptor;
19import javassist.expr.ConstructorCall;
20import javassist.expr.FieldAccess;
21import javassist.expr.MethodCall;
22import javassist.expr.NewExpr;
23
24import cuchaz.enigma.analysis.JarIndex;
25
26public class EntryFactory {
27
28 public static ClassEntry getClassEntry(CtClass c) {
29 return new ClassEntry(Descriptor.toJvmName(c.getName()));
30 }
31
32 public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) {
33 ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfFullName());
34 return obfClassEntry.buildClassEntry(jarIndex.getObfClassChain(obfClassEntry));
35 }
36
37 private static ClassEntry getObfClassEntry(ClassMapping classMapping) {
38 return new ClassEntry(classMapping.getObfFullName());
39 }
40
41 public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) {
42 return new ClassEntry(classMapping.getDeobfName());
43 }
44
45 public static ClassEntry getSuperclassEntry(CtClass c) {
46 return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
47 }
48
49 public static FieldEntry getFieldEntry(CtField field) {
50 return new FieldEntry(
51 getClassEntry(field.getDeclaringClass()),
52 field.getName(),
53 new Type(field.getFieldInfo().getDescriptor())
54 );
55 }
56
57 public static FieldEntry getFieldEntry(FieldAccess call) {
58 return new FieldEntry(
59 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
60 call.getFieldName(),
61 new Type(call.getSignature())
62 );
63 }
64
65 public static FieldEntry getFieldEntry(String className, String name, String type) {
66 return new FieldEntry(new ClassEntry(className), name, new Type(type));
67 }
68
69 public static FieldEntry getObfFieldEntry(ClassMapping classMapping, FieldMapping fieldMapping) {
70 return new FieldEntry(
71 getObfClassEntry(classMapping),
72 fieldMapping.getObfName(),
73 fieldMapping.getObfType()
74 );
75 }
76
77 public static MethodEntry getMethodEntry(CtMethod method) {
78 return new MethodEntry(
79 getClassEntry(method.getDeclaringClass()),
80 method.getName(),
81 new Signature(method.getMethodInfo().getDescriptor())
82 );
83 }
84
85 public static MethodEntry getMethodEntry(MethodCall call) {
86 return new MethodEntry(
87 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
88 call.getMethodName(),
89 new Signature(call.getSignature())
90 );
91 }
92
93 public static ConstructorEntry getConstructorEntry(CtConstructor constructor) {
94 if (constructor.isClassInitializer()) {
95 return new ConstructorEntry(
96 getClassEntry(constructor.getDeclaringClass())
97 );
98 } else {
99 return new ConstructorEntry(
100 getClassEntry(constructor.getDeclaringClass()),
101 new Signature(constructor.getMethodInfo().getDescriptor())
102 );
103 }
104 }
105
106 public static ConstructorEntry getConstructorEntry(ConstructorCall call) {
107 return new ConstructorEntry(
108 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
109 new Signature(call.getSignature())
110 );
111 }
112
113 public static ConstructorEntry getConstructorEntry(NewExpr call) {
114 return new ConstructorEntry(
115 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
116 new Signature(call.getSignature())
117 );
118 }
119
120 public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) {
121 if (behavior instanceof CtMethod) {
122 return getMethodEntry((CtMethod)behavior);
123 } else if (behavior instanceof CtConstructor) {
124 return getConstructorEntry((CtConstructor)behavior);
125 }
126 throw new Error("behavior is neither Method nor Constructor!");
127 }
128
129 public static BehaviorEntry getBehaviorEntry(String className, String behaviorName, String behaviorSignature) {
130 return getBehaviorEntry(new ClassEntry(className), behaviorName, new Signature(behaviorSignature));
131 }
132
133 public static BehaviorEntry getBehaviorEntry(String className, String behaviorName) {
134 return getBehaviorEntry(new ClassEntry(className), behaviorName);
135 }
136
137 public static BehaviorEntry getBehaviorEntry(String className) {
138 return new ConstructorEntry(new ClassEntry(className));
139 }
140
141 public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName, Signature behaviorSignature) {
142 if (behaviorName.equals("<init>")) {
143 return new ConstructorEntry(classEntry, behaviorSignature);
144 } else if(behaviorName.equals("<clinit>")) {
145 return new ConstructorEntry(classEntry);
146 } else {
147 return new MethodEntry(classEntry, behaviorName, behaviorSignature);
148 }
149 }
150
151 public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName) {
152 if(behaviorName.equals("<clinit>")) {
153 return new ConstructorEntry(classEntry);
154 } else {
155 throw new IllegalArgumentException("Only class initializers don't have signatures");
156 }
157 }
158
159 public static BehaviorEntry getObfBehaviorEntry(ClassEntry classEntry, MethodMapping methodMapping) {
160 return getBehaviorEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature());
161 }
162
163 public static BehaviorEntry getObfBehaviorEntry(ClassMapping classMapping, MethodMapping methodMapping) {
164 return getObfBehaviorEntry(getObfClassEntry(classMapping), methodMapping);
165 }
166}
diff --git a/src/cuchaz/enigma/mapping/EntryPair.java b/src/cuchaz/enigma/mapping/EntryPair.java
new file mode 100644
index 00000000..82b28cd1
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/EntryPair.java
@@ -0,0 +1,22 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..e4a74f4f
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/FieldEntry.java
@@ -0,0 +1,99 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class FieldEntry implements Entry, Serializable {
18
19 private static final long serialVersionUID = 3004663582802885451L;
20
21 private ClassEntry m_classEntry;
22 private String m_name;
23 private Type m_type;
24
25 // NOTE: this argument order is important for the MethodReader/MethodWriter
26 public FieldEntry(ClassEntry classEntry, String name, Type type) {
27 if (classEntry == null) {
28 throw new IllegalArgumentException("Class cannot be null!");
29 }
30 if (name == null) {
31 throw new IllegalArgumentException("Field name cannot be null!");
32 }
33 if (type == null) {
34 throw new IllegalArgumentException("Field type cannot be null!");
35 }
36
37 m_classEntry = classEntry;
38 m_name = name;
39 m_type = type;
40 }
41
42 public FieldEntry(FieldEntry other) {
43 this(other, new ClassEntry(other.m_classEntry));
44 }
45
46 public FieldEntry(FieldEntry other, ClassEntry newClassEntry) {
47 m_classEntry = newClassEntry;
48 m_name = other.m_name;
49 m_type = other.m_type;
50 }
51
52 @Override
53 public ClassEntry getClassEntry() {
54 return m_classEntry;
55 }
56
57 @Override
58 public String getName() {
59 return m_name;
60 }
61
62 @Override
63 public String getClassName() {
64 return m_classEntry.getName();
65 }
66
67 public Type getType() {
68 return m_type;
69 }
70
71 @Override
72 public FieldEntry cloneToNewClass(ClassEntry classEntry) {
73 return new FieldEntry(this, classEntry);
74 }
75
76 @Override
77 public int hashCode() {
78 return Util.combineHashesOrdered(m_classEntry, m_name, m_type);
79 }
80
81 @Override
82 public boolean equals(Object other) {
83 if (other instanceof FieldEntry) {
84 return equals((FieldEntry)other);
85 }
86 return false;
87 }
88
89 public boolean equals(FieldEntry other) {
90 return m_classEntry.equals(other.m_classEntry)
91 && m_name.equals(other.m_name)
92 && m_type.equals(other.m_type);
93 }
94
95 @Override
96 public String toString() {
97 return m_classEntry.getName() + "." + m_name + ":" + m_type;
98 }
99}
diff --git a/src/cuchaz/enigma/mapping/FieldMapping.java b/src/cuchaz/enigma/mapping/FieldMapping.java
new file mode 100644
index 00000000..28557406
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/FieldMapping.java
@@ -0,0 +1,89 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15public class FieldMapping implements Serializable, Comparable<FieldMapping>, MemberMapping<FieldEntry> {
16
17 private static final long serialVersionUID = 8610742471440861315L;
18
19 private String m_obfName;
20 private String m_deobfName;
21 private Type m_obfType;
22
23 public FieldMapping(String obfName, Type obfType, String deobfName) {
24 m_obfName = obfName;
25 m_deobfName = NameValidator.validateFieldName(deobfName);
26 m_obfType = obfType;
27 }
28
29 public FieldMapping(FieldMapping other, ClassNameReplacer obfClassNameReplacer) {
30 m_obfName = other.m_obfName;
31 m_deobfName = other.m_deobfName;
32 m_obfType = new Type(other.m_obfType, obfClassNameReplacer);
33 }
34
35 @Override
36 public String getObfName() {
37 return m_obfName;
38 }
39
40 public void setObfName(String val) {
41 m_obfName = NameValidator.validateFieldName(val);
42 }
43
44 public String getDeobfName() {
45 return m_deobfName;
46 }
47
48 public void setDeobfName(String val) {
49 m_deobfName = NameValidator.validateFieldName(val);
50 }
51
52 public Type getObfType() {
53 return m_obfType;
54 }
55
56 public void setObfType(Type val) {
57 m_obfType = val;
58 }
59
60 @Override
61 public int compareTo(FieldMapping other) {
62 return (m_obfName + m_obfType).compareTo(other.m_obfName + other.m_obfType);
63 }
64
65 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
66
67 // rename obf classes in the type
68 Type newType = new Type(m_obfType, new ClassNameReplacer() {
69 @Override
70 public String replace(String className) {
71 if (className.equals(oldObfClassName)) {
72 return newObfClassName;
73 }
74 return null;
75 }
76 });
77
78 if (!newType.equals(m_obfType)) {
79 m_obfType = newType;
80 return true;
81 }
82 return false;
83 }
84
85 @Override
86 public FieldEntry getObfEntry(ClassEntry classEntry) {
87 return new FieldEntry(classEntry, m_obfName, new Type(m_obfType));
88 }
89}
diff --git a/src/cuchaz/enigma/mapping/IllegalNameException.java b/src/cuchaz/enigma/mapping/IllegalNameException.java
new file mode 100644
index 00000000..f62df7c4
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/IllegalNameException.java
@@ -0,0 +1,44 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public class IllegalNameException extends RuntimeException {
14
15 private static final long serialVersionUID = -2279910052561114323L;
16
17 private String m_name;
18 private String m_reason;
19
20 public IllegalNameException(String name) {
21 this(name, null);
22 }
23
24 public IllegalNameException(String name, String reason) {
25 m_name = name;
26 m_reason = reason;
27 }
28
29 public String getReason() {
30 return m_reason;
31 }
32
33 @Override
34 public String getMessage() {
35 StringBuilder buf = new StringBuilder();
36 buf.append("Illegal name: ");
37 buf.append(m_name);
38 if (m_reason != null) {
39 buf.append(" because ");
40 buf.append(m_reason);
41 }
42 return buf.toString();
43 }
44}
diff --git a/src/cuchaz/enigma/mapping/MappingParseException.java b/src/cuchaz/enigma/mapping/MappingParseException.java
new file mode 100644
index 00000000..73fca94a
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingParseException.java
@@ -0,0 +1,29 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.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 00000000..11ed5d0c
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Mappings.java
@@ -0,0 +1,216 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.ArrayList;
15import java.util.Collection;
16import java.util.List;
17import java.util.Map;
18import java.util.Set;
19
20import com.google.common.collect.Lists;
21import com.google.common.collect.Maps;
22import com.google.common.collect.Sets;
23
24import cuchaz.enigma.analysis.TranslationIndex;
25
26public class Mappings implements Serializable {
27
28 private static final long serialVersionUID = 4649790259460259026L;
29
30 protected Map<String,ClassMapping> m_classesByObf;
31 protected Map<String,ClassMapping> m_classesByDeobf;
32
33 public Mappings() {
34 m_classesByObf = Maps.newHashMap();
35 m_classesByDeobf = Maps.newHashMap();
36 }
37
38 public Mappings(Iterable<ClassMapping> classes) {
39 this();
40
41 for (ClassMapping classMapping : classes) {
42 m_classesByObf.put(classMapping.getObfFullName(), classMapping);
43 if (classMapping.getDeobfName() != null) {
44 m_classesByDeobf.put(classMapping.getDeobfName(), classMapping);
45 }
46 }
47 }
48
49 public Collection<ClassMapping> classes() {
50 assert (m_classesByObf.size() >= m_classesByDeobf.size());
51 return m_classesByObf.values();
52 }
53
54 public void addClassMapping(ClassMapping classMapping) {
55 if (m_classesByObf.containsKey(classMapping.getObfFullName())) {
56 throw new Error("Already have mapping for " + classMapping.getObfFullName());
57 }
58 boolean obfWasAdded = m_classesByObf.put(classMapping.getObfFullName(), classMapping) == null;
59 assert (obfWasAdded);
60 if (classMapping.getDeobfName() != null) {
61 if (m_classesByDeobf.containsKey(classMapping.getDeobfName())) {
62 throw new Error("Already have mapping for " + classMapping.getDeobfName());
63 }
64 boolean deobfWasAdded = m_classesByDeobf.put(classMapping.getDeobfName(), classMapping) == null;
65 assert (deobfWasAdded);
66 }
67 }
68
69 public void removeClassMapping(ClassMapping classMapping) {
70 boolean obfWasRemoved = m_classesByObf.remove(classMapping.getObfFullName()) != null;
71 assert (obfWasRemoved);
72 if (classMapping.getDeobfName() != null) {
73 boolean deobfWasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
74 assert (deobfWasRemoved);
75 }
76 }
77
78 public ClassMapping getClassByObf(ClassEntry entry) {
79 return getClassByObf(entry.getName());
80 }
81
82 public ClassMapping getClassByObf(String obfName) {
83 return m_classesByObf.get(obfName);
84 }
85
86 public ClassMapping getClassByDeobf(ClassEntry entry) {
87 return getClassByDeobf(entry.getName());
88 }
89
90 public ClassMapping getClassByDeobf(String deobfName) {
91 return m_classesByDeobf.get(deobfName);
92 }
93
94 public void setClassDeobfName(ClassMapping classMapping, String deobfName) {
95 if (classMapping.getDeobfName() != null) {
96 boolean wasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
97 assert (wasRemoved);
98 }
99 classMapping.setDeobfName(deobfName);
100 if (deobfName != null) {
101 boolean wasAdded = m_classesByDeobf.put(deobfName, classMapping) == null;
102 assert (wasAdded);
103 }
104 }
105
106 public Translator getTranslator(TranslationDirection direction, TranslationIndex index) {
107 switch (direction) {
108 case Deobfuscating:
109
110 return new Translator(direction, m_classesByObf, index);
111
112 case Obfuscating:
113
114 // fill in the missing deobf class entries with obf entries
115 Map<String,ClassMapping> classes = Maps.newHashMap();
116 for (ClassMapping classMapping : classes()) {
117 if (classMapping.getDeobfName() != null) {
118 classes.put(classMapping.getDeobfName(), classMapping);
119 } else {
120 classes.put(classMapping.getObfFullName(), classMapping);
121 }
122 }
123
124 // translate the translation index
125 // NOTE: this isn't actually recursive
126 TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.Deobfuscating, index));
127
128 return new Translator(direction, classes, deobfIndex);
129
130 default:
131 throw new Error("Invalid translation direction!");
132 }
133 }
134
135 @Override
136 public String toString() {
137 StringBuilder buf = new StringBuilder();
138 for (ClassMapping classMapping : m_classesByObf.values()) {
139 buf.append(classMapping.toString());
140 buf.append("\n");
141 }
142 return buf.toString();
143 }
144
145 public void renameObfClass(String oldObfName, String newObfName) {
146 for (ClassMapping classMapping : new ArrayList<ClassMapping>(classes())) {
147 if (classMapping.renameObfClass(oldObfName, newObfName)) {
148 boolean wasRemoved = m_classesByObf.remove(oldObfName) != null;
149 assert (wasRemoved);
150 boolean wasAdded = m_classesByObf.put(newObfName, classMapping) == null;
151 assert (wasAdded);
152 }
153 }
154 }
155
156 public Set<String> getAllObfClassNames() {
157 final Set<String> classNames = Sets.newHashSet();
158 for (ClassMapping classMapping : classes()) {
159
160 // add the class name
161 classNames.add(classMapping.getObfFullName());
162
163 // add classes from method signatures
164 for (MethodMapping methodMapping : classMapping.methods()) {
165 for (Type type : methodMapping.getObfSignature().types()) {
166 if (type.hasClass()) {
167 classNames.add(type.getClassEntry().getClassName());
168 }
169 }
170 }
171 }
172 return classNames;
173 }
174
175 public boolean containsDeobfClass(String deobfName) {
176 return m_classesByDeobf.containsKey(deobfName);
177 }
178
179 public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName, Type obfType) {
180 ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
181 if (classMapping != null) {
182 return classMapping.containsDeobfField(deobfName, obfType);
183 }
184 return false;
185 }
186
187 public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, Signature deobfSignature) {
188 ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
189 if (classMapping != null) {
190 return classMapping.containsDeobfMethod(deobfName, deobfSignature);
191 }
192 return false;
193 }
194
195 public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) {
196 ClassMapping classMapping = m_classesByObf.get(obfBehaviorEntry.getClassName());
197 if (classMapping != null) {
198 return classMapping.containsArgument(obfBehaviorEntry, name);
199 }
200 return false;
201 }
202
203 public List<ClassMapping> getClassMappingChain(ClassEntry obfClass) {
204 List<ClassMapping> mappingChain = Lists.newArrayList();
205 ClassMapping classMapping = null;
206 for (ClassEntry obfClassEntry : obfClass.getClassChain()) {
207 if (mappingChain.isEmpty()) {
208 classMapping = m_classesByObf.get(obfClassEntry.getName());
209 } else if (classMapping != null) {
210 classMapping = classMapping.getInnerClassByObfSimple(obfClassEntry.getInnermostClassName());
211 }
212 mappingChain.add(classMapping);
213 }
214 return mappingChain;
215 }
216}
diff --git a/src/cuchaz/enigma/mapping/MappingsChecker.java b/src/cuchaz/enigma/mapping/MappingsChecker.java
new file mode 100644
index 00000000..b25ea3cf
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingsChecker.java
@@ -0,0 +1,107 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.util.Map;
14
15import com.google.common.collect.Lists;
16import com.google.common.collect.Maps;
17
18import cuchaz.enigma.analysis.JarIndex;
19import cuchaz.enigma.analysis.RelatedMethodChecker;
20
21
22public class MappingsChecker {
23
24 private JarIndex m_index;
25 private RelatedMethodChecker m_relatedMethodChecker;
26 private Map<ClassEntry,ClassMapping> m_droppedClassMappings;
27 private Map<ClassEntry,ClassMapping> m_droppedInnerClassMappings;
28 private Map<FieldEntry,FieldMapping> m_droppedFieldMappings;
29 private Map<BehaviorEntry,MethodMapping> m_droppedMethodMappings;
30
31 public MappingsChecker(JarIndex index) {
32 m_index = index;
33 m_relatedMethodChecker = new RelatedMethodChecker(m_index);
34 m_droppedClassMappings = Maps.newHashMap();
35 m_droppedInnerClassMappings = Maps.newHashMap();
36 m_droppedFieldMappings = Maps.newHashMap();
37 m_droppedMethodMappings = Maps.newHashMap();
38 }
39
40 public RelatedMethodChecker getRelatedMethodChecker() {
41 return m_relatedMethodChecker;
42 }
43
44 public Map<ClassEntry,ClassMapping> getDroppedClassMappings() {
45 return m_droppedClassMappings;
46 }
47
48 public Map<ClassEntry,ClassMapping> getDroppedInnerClassMappings() {
49 return m_droppedInnerClassMappings;
50 }
51
52 public Map<FieldEntry,FieldMapping> getDroppedFieldMappings() {
53 return m_droppedFieldMappings;
54 }
55
56 public Map<BehaviorEntry,MethodMapping> getDroppedMethodMappings() {
57 return m_droppedMethodMappings;
58 }
59
60 public void dropBrokenMappings(Mappings mappings) {
61 for (ClassMapping classMapping : Lists.newArrayList(mappings.classes())) {
62 if (!checkClassMapping(classMapping)) {
63 mappings.removeClassMapping(classMapping);
64 m_droppedClassMappings.put(EntryFactory.getObfClassEntry(m_index, classMapping), classMapping);
65 }
66 }
67 }
68
69 private boolean checkClassMapping(ClassMapping classMapping) {
70
71 // check the class
72 ClassEntry classEntry = EntryFactory.getObfClassEntry(m_index, classMapping);
73 if (!m_index.getObfClassEntries().contains(classEntry)) {
74 return false;
75 }
76
77 // check the fields
78 for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) {
79 FieldEntry obfFieldEntry = EntryFactory.getObfFieldEntry(classMapping, fieldMapping);
80 if (!m_index.containsObfField(obfFieldEntry)) {
81 classMapping.removeFieldMapping(fieldMapping);
82 m_droppedFieldMappings.put(obfFieldEntry, fieldMapping);
83 }
84 }
85
86 // check methods
87 for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) {
88 BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping);
89 if (!m_index.containsObfBehavior(obfBehaviorEntry)) {
90 classMapping.removeMethodMapping(methodMapping);
91 m_droppedMethodMappings.put(obfBehaviorEntry, methodMapping);
92 }
93
94 m_relatedMethodChecker.checkMethod(classEntry, methodMapping);
95 }
96
97 // check inner classes
98 for (ClassMapping innerClassMapping : Lists.newArrayList(classMapping.innerClasses())) {
99 if (!checkClassMapping(innerClassMapping)) {
100 classMapping.removeInnerClassMapping(innerClassMapping);
101 m_droppedInnerClassMappings.put(EntryFactory.getObfClassEntry(m_index, innerClassMapping), innerClassMapping);
102 }
103 }
104
105 return true;
106 }
107}
diff --git a/src/cuchaz/enigma/mapping/MappingsReader.java b/src/cuchaz/enigma/mapping/MappingsReader.java
new file mode 100644
index 00000000..0a4b117e
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingsReader.java
@@ -0,0 +1,134 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.BufferedReader;
14import java.io.IOException;
15import java.io.Reader;
16import java.util.Deque;
17
18import com.google.common.collect.Queues;
19
20public class MappingsReader {
21
22 public Mappings read(Reader in)
23 throws IOException, MappingParseException {
24 return read(new BufferedReader(in));
25 }
26
27 public Mappings read(BufferedReader in)
28 throws IOException, MappingParseException {
29 Mappings mappings = new Mappings();
30 Deque<Object> mappingStack = Queues.newArrayDeque();
31
32 int lineNumber = 0;
33 String line = null;
34 while ( (line = in.readLine()) != null) {
35 lineNumber++;
36
37 // strip comments
38 int commentPos = line.indexOf('#');
39 if (commentPos >= 0) {
40 line = line.substring(0, commentPos);
41 }
42
43 // skip blank lines
44 if (line.trim().length() <= 0) {
45 continue;
46 }
47
48 // get the indent of this line
49 int indent = 0;
50 for (int i = 0; i < line.length(); i++) {
51 if (line.charAt(i) != '\t') {
52 break;
53 }
54 indent++;
55 }
56
57 // handle stack pops
58 while (indent < mappingStack.size()) {
59 mappingStack.pop();
60 }
61
62 String[] parts = line.trim().split("\\s");
63 try {
64 // read the first token
65 String token = parts[0];
66
67 if (token.equalsIgnoreCase("CLASS")) {
68 ClassMapping classMapping;
69 if (indent <= 0) {
70 // outer class
71 classMapping = readClass(parts, false);
72 mappings.addClassMapping(classMapping);
73 } else {
74
75 // inner class
76 if (!(mappingStack.peek() instanceof ClassMapping)) {
77 throw new MappingParseException(lineNumber, "Unexpected CLASS entry here!");
78 }
79
80 classMapping = readClass(parts, true);
81 ((ClassMapping)mappingStack.peek()).addInnerClassMapping(classMapping);
82 }
83 mappingStack.push(classMapping);
84 } else if (token.equalsIgnoreCase("FIELD")) {
85 if (mappingStack.isEmpty() || ! (mappingStack.peek() instanceof ClassMapping)) {
86 throw new MappingParseException(lineNumber, "Unexpected FIELD entry here!");
87 }
88 ((ClassMapping)mappingStack.peek()).addFieldMapping(readField(parts));
89 } else if (token.equalsIgnoreCase("METHOD")) {
90 if (mappingStack.isEmpty() || ! (mappingStack.peek() instanceof ClassMapping)) {
91 throw new MappingParseException(lineNumber, "Unexpected METHOD entry here!");
92 }
93 MethodMapping methodMapping = readMethod(parts);
94 ((ClassMapping)mappingStack.peek()).addMethodMapping(methodMapping);
95 mappingStack.push(methodMapping);
96 } else if (token.equalsIgnoreCase("ARG")) {
97 if (mappingStack.isEmpty() || ! (mappingStack.peek() instanceof MethodMapping)) {
98 throw new MappingParseException(lineNumber, "Unexpected ARG entry here!");
99 }
100 ((MethodMapping)mappingStack.peek()).addArgumentMapping(readArgument(parts));
101 }
102 } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) {
103 throw new MappingParseException(lineNumber, "Malformed line:\n" + line);
104 }
105 }
106
107 return mappings;
108 }
109
110 private ArgumentMapping readArgument(String[] parts) {
111 return new ArgumentMapping(Integer.parseInt(parts[1]), parts[2]);
112 }
113
114 private ClassMapping readClass(String[] parts, boolean makeSimple) {
115 if (parts.length == 2) {
116 return new ClassMapping(parts[1]);
117 } else {
118 return new ClassMapping(parts[1], parts[2]);
119 }
120 }
121
122 /* TEMP */
123 protected FieldMapping readField(String[] parts) {
124 return new FieldMapping(parts[1], new Type(parts[3]), parts[2]);
125 }
126
127 private MethodMapping readMethod(String[] parts) {
128 if (parts.length == 3) {
129 return new MethodMapping(parts[1], new Signature(parts[2]));
130 } else {
131 return new MethodMapping(parts[1], new Signature(parts[3]), parts[2]);
132 }
133 }
134}
diff --git a/src/cuchaz/enigma/mapping/MappingsRenamer.java b/src/cuchaz/enigma/mapping/MappingsRenamer.java
new file mode 100644
index 00000000..47e5738c
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingsRenamer.java
@@ -0,0 +1,237 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.IOException;
14import java.io.ObjectOutputStream;
15import java.io.OutputStream;
16import java.util.List;
17import java.util.Set;
18import java.util.zip.GZIPOutputStream;
19
20import cuchaz.enigma.analysis.JarIndex;
21
22public class MappingsRenamer {
23
24 private JarIndex m_index;
25 private Mappings m_mappings;
26
27 public MappingsRenamer(JarIndex index, Mappings mappings) {
28 m_index = index;
29 m_mappings = mappings;
30 }
31
32 public void setClassName(ClassEntry obf, String deobfName) {
33
34 deobfName = NameValidator.validateClassName(deobfName, !obf.isInnerClass());
35
36 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obf);
37 if (mappingChain.size() == 1) {
38
39 if (deobfName != null) {
40 // make sure we don't rename to an existing obf or deobf class
41 if (m_mappings.containsDeobfClass(deobfName) || m_index.containsObfClass(new ClassEntry(deobfName))) {
42 throw new IllegalNameException(deobfName, "There is already a class with that name");
43 }
44 }
45
46 ClassMapping classMapping = mappingChain.get(0);
47 m_mappings.setClassDeobfName(classMapping, deobfName);
48
49 } else {
50
51 ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2);
52
53 if (deobfName != null) {
54 // make sure we don't rename to an existing obf or deobf inner class
55 if (outerClassMapping.hasInnerClassByDeobf(deobfName) || outerClassMapping.hasInnerClassByObfSimple(deobfName)) {
56 throw new IllegalNameException(deobfName, "There is already a class with that name");
57 }
58 }
59
60 outerClassMapping.setInnerClassName(obf, deobfName);
61 }
62 }
63
64 public void removeClassMapping(ClassEntry obf) {
65 setClassName(obf, null);
66 }
67
68 public void markClassAsDeobfuscated(ClassEntry obf) {
69 String deobfName = obf.isInnerClass() ? obf.getInnermostClassName() : obf.getName();
70 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obf);
71 if (mappingChain.size() == 1) {
72 ClassMapping classMapping = mappingChain.get(0);
73 m_mappings.setClassDeobfName(classMapping, deobfName);
74 } else {
75 ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2);
76 outerClassMapping.setInnerClassName(obf, deobfName);
77 }
78 }
79
80 public void setFieldName(FieldEntry obf, String deobfName) {
81 deobfName = NameValidator.validateFieldName(deobfName);
82 FieldEntry targetEntry = new FieldEntry(obf.getClassEntry(), deobfName, obf.getType());
83 if (m_mappings.containsDeobfField(obf.getClassEntry(), deobfName, obf.getType()) || m_index.containsObfField(targetEntry)) {
84 throw new IllegalNameException(deobfName, "There is already a field with that name");
85 }
86
87 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
88 classMapping.setFieldName(obf.getName(), obf.getType(), deobfName);
89 }
90
91 public void removeFieldMapping(FieldEntry obf) {
92 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
93 classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getType()));
94 }
95
96 public void markFieldAsDeobfuscated(FieldEntry obf) {
97 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
98 classMapping.setFieldName(obf.getName(), obf.getType(), obf.getName());
99 }
100
101 public void setMethodTreeName(MethodEntry obf, String deobfName) {
102 Set<MethodEntry> implementations = m_index.getRelatedMethodImplementations(obf);
103
104 deobfName = NameValidator.validateMethodName(deobfName);
105 for (MethodEntry entry : implementations) {
106 Signature deobfSignature = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateSignature(obf.getSignature());
107 MethodEntry targetEntry = new MethodEntry(entry.getClassEntry(), deobfName, deobfSignature);
108 if (m_mappings.containsDeobfMethod(entry.getClassEntry(), deobfName, entry.getSignature()) || m_index.containsObfBehavior(targetEntry)) {
109 String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(entry.getClassName());
110 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName);
111 }
112 }
113
114 for (MethodEntry entry : implementations) {
115 setMethodName(entry, deobfName);
116 }
117 }
118
119 public void setMethodName(MethodEntry obf, String deobfName) {
120 deobfName = NameValidator.validateMethodName(deobfName);
121 MethodEntry targetEntry = new MethodEntry(obf.getClassEntry(), deobfName, obf.getSignature());
122 if (m_mappings.containsDeobfMethod(obf.getClassEntry(), deobfName, obf.getSignature()) || m_index.containsObfBehavior(targetEntry)) {
123 String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(obf.getClassName());
124 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName);
125 }
126
127 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
128 classMapping.setMethodName(obf.getName(), obf.getSignature(), deobfName);
129 }
130
131 public void removeMethodTreeMapping(MethodEntry obf) {
132 for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) {
133 removeMethodMapping(implementation);
134 }
135 }
136
137 public void removeMethodMapping(MethodEntry obf) {
138 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
139 classMapping.setMethodName(obf.getName(), obf.getSignature(), null);
140 }
141
142 public void markMethodTreeAsDeobfuscated(MethodEntry obf) {
143 for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) {
144 markMethodAsDeobfuscated(implementation);
145 }
146 }
147
148 public void markMethodAsDeobfuscated(MethodEntry obf) {
149 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
150 classMapping.setMethodName(obf.getName(), obf.getSignature(), obf.getName());
151 }
152
153 public void setArgumentName(ArgumentEntry obf, String deobfName) {
154 deobfName = NameValidator.validateArgumentName(deobfName);
155 // NOTE: don't need to check arguments for name collisions with names determined by Procyon
156 if (m_mappings.containsArgument(obf.getBehaviorEntry(), deobfName)) {
157 throw new IllegalNameException(deobfName, "There is already an argument with that name");
158 }
159
160 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
161 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), deobfName);
162 }
163
164 public void removeArgumentMapping(ArgumentEntry obf) {
165 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
166 classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex());
167 }
168
169 public void markArgumentAsDeobfuscated(ArgumentEntry obf) {
170 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
171 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), obf.getName());
172 }
173
174 public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) {
175 classMapping.removeFieldMapping(fieldMapping);
176 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
177 if (!targetClassMapping.containsObfField(fieldMapping.getObfName(), fieldMapping.getObfType())) {
178 if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfType())) {
179 targetClassMapping.addFieldMapping(fieldMapping);
180 return true;
181 } else {
182 System.err.println("WARNING: deobf field was already there: " + obfClass + "." + fieldMapping.getDeobfName());
183 }
184 }
185 return false;
186 }
187
188 public boolean moveMethodToObfClass(ClassMapping classMapping, MethodMapping methodMapping, ClassEntry obfClass) {
189 classMapping.removeMethodMapping(methodMapping);
190 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
191 if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfSignature())) {
192 if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfSignature())) {
193 targetClassMapping.addMethodMapping(methodMapping);
194 return true;
195 } else {
196 System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfSignature());
197 }
198 }
199 return false;
200 }
201
202 public void write(OutputStream out) throws IOException {
203 // TEMP: just use the object output for now. We can find a more efficient storage format later
204 GZIPOutputStream gzipout = new GZIPOutputStream(out);
205 ObjectOutputStream oout = new ObjectOutputStream(gzipout);
206 oout.writeObject(this);
207 gzipout.finish();
208 }
209
210 private ClassMapping getOrCreateClassMapping(ClassEntry obfClassEntry) {
211 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obfClassEntry);
212 return mappingChain.get(mappingChain.size() - 1);
213 }
214
215 private List<ClassMapping> getOrCreateClassMappingChain(ClassEntry obfClassEntry) {
216 List<ClassEntry> classChain = obfClassEntry.getClassChain();
217 List<ClassMapping> mappingChain = m_mappings.getClassMappingChain(obfClassEntry);
218 for (int i=0; i<classChain.size(); i++) {
219 ClassEntry classEntry = classChain.get(i);
220 ClassMapping classMapping = mappingChain.get(i);
221 if (classMapping == null) {
222
223 // create it
224 classMapping = new ClassMapping(classEntry.getName());
225 mappingChain.set(i, classMapping);
226
227 // add it to the right parent
228 if (i == 0) {
229 m_mappings.addClassMapping(classMapping);
230 } else {
231 mappingChain.get(i-1).addInnerClassMapping(classMapping);
232 }
233 }
234 }
235 return mappingChain;
236 }
237}
diff --git a/src/cuchaz/enigma/mapping/MappingsWriter.java b/src/cuchaz/enigma/mapping/MappingsWriter.java
new file mode 100644
index 00000000..1ebefefa
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingsWriter.java
@@ -0,0 +1,88 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.IOException;
14import java.io.PrintWriter;
15import java.io.Writer;
16import java.util.ArrayList;
17import java.util.Collections;
18import java.util.List;
19
20public class MappingsWriter {
21
22 public void write(Writer out, Mappings mappings) throws IOException {
23 write(new PrintWriter(out), mappings);
24 }
25
26 public void write(PrintWriter out, Mappings mappings) throws IOException {
27 for (ClassMapping classMapping : sorted(mappings.classes())) {
28 write(out, classMapping, 0);
29 }
30 }
31
32 private void write(PrintWriter out, ClassMapping classMapping, int depth) throws IOException {
33 if (classMapping.getDeobfName() == null) {
34 out.format("%sCLASS %s\n", getIndent(depth), classMapping.getObfFullName());
35 } else {
36 out.format("%sCLASS %s %s\n", getIndent(depth), classMapping.getObfFullName(), classMapping.getDeobfName());
37 }
38
39 for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) {
40 write(out, innerClassMapping, depth + 1);
41 }
42
43 for (FieldMapping fieldMapping : sorted(classMapping.fields())) {
44 write(out, fieldMapping, depth + 1);
45 }
46
47 for (MethodMapping methodMapping : sorted(classMapping.methods())) {
48 write(out, methodMapping, depth + 1);
49 }
50 }
51
52 private void write(PrintWriter out, FieldMapping fieldMapping, int depth) throws IOException {
53 out.format("%sFIELD %s %s %s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName(), fieldMapping.getObfType().toString());
54 }
55
56 private void write(PrintWriter out, MethodMapping methodMapping, int depth) throws IOException {
57 if (methodMapping.getDeobfName() == null) {
58 out.format("%sMETHOD %s %s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfSignature());
59 } else {
60 out.format("%sMETHOD %s %s %s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfSignature());
61 }
62
63 for (ArgumentMapping argumentMapping : sorted(methodMapping.arguments())) {
64 write(out, argumentMapping, depth + 1);
65 }
66 }
67
68 private void write(PrintWriter out, ArgumentMapping argumentMapping, int depth) throws IOException {
69 out.format("%sARG %d %s\n", getIndent(depth), argumentMapping.getIndex(), argumentMapping.getName());
70 }
71
72 private <T extends Comparable<T>> List<T> sorted(Iterable<T> classes) {
73 List<T> out = new ArrayList<T>();
74 for (T t : classes) {
75 out.add(t);
76 }
77 Collections.sort(out);
78 return out;
79 }
80
81 private String getIndent(int depth) {
82 StringBuilder buf = new StringBuilder();
83 for (int i = 0; i < depth; i++) {
84 buf.append("\t");
85 }
86 return buf.toString();
87 }
88}
diff --git a/src/cuchaz/enigma/mapping/MemberMapping.java b/src/cuchaz/enigma/mapping/MemberMapping.java
new file mode 100644
index 00000000..83782975
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MemberMapping.java
@@ -0,0 +1,17 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13
14public interface MemberMapping<T extends Entry> {
15 T getObfEntry(ClassEntry classEntry);
16 String getObfName();
17}
diff --git a/src/cuchaz/enigma/mapping/MethodEntry.java b/src/cuchaz/enigma/mapping/MethodEntry.java
new file mode 100644
index 00000000..eb9e2043
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MethodEntry.java
@@ -0,0 +1,104 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..055e1fe1
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MethodMapping.java
@@ -0,0 +1,191 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.Map;
15import java.util.Map.Entry;
16
17import com.google.common.collect.Maps;
18
19public class MethodMapping implements Serializable, Comparable<MethodMapping>, MemberMapping<BehaviorEntry> {
20
21 private static final long serialVersionUID = -4409570216084263978L;
22
23 private String m_obfName;
24 private String m_deobfName;
25 private Signature m_obfSignature;
26 private Map<Integer,ArgumentMapping> m_arguments;
27
28 public MethodMapping(String obfName, Signature obfSignature) {
29 this(obfName, obfSignature, null);
30 }
31
32 public MethodMapping(String obfName, Signature obfSignature, String deobfName) {
33 if (obfName == null) {
34 throw new IllegalArgumentException("obf name cannot be null!");
35 }
36 if (obfSignature == null) {
37 throw new IllegalArgumentException("obf signature cannot be null!");
38 }
39 m_obfName = obfName;
40 m_deobfName = NameValidator.validateMethodName(deobfName);
41 m_obfSignature = obfSignature;
42 m_arguments = Maps.newTreeMap();
43 }
44
45 public MethodMapping(MethodMapping other, ClassNameReplacer obfClassNameReplacer) {
46 m_obfName = other.m_obfName;
47 m_deobfName = other.m_deobfName;
48 m_obfSignature = new Signature(other.m_obfSignature, obfClassNameReplacer);
49 m_arguments = Maps.newTreeMap();
50 for (Entry<Integer,ArgumentMapping> entry : other.m_arguments.entrySet()) {
51 m_arguments.put(entry.getKey(), new ArgumentMapping(entry.getValue()));
52 }
53 }
54
55 @Override
56 public String getObfName() {
57 return m_obfName;
58 }
59
60 public void setObfName(String val) {
61 m_obfName = NameValidator.validateMethodName(val);
62 }
63
64 public String getDeobfName() {
65 return m_deobfName;
66 }
67
68 public void setDeobfName(String val) {
69 m_deobfName = NameValidator.validateMethodName(val);
70 }
71
72 public Signature getObfSignature() {
73 return m_obfSignature;
74 }
75
76 public void setObfSignature(Signature val) {
77 m_obfSignature = val;
78 }
79
80 public Iterable<ArgumentMapping> arguments() {
81 return m_arguments.values();
82 }
83
84 public boolean isConstructor() {
85 return m_obfName.startsWith("<");
86 }
87
88 public void addArgumentMapping(ArgumentMapping argumentMapping) {
89 boolean wasAdded = m_arguments.put(argumentMapping.getIndex(), argumentMapping) == null;
90 assert (wasAdded);
91 }
92
93 public String getObfArgumentName(int index) {
94 ArgumentMapping argumentMapping = m_arguments.get(index);
95 if (argumentMapping != null) {
96 return argumentMapping.getName();
97 }
98
99 return null;
100 }
101
102 public String getDeobfArgumentName(int index) {
103 ArgumentMapping argumentMapping = m_arguments.get(index);
104 if (argumentMapping != null) {
105 return argumentMapping.getName();
106 }
107
108 return null;
109 }
110
111 public void setArgumentName(int index, String name) {
112 ArgumentMapping argumentMapping = m_arguments.get(index);
113 if (argumentMapping == null) {
114 argumentMapping = new ArgumentMapping(index, name);
115 boolean wasAdded = m_arguments.put(index, argumentMapping) == null;
116 assert (wasAdded);
117 } else {
118 argumentMapping.setName(name);
119 }
120 }
121
122 public void removeArgumentName(int index) {
123 boolean wasRemoved = m_arguments.remove(index) != null;
124 assert (wasRemoved);
125 }
126
127 @Override
128 public String toString() {
129 StringBuilder buf = new StringBuilder();
130 buf.append("\t");
131 buf.append(m_obfName);
132 buf.append(" <-> ");
133 buf.append(m_deobfName);
134 buf.append("\n");
135 buf.append("\t");
136 buf.append(m_obfSignature);
137 buf.append("\n");
138 buf.append("\tArguments:\n");
139 for (ArgumentMapping argumentMapping : m_arguments.values()) {
140 buf.append("\t\t");
141 buf.append(argumentMapping.getIndex());
142 buf.append(" -> ");
143 buf.append(argumentMapping.getName());
144 buf.append("\n");
145 }
146 return buf.toString();
147 }
148
149 @Override
150 public int compareTo(MethodMapping other) {
151 return (m_obfName + m_obfSignature).compareTo(other.m_obfName + other.m_obfSignature);
152 }
153
154 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
155
156 // rename obf classes in the signature
157 Signature newSignature = new Signature(m_obfSignature, new ClassNameReplacer() {
158 @Override
159 public String replace(String className) {
160 if (className.equals(oldObfClassName)) {
161 return newObfClassName;
162 }
163 return null;
164 }
165 });
166
167 if (!newSignature.equals(m_obfSignature)) {
168 m_obfSignature = newSignature;
169 return true;
170 }
171 return false;
172 }
173
174 public boolean containsArgument(String name) {
175 for (ArgumentMapping argumentMapping : m_arguments.values()) {
176 if (argumentMapping.getName().equals(name)) {
177 return true;
178 }
179 }
180 return false;
181 }
182
183 @Override
184 public BehaviorEntry getObfEntry(ClassEntry classEntry) {
185 if (isConstructor()) {
186 return new ConstructorEntry(classEntry, m_obfSignature);
187 } else {
188 return new MethodEntry(classEntry, m_obfName, m_obfSignature);
189 }
190 }
191}
diff --git a/src/cuchaz/enigma/mapping/NameValidator.java b/src/cuchaz/enigma/mapping/NameValidator.java
new file mode 100644
index 00000000..12520e12
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/NameValidator.java
@@ -0,0 +1,80 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.util.Arrays;
14import java.util.List;
15import java.util.regex.Pattern;
16
17import javassist.bytecode.Descriptor;
18
19public class NameValidator {
20
21 private static final Pattern IdentifierPattern;
22 private static final Pattern ClassPattern;
23 private static final List<String> ReservedWords = Arrays.asList(
24 "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized",
25 "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte",
26 "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch",
27 "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally",
28 "long", "strictfp", "volatile", "const", "float", "native", "super", "while"
29 );
30
31 static {
32
33 // java allows all kinds of weird characters...
34 StringBuilder startChars = new StringBuilder();
35 StringBuilder partChars = new StringBuilder();
36 for (int i = Character.MIN_CODE_POINT; i <= Character.MAX_CODE_POINT; i++) {
37 if (Character.isJavaIdentifierStart(i)) {
38 startChars.appendCodePoint(i);
39 }
40 if (Character.isJavaIdentifierPart(i)) {
41 partChars.appendCodePoint(i);
42 }
43 }
44
45 String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*";
46 IdentifierPattern = Pattern.compile(identifierRegex);
47 ClassPattern = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex));
48 }
49
50 public static String validateClassName(String name, boolean packageRequired) {
51 if (name == null) {
52 return null;
53 }
54 if (!ClassPattern.matcher(name).matches() || ReservedWords.contains(name)) {
55 throw new IllegalNameException(name, "This doesn't look like a legal class name");
56 }
57 if (packageRequired && new ClassEntry(name).getPackageName() == null) {
58 throw new IllegalNameException(name, "Class must be in a package");
59 }
60 return Descriptor.toJvmName(name);
61 }
62
63 public static String validateFieldName(String name) {
64 if (name == null) {
65 return null;
66 }
67 if (!IdentifierPattern.matcher(name).matches() || ReservedWords.contains(name)) {
68 throw new IllegalNameException(name, "This doesn't look like a legal identifier");
69 }
70 return name;
71 }
72
73 public static String validateMethodName(String name) {
74 return validateFieldName(name);
75 }
76
77 public static String validateArgumentName(String name) {
78 return validateFieldName(name);
79 }
80}
diff --git a/src/cuchaz/enigma/mapping/ProcyonEntryFactory.java b/src/cuchaz/enigma/mapping/ProcyonEntryFactory.java
new file mode 100644
index 00000000..777a12e4
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ProcyonEntryFactory.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import com.strobel.assembler.metadata.FieldDefinition;
14import com.strobel.assembler.metadata.MethodDefinition;
15
16
17public class ProcyonEntryFactory {
18
19 public static FieldEntry getFieldEntry(FieldDefinition def) {
20 return new FieldEntry(
21 new ClassEntry(def.getDeclaringType().getInternalName()),
22 def.getName(),
23 new Type(def.getErasedSignature())
24 );
25 }
26
27 public static MethodEntry getMethodEntry(MethodDefinition def) {
28 return new MethodEntry(
29 new ClassEntry(def.getDeclaringType().getInternalName()),
30 def.getName(),
31 new Signature(def.getErasedSignature())
32 );
33 }
34
35 public static ConstructorEntry getConstructorEntry(MethodDefinition def) {
36 if (def.isTypeInitializer()) {
37 return new ConstructorEntry(
38 new ClassEntry(def.getDeclaringType().getInternalName())
39 );
40 } else {
41 return new ConstructorEntry(
42 new ClassEntry(def.getDeclaringType().getInternalName()),
43 new Signature(def.getErasedSignature())
44 );
45 }
46 }
47
48 public static BehaviorEntry getBehaviorEntry(MethodDefinition def) {
49 if (def.isConstructor() || def.isTypeInitializer()) {
50 return getConstructorEntry(def);
51 } else {
52 return getMethodEntry(def);
53 }
54 }
55}
diff --git a/src/cuchaz/enigma/mapping/Signature.java b/src/cuchaz/enigma/mapping/Signature.java
new file mode 100644
index 00000000..8f2b6b2e
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Signature.java
@@ -0,0 +1,117 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.List;
15
16import com.google.common.collect.Lists;
17
18import cuchaz.enigma.Util;
19
20public class Signature implements Serializable {
21
22 private static final long serialVersionUID = -5843719505729497539L;
23
24 private List<Type> m_argumentTypes;
25 private Type m_returnType;
26
27 public Signature(String signature) {
28 try {
29 m_argumentTypes = Lists.newArrayList();
30 int i=0;
31 while (i<signature.length()) {
32 char c = signature.charAt(i);
33 if (c == '(') {
34 assert(m_argumentTypes.isEmpty());
35 assert(m_returnType == null);
36 i++;
37 } else if (c == ')') {
38 i++;
39 break;
40 } else {
41 String type = Type.parseFirst(signature.substring(i));
42 m_argumentTypes.add(new Type(type));
43 i += type.length();
44 }
45 }
46 m_returnType = new Type(Type.parseFirst(signature.substring(i)));
47 } catch (Exception ex) {
48 throw new IllegalArgumentException("Unable to parse signature: " + signature, ex);
49 }
50 }
51
52 public Signature(Signature other) {
53 m_argumentTypes = Lists.newArrayList(other.m_argumentTypes);
54 m_returnType = new Type(other.m_returnType);
55 }
56
57 public Signature(Signature other, ClassNameReplacer replacer) {
58 m_argumentTypes = Lists.newArrayList(other.m_argumentTypes);
59 for (int i=0; i<m_argumentTypes.size(); i++) {
60 m_argumentTypes.set(i, new Type(m_argumentTypes.get(i), replacer));
61 }
62 m_returnType = new Type(other.m_returnType, replacer);
63 }
64
65 public List<Type> getArgumentTypes() {
66 return m_argumentTypes;
67 }
68
69 public Type getReturnType() {
70 return m_returnType;
71 }
72
73 @Override
74 public String toString() {
75 StringBuilder buf = new StringBuilder();
76 buf.append("(");
77 for (Type type : m_argumentTypes) {
78 buf.append(type.toString());
79 }
80 buf.append(")");
81 buf.append(m_returnType.toString());
82 return buf.toString();
83 }
84
85 public Iterable<Type> types() {
86 List<Type> types = Lists.newArrayList();
87 types.addAll(m_argumentTypes);
88 types.add(m_returnType);
89 return types;
90 }
91
92 @Override
93 public boolean equals(Object other) {
94 if (other instanceof Signature) {
95 return equals((Signature)other);
96 }
97 return false;
98 }
99
100 public boolean equals(Signature other) {
101 return m_argumentTypes.equals(other.m_argumentTypes) && m_returnType.equals(other.m_returnType);
102 }
103
104 @Override
105 public int hashCode() {
106 return Util.combineHashesOrdered(m_argumentTypes.hashCode(), m_returnType.hashCode());
107 }
108
109 public boolean hasClass(ClassEntry classEntry) {
110 for (Type type : types()) {
111 if (type.hasClass() && type.getClassEntry().equals(classEntry)) {
112 return true;
113 }
114 }
115 return false;
116 }
117}
diff --git a/src/cuchaz/enigma/mapping/SignatureUpdater.java b/src/cuchaz/enigma/mapping/SignatureUpdater.java
new file mode 100644
index 00000000..eb53233e
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/SignatureUpdater.java
@@ -0,0 +1,94 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..bc3aaa13
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/TranslationDirection.java
@@ -0,0 +1,29 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
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 00000000..41c7d7cc
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Translator.java
@@ -0,0 +1,289 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.util.List;
14import java.util.Map;
15
16import com.google.common.collect.Lists;
17import com.google.common.collect.Maps;
18
19import cuchaz.enigma.analysis.TranslationIndex;
20
21public class Translator {
22
23 private TranslationDirection m_direction;
24 private Map<String,ClassMapping> m_classes;
25 private TranslationIndex m_index;
26
27 private ClassNameReplacer m_classNameReplacer = new ClassNameReplacer() {
28 @Override
29 public String replace(String className) {
30 return translateEntry(new ClassEntry(className)).getName();
31 }
32 };
33
34 public Translator() {
35 m_direction = null;
36 m_classes = Maps.newHashMap();
37 m_index = new TranslationIndex();
38 }
39
40 public Translator(TranslationDirection direction, Map<String,ClassMapping> classes, TranslationIndex index) {
41 m_direction = direction;
42 m_classes = classes;
43 m_index = index;
44 }
45
46 public TranslationDirection getDirection() {
47 return m_direction;
48 }
49
50 public TranslationIndex getTranslationIndex() {
51 return m_index;
52 }
53
54 @SuppressWarnings("unchecked")
55 public <T extends Entry> T translateEntry(T entry) {
56 if (entry instanceof ClassEntry) {
57 return (T)translateEntry((ClassEntry)entry);
58 } else if (entry instanceof FieldEntry) {
59 return (T)translateEntry((FieldEntry)entry);
60 } else if (entry instanceof MethodEntry) {
61 return (T)translateEntry((MethodEntry)entry);
62 } else if (entry instanceof ConstructorEntry) {
63 return (T)translateEntry((ConstructorEntry)entry);
64 } else if (entry instanceof ArgumentEntry) {
65 return (T)translateEntry((ArgumentEntry)entry);
66 } else {
67 throw new Error("Unknown entry type: " + entry.getClass().getName());
68 }
69 }
70
71 public <T extends Entry> String translate(T entry) {
72 if (entry instanceof ClassEntry) {
73 return translate((ClassEntry)entry);
74 } else if (entry instanceof FieldEntry) {
75 return translate((FieldEntry)entry);
76 } else if (entry instanceof MethodEntry) {
77 return translate((MethodEntry)entry);
78 } else if (entry instanceof ConstructorEntry) {
79 return translate((ConstructorEntry)entry);
80 } else if (entry instanceof ArgumentEntry) {
81 return translate((ArgumentEntry)entry);
82 } else {
83 throw new Error("Unknown entry type: " + entry.getClass().getName());
84 }
85 }
86
87 public String translate(ClassEntry in) {
88 ClassEntry translated = translateEntry(in);
89 if (translated.equals(in)) {
90 return null;
91 }
92 return translated.getName();
93 }
94
95 public String translateClass(String className) {
96 return translate(new ClassEntry(className));
97 }
98
99 public ClassEntry translateEntry(ClassEntry in) {
100
101 if (in.isInnerClass()) {
102
103 // translate as much of the class chain as we can
104 List<ClassMapping> mappingsChain = getClassMappingChain(in);
105 String[] obfClassNames = in.getName().split("\\$");
106 StringBuilder buf = new StringBuilder();
107 for (int i=0; i<obfClassNames.length; i++) {
108 boolean isFirstClass = buf.length() == 0;
109 String className = null;
110 ClassMapping classMapping = mappingsChain.get(i);
111 if (classMapping != null) {
112 className = m_direction.choose(
113 classMapping.getDeobfName(),
114 isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName()
115 );
116 }
117 if (className == null) {
118 className = obfClassNames[i];
119 }
120 if (!isFirstClass) {
121 buf.append("$");
122 }
123 buf.append(className);
124 }
125 return new ClassEntry(buf.toString());
126
127 } else {
128
129 // normal classes are easy
130 ClassMapping classMapping = m_classes.get(in.getName());
131 if (classMapping == null) {
132 return in;
133 }
134 return m_direction.choose(
135 classMapping.getDeobfName() != null ? new ClassEntry(classMapping.getDeobfName()) : in,
136 new ClassEntry(classMapping.getObfFullName())
137 );
138 }
139 }
140
141 public String translate(FieldEntry in) {
142
143 // resolve the class entry
144 ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in);
145 if (resolvedClassEntry != null) {
146
147 // look for the class
148 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
149 if (classMapping != null) {
150
151 // look for the field
152 String translatedName = m_direction.choose(
153 classMapping.getDeobfFieldName(in.getName(), in.getType()),
154 classMapping.getObfFieldName(in.getName(), translateType(in.getType()))
155 );
156 if (translatedName != null) {
157 return translatedName;
158 }
159 }
160 }
161 return null;
162 }
163
164 public FieldEntry translateEntry(FieldEntry in) {
165 String name = translate(in);
166 if (name == null) {
167 name = in.getName();
168 }
169 return new FieldEntry(translateEntry(in.getClassEntry()), name, translateType(in.getType()));
170 }
171
172 public String translate(MethodEntry in) {
173
174 // resolve the class entry
175 ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in);
176 if (resolvedClassEntry != null) {
177
178 // look for class
179 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
180 if (classMapping != null) {
181
182 // look for the method
183 MethodMapping methodMapping = m_direction.choose(
184 classMapping.getMethodByObf(in.getName(), in.getSignature()),
185 classMapping.getMethodByDeobf(in.getName(), translateSignature(in.getSignature()))
186 );
187 if (methodMapping != null) {
188 return m_direction.choose(methodMapping.getDeobfName(), methodMapping.getObfName());
189 }
190 }
191 }
192 return null;
193 }
194
195 public MethodEntry translateEntry(MethodEntry in) {
196 String name = translate(in);
197 if (name == null) {
198 name = in.getName();
199 }
200 return new MethodEntry(translateEntry(in.getClassEntry()), name, translateSignature(in.getSignature()));
201 }
202
203 public ConstructorEntry translateEntry(ConstructorEntry in) {
204 if (in.isStatic()) {
205 return new ConstructorEntry(translateEntry(in.getClassEntry()));
206 } else {
207 return new ConstructorEntry(translateEntry(in.getClassEntry()), translateSignature(in.getSignature()));
208 }
209 }
210
211 public BehaviorEntry translateEntry(BehaviorEntry in) {
212 if (in instanceof MethodEntry) {
213 return translateEntry((MethodEntry)in);
214 } else if (in instanceof ConstructorEntry) {
215 return translateEntry((ConstructorEntry)in);
216 }
217 throw new Error("Wrong entry type!");
218 }
219
220 public String translate(ArgumentEntry in) {
221
222 // look for the class
223 ClassMapping classMapping = findClassMapping(in.getClassEntry());
224 if (classMapping != null) {
225
226 // look for the method
227 MethodMapping methodMapping = m_direction.choose(
228 classMapping.getMethodByObf(in.getMethodName(), in.getMethodSignature()),
229 classMapping.getMethodByDeobf(in.getMethodName(), translateSignature(in.getMethodSignature()))
230 );
231 if (methodMapping != null) {
232 return m_direction.choose(
233 methodMapping.getDeobfArgumentName(in.getIndex()),
234 methodMapping.getObfArgumentName(in.getIndex())
235 );
236 }
237 }
238 return null;
239 }
240
241 public ArgumentEntry translateEntry(ArgumentEntry in) {
242 String name = translate(in);
243 if (name == null) {
244 name = in.getName();
245 }
246 return new ArgumentEntry(translateEntry(in.getBehaviorEntry()), in.getIndex(), name);
247 }
248
249 public Type translateType(Type type) {
250 return new Type(type, m_classNameReplacer);
251 }
252
253 public Signature translateSignature(Signature signature) {
254 return new Signature(signature, m_classNameReplacer);
255 }
256
257 private ClassMapping findClassMapping(ClassEntry in) {
258 List<ClassMapping> mappingChain = getClassMappingChain(in);
259 return mappingChain.get(mappingChain.size() - 1);
260 }
261
262 private List<ClassMapping> getClassMappingChain(ClassEntry in) {
263
264 // get a list of all the classes in the hierarchy
265 String[] parts = in.getName().split("\\$");
266 List<ClassMapping> mappingsChain = Lists.newArrayList();
267
268 // get mappings for the outer class
269 ClassMapping outerClassMapping = m_classes.get(parts[0]);
270 mappingsChain.add(outerClassMapping);
271
272 for (int i=1; i<parts.length; i++) {
273
274 // get mappings for the inner class
275 ClassMapping innerClassMapping = null;
276 if (outerClassMapping != null) {
277 innerClassMapping = m_direction.choose(
278 outerClassMapping.getInnerClassByObfSimple(parts[i]),
279 outerClassMapping.getInnerClassByDeobfThenObfSimple(parts[i])
280 );
281 }
282 mappingsChain.add(innerClassMapping);
283 outerClassMapping = innerClassMapping;
284 }
285
286 assert(mappingsChain.size() == parts.length);
287 return mappingsChain;
288 }
289}
diff --git a/src/cuchaz/enigma/mapping/Type.java b/src/cuchaz/enigma/mapping/Type.java
new file mode 100644
index 00000000..f86a5ccc
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Type.java
@@ -0,0 +1,247 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.Map;
15
16import com.google.common.collect.Maps;
17
18public class Type implements Serializable {
19
20 private static final long serialVersionUID = 7862257669347104063L;
21
22 public enum Primitive {
23 Byte('B'),
24 Character('C'),
25 Short('S'),
26 Integer('I'),
27 Long('J'),
28 Float('F'),
29 Double('D'),
30 Boolean('Z');
31
32 private static final Map<Character,Primitive> m_lookup;
33
34 static {
35 m_lookup = Maps.newTreeMap();
36 for (Primitive val : values()) {
37 m_lookup.put(val.getCode(), val);
38 }
39 }
40
41 public static Primitive get(char code) {
42 return m_lookup.get(code);
43 }
44
45 private char m_code;
46
47 private Primitive(char code) {
48 m_code = code;
49 }
50
51 public char getCode() {
52 return m_code;
53 }
54 }
55
56 public static String parseFirst(String in) {
57
58 if (in == null || in.length() <= 0) {
59 throw new IllegalArgumentException("No type to parse, input is empty!");
60 }
61
62 // read one type from the input
63
64 char c = in.charAt(0);
65
66 // first check for void
67 if (c == 'V') {
68 return "V";
69 }
70
71 // then check for primitives
72 Primitive primitive = Primitive.get(c);
73 if (primitive != null) {
74 return in.substring(0, 1);
75 }
76
77 // then check for classes
78 if (c == 'L') {
79 return readClass(in);
80 }
81
82 // then check for templates
83 if (c == 'T') {
84 return readClass(in);
85 }
86
87 // then check for arrays
88 int dim = countArrayDimension(in);
89 if (dim > 0) {
90 String arrayType = Type.parseFirst(in.substring(dim));
91 return in.substring(0, dim + arrayType.length());
92 }
93
94 throw new IllegalArgumentException("don't know how to parse: " + in);
95 }
96
97 protected String m_name;
98
99 public Type(String name) {
100
101 // don't deal with generics
102 // this is just for raw jvm types
103 if (name.charAt(0) == 'T' || name.indexOf('<') >= 0 || name.indexOf('>') >= 0) {
104 throw new IllegalArgumentException("don't use with generic types or templates: " + name);
105 }
106
107 m_name = name;
108 }
109
110 public Type(Type other) {
111 m_name = other.m_name;
112 }
113
114 public Type(ClassEntry classEntry) {
115 m_name = "L" + classEntry.getClassName() + ";";
116 }
117
118 public Type(Type other, ClassNameReplacer replacer) {
119 m_name = other.m_name;
120 if (other.isClass()) {
121 String replacedName = replacer.replace(other.getClassEntry().getClassName());
122 if (replacedName != null) {
123 m_name = "L" + replacedName + ";";
124 }
125 } else if (other.isArray() && other.hasClass()) {
126 String replacedName = replacer.replace(other.getClassEntry().getClassName());
127 if (replacedName != null) {
128 m_name = Type.getArrayPrefix(other.getArrayDimension()) + "L" + replacedName + ";";
129 }
130 }
131 }
132
133 @Override
134 public String toString() {
135 return m_name;
136 }
137
138 public boolean isVoid() {
139 return m_name.length() == 1 && m_name.charAt(0) == 'V';
140 }
141
142 public boolean isPrimitive() {
143 return m_name.length() == 1 && Primitive.get(m_name.charAt(0)) != null;
144 }
145
146 public Primitive getPrimitive() {
147 if (!isPrimitive()) {
148 throw new IllegalStateException("not a primitive");
149 }
150 return Primitive.get(m_name.charAt(0));
151 }
152
153 public boolean isClass() {
154 return m_name.charAt(0) == 'L' && m_name.charAt(m_name.length() - 1) == ';';
155 }
156
157 public ClassEntry getClassEntry() {
158 if (isClass()) {
159 String name = m_name.substring(1, m_name.length() - 1);
160
161 int pos = name.indexOf('<');
162 if (pos >= 0) {
163 // remove the parameters from the class name
164 name = name.substring(0, pos);
165 }
166
167 return new ClassEntry(name);
168
169 } else if (isArray() && getArrayType().isClass()) {
170 return getArrayType().getClassEntry();
171 } else {
172 throw new IllegalStateException("type doesn't have a class");
173 }
174 }
175
176 public boolean isArray() {
177 return m_name.charAt(0) == '[';
178 }
179
180 public int getArrayDimension() {
181 if (!isArray()) {
182 throw new IllegalStateException("not an array");
183 }
184 return countArrayDimension(m_name);
185 }
186
187 public Type getArrayType() {
188 if (!isArray()) {
189 throw new IllegalStateException("not an array");
190 }
191 return new Type(m_name.substring(getArrayDimension(), m_name.length()));
192 }
193
194 private static String getArrayPrefix(int dimension) {
195 StringBuilder buf = new StringBuilder();
196 for (int i=0; i<dimension; i++) {
197 buf.append("[");
198 }
199 return buf.toString();
200 }
201
202 public boolean hasClass() {
203 return isClass() || (isArray() && getArrayType().hasClass());
204 }
205
206 @Override
207 public boolean equals(Object other) {
208 if (other instanceof Type) {
209 return equals((Type)other);
210 }
211 return false;
212 }
213
214 public boolean equals(Type other) {
215 return m_name.equals(other.m_name);
216 }
217
218 public int hashCode() {
219 return m_name.hashCode();
220 }
221
222 private static int countArrayDimension(String in) {
223 int i=0;
224 for(; i < in.length() && in.charAt(i) == '['; i++);
225 return i;
226 }
227
228 private static String readClass(String in) {
229 // read all the characters in the buffer until we hit a ';'
230 // include the parameters too
231 StringBuilder buf = new StringBuilder();
232 int depth = 0;
233 for (int i=0; i<in.length(); i++) {
234 char c = in.charAt(i);
235 buf.append(c);
236
237 if (c == '<') {
238 depth++;
239 } else if (c == '>') {
240 depth--;
241 } else if (depth == 0 && c == ';') {
242 return buf.toString();
243 }
244 }
245 return null;
246 }
247}
diff --git a/test/cuchaz/enigma/TestDeobfed.java b/test/cuchaz/enigma/TestDeobfed.java
new file mode 100644
index 00000000..5f3ef8cf
--- /dev/null
+++ b/test/cuchaz/enigma/TestDeobfed.java
@@ -0,0 +1,95 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13
14import static cuchaz.enigma.TestEntryFactory.*;
15import static org.hamcrest.MatcherAssert.*;
16import static org.hamcrest.Matchers.*;
17
18import java.util.jar.JarFile;
19
20import org.junit.BeforeClass;
21import org.junit.Test;
22
23import cuchaz.enigma.analysis.JarIndex;
24
25
26public class TestDeobfed {
27
28 private static JarFile m_jar;
29 private static JarIndex m_index;
30
31 @BeforeClass
32 public static void beforeClass()
33 throws Exception {
34 m_jar = new JarFile("build/test-deobf/translation.jar");
35 m_index = new JarIndex();
36 m_index.indexJar(m_jar, true);
37 }
38
39 @Test
40 public void obfEntries() {
41 assertThat(m_index.getObfClassEntries(), containsInAnyOrder(
42 newClass("cuchaz/enigma/inputs/Keep"),
43 newClass("none/a"),
44 newClass("none/b"),
45 newClass("none/c"),
46 newClass("none/d"),
47 newClass("none/d$1"),
48 newClass("none/e"),
49 newClass("none/f"),
50 newClass("none/g"),
51 newClass("none/g$a"),
52 newClass("none/g$a$a"),
53 newClass("none/g$b"),
54 newClass("none/g$b$a"),
55 newClass("none/h"),
56 newClass("none/h$a"),
57 newClass("none/h$a$a"),
58 newClass("none/h$b"),
59 newClass("none/h$b$a"),
60 newClass("none/h$b$a$a"),
61 newClass("none/h$b$a$b"),
62 newClass("none/i"),
63 newClass("none/i$a"),
64 newClass("none/i$b")
65 ));
66 }
67
68 @Test
69 public void decompile()
70 throws Exception {
71 Deobfuscator deobfuscator = new Deobfuscator(m_jar);
72 deobfuscator.getSourceTree("none/a");
73 deobfuscator.getSourceTree("none/b");
74 deobfuscator.getSourceTree("none/c");
75 deobfuscator.getSourceTree("none/d");
76 deobfuscator.getSourceTree("none/d$1");
77 deobfuscator.getSourceTree("none/e");
78 deobfuscator.getSourceTree("none/f");
79 deobfuscator.getSourceTree("none/g");
80 deobfuscator.getSourceTree("none/g$a");
81 deobfuscator.getSourceTree("none/g$a$a");
82 deobfuscator.getSourceTree("none/g$b");
83 deobfuscator.getSourceTree("none/g$b$a");
84 deobfuscator.getSourceTree("none/h");
85 deobfuscator.getSourceTree("none/h$a");
86 deobfuscator.getSourceTree("none/h$a$a");
87 deobfuscator.getSourceTree("none/h$b");
88 deobfuscator.getSourceTree("none/h$b$a");
89 deobfuscator.getSourceTree("none/h$b$a$a");
90 deobfuscator.getSourceTree("none/h$b$a$b");
91 deobfuscator.getSourceTree("none/i");
92 deobfuscator.getSourceTree("none/i$a");
93 deobfuscator.getSourceTree("none/i$b");
94 }
95}
diff --git a/test/cuchaz/enigma/TestDeobfuscator.java b/test/cuchaz/enigma/TestDeobfuscator.java
new file mode 100644
index 00000000..1b0aa74d
--- /dev/null
+++ b/test/cuchaz/enigma/TestDeobfuscator.java
@@ -0,0 +1,57 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static org.junit.Assert.*;
14
15import java.io.IOException;
16import java.util.List;
17import java.util.jar.JarFile;
18
19import org.junit.Test;
20
21import com.google.common.collect.Lists;
22
23import cuchaz.enigma.mapping.ClassEntry;
24
25public class TestDeobfuscator {
26
27 private Deobfuscator getDeobfuscator()
28 throws IOException {
29 return new Deobfuscator(new JarFile("build/test-obf/loneClass.jar"));
30 }
31
32 @Test
33 public void loadJar()
34 throws Exception {
35 getDeobfuscator();
36 }
37
38 @Test
39 public void getClasses()
40 throws Exception {
41 Deobfuscator deobfuscator = getDeobfuscator();
42 List<ClassEntry> obfClasses = Lists.newArrayList();
43 List<ClassEntry> deobfClasses = Lists.newArrayList();
44 deobfuscator.getSeparatedClasses(obfClasses, deobfClasses);
45 assertEquals(1, obfClasses.size());
46 assertEquals("none/a", obfClasses.get(0).getName());
47 assertEquals(1, deobfClasses.size());
48 assertEquals("cuchaz/enigma/inputs/Keep", deobfClasses.get(0).getName());
49 }
50
51 @Test
52 public void decompileClass()
53 throws Exception {
54 Deobfuscator deobfuscator = getDeobfuscator();
55 deobfuscator.getSource(deobfuscator.getSourceTree("none/a"));
56 }
57}
diff --git a/test/cuchaz/enigma/TestEntryFactory.java b/test/cuchaz/enigma/TestEntryFactory.java
new file mode 100644
index 00000000..4aa773b6
--- /dev/null
+++ b/test/cuchaz/enigma/TestEntryFactory.java
@@ -0,0 +1,67 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import cuchaz.enigma.analysis.EntryReference;
14import cuchaz.enigma.mapping.BehaviorEntry;
15import cuchaz.enigma.mapping.ClassEntry;
16import cuchaz.enigma.mapping.ConstructorEntry;
17import cuchaz.enigma.mapping.FieldEntry;
18import cuchaz.enigma.mapping.MethodEntry;
19import cuchaz.enigma.mapping.Signature;
20import cuchaz.enigma.mapping.Type;
21
22public class TestEntryFactory {
23
24 public static ClassEntry newClass(String name) {
25 return new ClassEntry(name);
26 }
27
28 public static FieldEntry newField(String className, String fieldName, String fieldType) {
29 return newField(newClass(className), fieldName, fieldType);
30 }
31
32 public static FieldEntry newField(ClassEntry classEntry, String fieldName, String fieldType) {
33 return new FieldEntry(classEntry, fieldName, new Type(fieldType));
34 }
35
36 public static MethodEntry newMethod(String className, String methodName, String methodSignature) {
37 return newMethod(newClass(className), methodName, methodSignature);
38 }
39
40 public static MethodEntry newMethod(ClassEntry classEntry, String methodName, String methodSignature) {
41 return new MethodEntry(classEntry, methodName, new Signature(methodSignature));
42 }
43
44 public static ConstructorEntry newConstructor(String className, String signature) {
45 return newConstructor(newClass(className), signature);
46 }
47
48 public static ConstructorEntry newConstructor(ClassEntry classEntry, String signature) {
49 return new ConstructorEntry(classEntry, new Signature(signature));
50 }
51
52 public static EntryReference<FieldEntry,BehaviorEntry> newFieldReferenceByMethod(FieldEntry fieldEntry, String callerClassName, String callerName, String callerSignature) {
53 return new EntryReference<FieldEntry,BehaviorEntry>(fieldEntry, "", newMethod(callerClassName, callerName, callerSignature));
54 }
55
56 public static EntryReference<FieldEntry,BehaviorEntry> newFieldReferenceByConstructor(FieldEntry fieldEntry, String callerClassName, String callerSignature) {
57 return new EntryReference<FieldEntry,BehaviorEntry>(fieldEntry, "", newConstructor(callerClassName, callerSignature));
58 }
59
60 public static EntryReference<BehaviorEntry,BehaviorEntry> newBehaviorReferenceByMethod(BehaviorEntry behaviorEntry, String callerClassName, String callerName, String callerSignature) {
61 return new EntryReference<BehaviorEntry,BehaviorEntry>(behaviorEntry, "", newMethod(callerClassName, callerName, callerSignature));
62 }
63
64 public static EntryReference<BehaviorEntry,BehaviorEntry> newBehaviorReferenceByConstructor(BehaviorEntry behaviorEntry, String callerClassName, String callerSignature) {
65 return new EntryReference<BehaviorEntry,BehaviorEntry>(behaviorEntry, "", newConstructor(callerClassName, callerSignature));
66 }
67}
diff --git a/test/cuchaz/enigma/TestInnerClasses.java b/test/cuchaz/enigma/TestInnerClasses.java
new file mode 100644
index 00000000..a4f90217
--- /dev/null
+++ b/test/cuchaz/enigma/TestInnerClasses.java
@@ -0,0 +1,132 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static org.hamcrest.MatcherAssert.*;
14import static org.hamcrest.Matchers.*;
15
16import java.util.jar.JarFile;
17
18import org.junit.Test;
19
20import static cuchaz.enigma.TestEntryFactory.*;
21
22import cuchaz.enigma.analysis.JarIndex;
23import cuchaz.enigma.mapping.ClassEntry;
24
25public class TestInnerClasses {
26
27 private JarIndex m_index;
28 private Deobfuscator m_deobfuscator;
29
30 private static final ClassEntry AnonymousOuter = newClass("none/a");
31 private static final ClassEntry AnonymousInner = newClass("none/a$1");
32 private static final ClassEntry SimpleOuter = newClass("none/d");
33 private static final ClassEntry SimpleInner = newClass("none/d$a");
34 private static final ClassEntry ConstructorArgsOuter = newClass("none/c");
35 private static final ClassEntry ConstructorArgsInner = newClass("none/c$a");
36 private static final ClassEntry AnonymousWithScopeArgsOuter = newClass("none/b");
37 private static final ClassEntry AnonymousWithScopeArgsInner = newClass("none/b$1");
38 private static final ClassEntry AnonymousWithOuterAccessOuter = newClass("none/e");
39 private static final ClassEntry AnonymousWithOuterAccessInner = newClass("none/e$1");
40 private static final ClassEntry ClassTreeRoot = newClass("none/f");
41 private static final ClassEntry ClassTreeLevel1 = newClass("none/f$a");
42 private static final ClassEntry ClassTreeLevel2 = newClass("none/f$a$a");
43 private static final ClassEntry ClassTreeLevel3 = newClass("none/f$a$a$a");
44
45 public TestInnerClasses()
46 throws Exception {
47 m_index = new JarIndex();
48 JarFile jar = new JarFile("build/test-obf/innerClasses.jar");
49 m_index.indexJar(jar, true);
50 m_deobfuscator = new Deobfuscator(jar);
51 }
52
53 @Test
54 public void simple() {
55 assertThat(m_index.getOuterClass(SimpleInner), is(SimpleOuter));
56 assertThat(m_index.getInnerClasses(SimpleOuter), containsInAnyOrder(SimpleInner));
57 assertThat(m_index.isAnonymousClass(SimpleInner), is(false));
58 decompile(SimpleOuter);
59 }
60
61 @Test
62 public void anonymous() {
63 assertThat(m_index.getOuterClass(AnonymousInner), is(AnonymousOuter));
64 assertThat(m_index.getInnerClasses(AnonymousOuter), containsInAnyOrder(AnonymousInner));
65 assertThat(m_index.isAnonymousClass(AnonymousInner), is(true));
66 decompile(AnonymousOuter);
67 }
68
69 @Test
70 public void constructorArgs() {
71 assertThat(m_index.getOuterClass(ConstructorArgsInner), is(ConstructorArgsOuter));
72 assertThat(m_index.getInnerClasses(ConstructorArgsOuter), containsInAnyOrder(ConstructorArgsInner));
73 assertThat(m_index.isAnonymousClass(ConstructorArgsInner), is(false));
74 decompile(ConstructorArgsOuter);
75 }
76
77 @Test
78 public void anonymousWithScopeArgs() {
79 assertThat(m_index.getOuterClass(AnonymousWithScopeArgsInner), is(AnonymousWithScopeArgsOuter));
80 assertThat(m_index.getInnerClasses(AnonymousWithScopeArgsOuter), containsInAnyOrder(AnonymousWithScopeArgsInner));
81 assertThat(m_index.isAnonymousClass(AnonymousWithScopeArgsInner), is(true));
82 decompile(AnonymousWithScopeArgsOuter);
83 }
84
85 @Test
86 public void anonymousWithOuterAccess() {
87 assertThat(m_index.getOuterClass(AnonymousWithOuterAccessInner), is(AnonymousWithOuterAccessOuter));
88 assertThat(m_index.getInnerClasses(AnonymousWithOuterAccessOuter), containsInAnyOrder(AnonymousWithOuterAccessInner));
89 assertThat(m_index.isAnonymousClass(AnonymousWithOuterAccessInner), is(true));
90 decompile(AnonymousWithOuterAccessOuter);
91 }
92
93 @Test
94 public void classTree() {
95
96 // root level
97 assertThat(m_index.containsObfClass(ClassTreeRoot), is(true));
98 assertThat(m_index.getOuterClass(ClassTreeRoot), is(nullValue()));
99 assertThat(m_index.getInnerClasses(ClassTreeRoot), containsInAnyOrder(ClassTreeLevel1));
100
101 // level 1
102 ClassEntry fullClassEntry = new ClassEntry(ClassTreeRoot.getName()
103 + "$" + ClassTreeLevel1.getInnermostClassName()
104 );
105 assertThat(m_index.containsObfClass(fullClassEntry), is(true));
106 assertThat(m_index.getOuterClass(ClassTreeLevel1), is(ClassTreeRoot));
107 assertThat(m_index.getInnerClasses(ClassTreeLevel1), containsInAnyOrder(ClassTreeLevel2));
108
109 // level 2
110 fullClassEntry = new ClassEntry(ClassTreeRoot.getName()
111 + "$" + ClassTreeLevel1.getInnermostClassName()
112 + "$" + ClassTreeLevel2.getInnermostClassName()
113 );
114 assertThat(m_index.containsObfClass(fullClassEntry), is(true));
115 assertThat(m_index.getOuterClass(ClassTreeLevel2), is(ClassTreeLevel1));
116 assertThat(m_index.getInnerClasses(ClassTreeLevel2), containsInAnyOrder(ClassTreeLevel3));
117
118 // level 3
119 fullClassEntry = new ClassEntry(ClassTreeRoot.getName()
120 + "$" + ClassTreeLevel1.getInnermostClassName()
121 + "$" + ClassTreeLevel2.getInnermostClassName()
122 + "$" + ClassTreeLevel3.getInnermostClassName()
123 );
124 assertThat(m_index.containsObfClass(fullClassEntry), is(true));
125 assertThat(m_index.getOuterClass(ClassTreeLevel3), is(ClassTreeLevel2));
126 assertThat(m_index.getInnerClasses(ClassTreeLevel3), is(empty()));
127 }
128
129 private void decompile(ClassEntry classEntry) {
130 m_deobfuscator.getSourceTree(classEntry.getName());
131 }
132}
diff --git a/test/cuchaz/enigma/TestJarIndexConstructorReferences.java b/test/cuchaz/enigma/TestJarIndexConstructorReferences.java
new file mode 100644
index 00000000..606801bd
--- /dev/null
+++ b/test/cuchaz/enigma/TestJarIndexConstructorReferences.java
@@ -0,0 +1,124 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static cuchaz.enigma.TestEntryFactory.*;
14import static org.hamcrest.MatcherAssert.*;
15import static org.hamcrest.Matchers.*;
16
17import java.io.File;
18import java.util.Collection;
19import java.util.jar.JarFile;
20
21import org.junit.Test;
22
23import cuchaz.enigma.analysis.EntryReference;
24import cuchaz.enigma.analysis.JarIndex;
25import cuchaz.enigma.mapping.BehaviorEntry;
26import cuchaz.enigma.mapping.ClassEntry;
27
28public class TestJarIndexConstructorReferences {
29
30 private JarIndex m_index;
31
32 private ClassEntry m_baseClass = newClass("none/a");
33 private ClassEntry m_subClass = newClass("none/d");
34 private ClassEntry m_subsubClass = newClass("none/e");
35 private ClassEntry m_defaultClass = newClass("none/c");
36 private ClassEntry m_callerClass = newClass("none/b");
37
38 public TestJarIndexConstructorReferences()
39 throws Exception {
40 File jarFile = new File("build/test-obf/constructors.jar");
41 m_index = new JarIndex();
42 m_index.indexJar(new JarFile(jarFile), false);
43 }
44
45 @Test
46 public void obfEntries() {
47 assertThat(m_index.getObfClassEntries(), containsInAnyOrder(newClass("cuchaz/enigma/inputs/Keep"), m_baseClass, m_subClass, m_subsubClass, m_defaultClass, m_callerClass));
48 }
49
50 @Test
51 @SuppressWarnings("unchecked")
52 public void baseDefault() {
53 BehaviorEntry source = newConstructor(m_baseClass, "()V");
54 Collection<EntryReference<BehaviorEntry,BehaviorEntry>> references = m_index.getBehaviorReferences(source);
55 assertThat(references, containsInAnyOrder(
56 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "a", "()V"),
57 newBehaviorReferenceByConstructor(source, m_subClass.getName(), "()V"),
58 newBehaviorReferenceByConstructor(source, m_subClass.getName(), "(III)V")
59 ));
60 }
61
62 @Test
63 @SuppressWarnings("unchecked")
64 public void baseInt() {
65 BehaviorEntry source = newConstructor(m_baseClass, "(I)V");
66 assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder(
67 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "b", "()V")
68 ));
69 }
70
71 @Test
72 @SuppressWarnings("unchecked")
73 public void subDefault() {
74 BehaviorEntry source = newConstructor(m_subClass, "()V");
75 assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder(
76 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "c", "()V"),
77 newBehaviorReferenceByConstructor(source, m_subClass.getName(), "(I)V")
78 ));
79 }
80
81 @Test
82 @SuppressWarnings("unchecked")
83 public void subInt() {
84 BehaviorEntry source = newConstructor(m_subClass, "(I)V");
85 assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder(
86 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "d", "()V"),
87 newBehaviorReferenceByConstructor(source, m_subClass.getName(), "(II)V"),
88 newBehaviorReferenceByConstructor(source, m_subsubClass.getName(), "(I)V")
89 ));
90 }
91
92 @Test
93 @SuppressWarnings("unchecked")
94 public void subIntInt() {
95 BehaviorEntry source = newConstructor(m_subClass, "(II)V");
96 assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder(
97 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "e", "()V")
98 ));
99 }
100
101 @Test
102 public void subIntIntInt() {
103 BehaviorEntry source = newConstructor(m_subClass, "(III)V");
104 assertThat(m_index.getBehaviorReferences(source), is(empty()));
105 }
106
107 @Test
108 @SuppressWarnings("unchecked")
109 public void subsubInt() {
110 BehaviorEntry source = newConstructor(m_subsubClass, "(I)V");
111 assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder(
112 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "f", "()V")
113 ));
114 }
115
116 @Test
117 @SuppressWarnings("unchecked")
118 public void defaultConstructable() {
119 BehaviorEntry source = newConstructor(m_defaultClass, "()V");
120 assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder(
121 newBehaviorReferenceByMethod(source, m_callerClass.getName(), "g", "()V")
122 ));
123 }
124}
diff --git a/test/cuchaz/enigma/TestJarIndexInheritanceTree.java b/test/cuchaz/enigma/TestJarIndexInheritanceTree.java
new file mode 100644
index 00000000..010f2dc7
--- /dev/null
+++ b/test/cuchaz/enigma/TestJarIndexInheritanceTree.java
@@ -0,0 +1,228 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static cuchaz.enigma.TestEntryFactory.*;
14import static org.hamcrest.MatcherAssert.*;
15import static org.hamcrest.Matchers.*;
16
17import java.util.Collection;
18import java.util.Set;
19import java.util.jar.JarFile;
20
21import org.junit.Test;
22
23import cuchaz.enigma.analysis.Access;
24import cuchaz.enigma.analysis.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 = newField(m_baseClass, "a", "Ljava/lang/String;");
41 private FieldEntry m_numThingsField = newField(m_subClassB, "a", "I");
42
43 public TestJarIndexInheritanceTree()
44 throws Exception {
45 m_index = new JarIndex();
46 m_index.indexJar(new JarFile("build/test-obf/inheritanceTree.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 00000000..09479bb8
--- /dev/null
+++ b/test/cuchaz/enigma/TestJarIndexLoneClass.java
@@ -0,0 +1,164 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static cuchaz.enigma.TestEntryFactory.*;
14import static org.hamcrest.MatcherAssert.*;
15import static org.hamcrest.Matchers.*;
16
17import java.util.Collection;
18import java.util.Set;
19import java.util.jar.JarFile;
20
21import org.junit.Test;
22
23import cuchaz.enigma.analysis.Access;
24import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
25import cuchaz.enigma.analysis.ClassInheritanceTreeNode;
26import cuchaz.enigma.analysis.EntryReference;
27import cuchaz.enigma.analysis.JarIndex;
28import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
29import cuchaz.enigma.mapping.BehaviorEntry;
30import cuchaz.enigma.mapping.ClassEntry;
31import cuchaz.enigma.mapping.FieldEntry;
32import cuchaz.enigma.mapping.MethodEntry;
33import cuchaz.enigma.mapping.Translator;
34
35public class TestJarIndexLoneClass {
36
37 private JarIndex m_index;
38
39 public TestJarIndexLoneClass()
40 throws Exception {
41 m_index = new JarIndex();
42 m_index.indexJar(new JarFile("build/test-obf/loneClass.jar"), false);
43 }
44
45 @Test
46 public void obfEntries() {
47 assertThat(m_index.getObfClassEntries(), containsInAnyOrder(
48 newClass("cuchaz/enigma/inputs/Keep"),
49 newClass("none/a")
50 ));
51 }
52
53 @Test
54 public void translationIndex() {
55 assertThat(m_index.getTranslationIndex().getSuperclass(new ClassEntry("none/a")), is(nullValue()));
56 assertThat(m_index.getTranslationIndex().getSuperclass(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(nullValue()));
57 assertThat(m_index.getTranslationIndex().getAncestry(new ClassEntry("none/a")), is(empty()));
58 assertThat(m_index.getTranslationIndex().getAncestry(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(empty()));
59 assertThat(m_index.getTranslationIndex().getSubclass(new ClassEntry("none/a")), is(empty()));
60 assertThat(m_index.getTranslationIndex().getSubclass(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(empty()));
61 }
62
63 @Test
64 public void access() {
65 assertThat(m_index.getAccess(newField("none/a", "a", "Ljava/lang/String;")), is(Access.Private));
66 assertThat(m_index.getAccess(newMethod("none/a", "a", "()Ljava/lang/String;")), is(Access.Public));
67 assertThat(m_index.getAccess(newField("none/a", "b", "Ljava/lang/String;")), is(nullValue()));
68 assertThat(m_index.getAccess(newField("none/a", "a", "LFoo;")), is(nullValue()));
69 }
70
71 @Test
72 public void classInheritance() {
73 ClassInheritanceTreeNode node = m_index.getClassInheritance(new Translator(), newClass("none/a"));
74 assertThat(node, is(not(nullValue())));
75 assertThat(node.getObfClassName(), is("none/a"));
76 assertThat(node.getChildCount(), is(0));
77 }
78
79 @Test
80 public void methodInheritance() {
81 MethodEntry source = newMethod("none/a", "a", "()Ljava/lang/String;");
82 MethodInheritanceTreeNode node = m_index.getMethodInheritance(new Translator(), source);
83 assertThat(node, is(not(nullValue())));
84 assertThat(node.getMethodEntry(), is(source));
85 assertThat(node.getChildCount(), is(0));
86 }
87
88 @Test
89 public void classImplementations() {
90 ClassImplementationsTreeNode node = m_index.getClassImplementations(new Translator(), newClass("none/a"));
91 assertThat(node, is(nullValue()));
92 }
93
94 @Test
95 public void methodImplementations() {
96 MethodEntry source = newMethod("none/a", "a", "()Ljava/lang/String;");
97 assertThat(m_index.getMethodImplementations(new Translator(), source), is(empty()));
98 }
99
100 @Test
101 public void relatedMethodImplementations() {
102 Set<MethodEntry> entries = m_index.getRelatedMethodImplementations(newMethod("none/a", "a", "()Ljava/lang/String;"));
103 assertThat(entries, containsInAnyOrder(
104 newMethod("none/a", "a", "()Ljava/lang/String;")
105 ));
106 }
107
108 @Test
109 @SuppressWarnings("unchecked")
110 public void fieldReferences() {
111 FieldEntry source = newField("none/a", "a", "Ljava/lang/String;");
112 Collection<EntryReference<FieldEntry,BehaviorEntry>> references = m_index.getFieldReferences(source);
113 assertThat(references, containsInAnyOrder(
114 newFieldReferenceByConstructor(source, "none/a", "(Ljava/lang/String;)V"),
115 newFieldReferenceByMethod(source, "none/a", "a", "()Ljava/lang/String;")
116 ));
117 }
118
119 @Test
120 public void behaviorReferences() {
121 assertThat(m_index.getBehaviorReferences(newMethod("none/a", "a", "()Ljava/lang/String;")), is(empty()));
122 }
123
124 @Test
125 public void innerClasses() {
126 assertThat(m_index.getInnerClasses(newClass("none/a")), is(empty()));
127 }
128
129 @Test
130 public void outerClass() {
131 assertThat(m_index.getOuterClass(newClass("a")), is(nullValue()));
132 }
133
134 @Test
135 public void isAnonymousClass() {
136 assertThat(m_index.isAnonymousClass(newClass("none/a")), is(false));
137 }
138
139 @Test
140 public void interfaces() {
141 assertThat(m_index.getInterfaces("none/a"), is(empty()));
142 }
143
144 @Test
145 public void implementingClasses() {
146 assertThat(m_index.getImplementingClasses("none/a"), is(empty()));
147 }
148
149 @Test
150 public void isInterface() {
151 assertThat(m_index.isInterface("none/a"), is(false));
152 }
153
154 @Test
155 public void contains() {
156 assertThat(m_index.containsObfClass(newClass("none/a")), is(true));
157 assertThat(m_index.containsObfClass(newClass("none/b")), is(false));
158 assertThat(m_index.containsObfField(newField("none/a", "a", "Ljava/lang/String;")), is(true));
159 assertThat(m_index.containsObfField(newField("none/a", "b", "Ljava/lang/String;")), is(false));
160 assertThat(m_index.containsObfField(newField("none/a", "a", "LFoo;")), is(false));
161 assertThat(m_index.containsObfBehavior(newMethod("none/a", "a", "()Ljava/lang/String;")), is(true));
162 assertThat(m_index.containsObfBehavior(newMethod("none/a", "b", "()Ljava/lang/String;")), is(false));
163 }
164}
diff --git a/test/cuchaz/enigma/TestSignature.java b/test/cuchaz/enigma/TestSignature.java
new file mode 100644
index 00000000..8537adfb
--- /dev/null
+++ b/test/cuchaz/enigma/TestSignature.java
@@ -0,0 +1,268 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static org.hamcrest.MatcherAssert.*;
14import static org.hamcrest.Matchers.*;
15
16import org.junit.Test;
17
18import cuchaz.enigma.mapping.ClassNameReplacer;
19import cuchaz.enigma.mapping.Signature;
20import cuchaz.enigma.mapping.Type;
21
22
23public class TestSignature {
24
25 @Test
26 public void easiest() {
27 final Signature sig = new Signature("()V");
28 assertThat(sig.getArgumentTypes(), is(empty()));
29 assertThat(sig.getReturnType(), is(new Type("V")));
30 }
31
32 @Test
33 public void primitives() {
34 {
35 final Signature sig = new Signature("(I)V");
36 assertThat(sig.getArgumentTypes(), contains(
37 new Type("I")
38 ));
39 assertThat(sig.getReturnType(), is(new Type("V")));
40 }
41 {
42 final Signature sig = new Signature("(I)I");
43 assertThat(sig.getArgumentTypes(), contains(
44 new Type("I")
45 ));
46 assertThat(sig.getReturnType(), is(new Type("I")));
47 }
48 {
49 final Signature sig = new Signature("(IBCJ)Z");
50 assertThat(sig.getArgumentTypes(), contains(
51 new Type("I"),
52 new Type("B"),
53 new Type("C"),
54 new Type("J")
55 ));
56 assertThat(sig.getReturnType(), is(new Type("Z")));
57 }
58 }
59
60 @Test
61 public void classes() {
62 {
63 final Signature sig = new Signature("([LFoo;)V");
64 assertThat(sig.getArgumentTypes().size(), is(1));
65 assertThat(sig.getArgumentTypes().get(0), is(new Type("[LFoo;")));
66 assertThat(sig.getReturnType(), is(new Type("V")));
67 }
68 {
69 final Signature sig = new Signature("(LFoo;)LBar;");
70 assertThat(sig.getArgumentTypes(), contains(
71 new Type("LFoo;")
72 ));
73 assertThat(sig.getReturnType(), is(new Type("LBar;")));
74 }
75 {
76 final Signature sig = new Signature("(LFoo;LMoo;LZoo;)LBar;");
77 assertThat(sig.getArgumentTypes(), contains(
78 new Type("LFoo;"),
79 new Type("LMoo;"),
80 new Type("LZoo;")
81 ));
82 assertThat(sig.getReturnType(), is(new Type("LBar;")));
83 }
84 }
85
86 @Test
87 public void arrays() {
88 {
89 final Signature sig = new Signature("([I)V");
90 assertThat(sig.getArgumentTypes(), contains(
91 new Type("[I")
92 ));
93 assertThat(sig.getReturnType(), is(new Type("V")));
94 }
95 {
96 final Signature sig = new Signature("([I)[J");
97 assertThat(sig.getArgumentTypes(), contains(
98 new Type("[I")
99 ));
100 assertThat(sig.getReturnType(), is(new Type("[J")));
101 }
102 {
103 final Signature sig = new Signature("([I[Z[F)[D");
104 assertThat(sig.getArgumentTypes(), contains(
105 new Type("[I"),
106 new Type("[Z"),
107 new Type("[F")
108 ));
109 assertThat(sig.getReturnType(), is(new Type("[D")));
110 }
111 }
112
113 @Test
114 public void mixed() {
115 {
116 final Signature sig = new Signature("(I[JLFoo;)Z");
117 assertThat(sig.getArgumentTypes(), contains(
118 new Type("I"),
119 new Type("[J"),
120 new Type("LFoo;")
121 ));
122 assertThat(sig.getReturnType(), is(new Type("Z")));
123 }
124 {
125 final Signature sig = new Signature("(III)[LFoo;");
126 assertThat(sig.getArgumentTypes(), contains(
127 new Type("I"),
128 new Type("I"),
129 new Type("I")
130 ));
131 assertThat(sig.getReturnType(), is(new Type("[LFoo;")));
132 }
133 }
134
135 @Test
136 public void replaceClasses() {
137 {
138 final Signature oldSig = new Signature("()V");
139 final Signature sig = new Signature(oldSig, new ClassNameReplacer() {
140 @Override
141 public String replace(String val) {
142 return null;
143 }
144 });
145 assertThat(sig.getArgumentTypes(), is(empty()));
146 assertThat(sig.getReturnType(), is(new Type("V")));
147 }
148 {
149 final Signature oldSig = new Signature("(IJLFoo;)V");
150 final Signature sig = new Signature(oldSig, new ClassNameReplacer() {
151 @Override
152 public String replace(String val) {
153 return null;
154 }
155 });
156 assertThat(sig.getArgumentTypes(), contains(
157 new Type("I"),
158 new Type("J"),
159 new Type("LFoo;")
160 ));
161 assertThat(sig.getReturnType(), is(new Type("V")));
162 }
163 {
164 final Signature oldSig = new Signature("(LFoo;LBar;)LMoo;");
165 final Signature sig = new Signature(oldSig, new ClassNameReplacer() {
166 @Override
167 public String replace(String val) {
168 if (val.equals("Foo")) {
169 return "Bar";
170 }
171 return null;
172 }
173 });
174 assertThat(sig.getArgumentTypes(), contains(
175 new Type("LBar;"),
176 new Type("LBar;")
177 ));
178 assertThat(sig.getReturnType(), is(new Type("LMoo;")));
179 }
180 {
181 final Signature oldSig = new Signature("(LFoo;LBar;)LMoo;");
182 final Signature sig = new Signature(oldSig, new ClassNameReplacer() {
183 @Override
184 public String replace(String val) {
185 if (val.equals("Moo")) {
186 return "Cow";
187 }
188 return null;
189 }
190 });
191 assertThat(sig.getArgumentTypes(), contains(
192 new Type("LFoo;"),
193 new Type("LBar;")
194 ));
195 assertThat(sig.getReturnType(), is(new Type("LCow;")));
196 }
197 }
198
199 @Test
200 public void replaceArrayClasses() {
201 {
202 final Signature oldSig = new Signature("([LFoo;)[[[LBar;");
203 final Signature sig = new Signature(oldSig, new ClassNameReplacer() {
204 @Override
205 public String replace(String val) {
206 if (val.equals("Foo")) {
207 return "Food";
208 } else if (val.equals("Bar")) {
209 return "Beer";
210 }
211 return null;
212 }
213 });
214 assertThat(sig.getArgumentTypes(), contains(
215 new Type("[LFood;")
216 ));
217 assertThat(sig.getReturnType(), is(new Type("[[[LBeer;")));
218 }
219 }
220
221 @Test
222 public void equals() {
223
224 // base
225 assertThat(new Signature("()V"), is(new Signature("()V")));
226
227 // arguments
228 assertThat(new Signature("(I)V"), is(new Signature("(I)V")));
229 assertThat(new Signature("(ZIZ)V"), is(new Signature("(ZIZ)V")));
230 assertThat(new Signature("(LFoo;)V"), is(new Signature("(LFoo;)V")));
231 assertThat(new Signature("(LFoo;LBar;)V"), is(new Signature("(LFoo;LBar;)V")));
232 assertThat(new Signature("([I)V"), is(new Signature("([I)V")));
233 assertThat(new Signature("([[D[[[J)V"), is(new Signature("([[D[[[J)V")));
234
235 assertThat(new Signature("()V"), is(not(new Signature("(I)V"))));
236 assertThat(new Signature("(I)V"), is(not(new Signature("()V"))));
237 assertThat(new Signature("(IJ)V"), is(not(new Signature("(JI)V"))));
238 assertThat(new Signature("([[Z)V"), is(not(new Signature("([[LFoo;)V"))));
239 assertThat(new Signature("(LFoo;LBar;)V"), is(not(new Signature("(LFoo;LCow;)V"))));
240 assertThat(new Signature("([LFoo;LBar;)V"), is(not(new Signature("(LFoo;LCow;)V"))));
241
242 // return type
243 assertThat(new Signature("()I"), is(new Signature("()I")));
244 assertThat(new Signature("()Z"), is(new Signature("()Z")));
245 assertThat(new Signature("()[D"), is(new Signature("()[D")));
246 assertThat(new Signature("()[[[Z"), is(new Signature("()[[[Z")));
247 assertThat(new Signature("()LFoo;"), is(new Signature("()LFoo;")));
248 assertThat(new Signature("()[LFoo;"), is(new Signature("()[LFoo;")));
249
250 assertThat(new Signature("()I"), is(not(new Signature("()Z"))));
251 assertThat(new Signature("()Z"), is(not(new Signature("()I"))));
252 assertThat(new Signature("()[D"), is(not(new Signature("()[J"))));
253 assertThat(new Signature("()[[[Z"), is(not(new Signature("()[[Z"))));
254 assertThat(new Signature("()LFoo;"), is(not(new Signature("()LBar;"))));
255 assertThat(new Signature("()[LFoo;"), is(not(new Signature("()[LBar;"))));
256 }
257
258 @Test
259 public void testToString() {
260 assertThat(new Signature("()V").toString(), is("()V"));
261 assertThat(new Signature("(I)V").toString(), is("(I)V"));
262 assertThat(new Signature("(ZIZ)V").toString(), is("(ZIZ)V"));
263 assertThat(new Signature("(LFoo;)V").toString(), is("(LFoo;)V"));
264 assertThat(new Signature("(LFoo;LBar;)V").toString(), is("(LFoo;LBar;)V"));
265 assertThat(new Signature("([I)V").toString(), is("([I)V"));
266 assertThat(new Signature("([[D[[[J)V").toString(), is("([[D[[[J)V"));
267 }
268}
diff --git a/test/cuchaz/enigma/TestSourceIndex.java b/test/cuchaz/enigma/TestSourceIndex.java
new file mode 100644
index 00000000..58d9ca91
--- /dev/null
+++ b/test/cuchaz/enigma/TestSourceIndex.java
@@ -0,0 +1,67 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.File;
14import java.util.Set;
15import java.util.jar.JarFile;
16
17import org.junit.Test;
18
19import com.google.common.collect.Sets;
20import com.strobel.decompiler.languages.java.ast.CompilationUnit;
21
22import cuchaz.enigma.mapping.ClassEntry;
23
24public class TestSourceIndex {
25 @Test
26 public void indexEverything()
27 throws Exception {
28 // Figure out where Minecraft is...
29 final String mcDir = System.getProperty("enigma.test.minecraftdir");
30 File mcJar = null;
31 if (mcDir == null) {
32 String osname = System.getProperty("os.name").toLowerCase();
33 if (osname.contains("nix") || osname.contains("nux") || osname.contains("solaris")) {
34 mcJar = new File(System.getProperty("user.home"), ".minecraft/versions/1.8.3/1.8.3.jar");
35 }
36 else if (osname.contains("mac") || osname.contains("darwin")) {
37 mcJar = new File(System.getProperty("user.home"), "Library/Application Support/minecraft/versions/1.8.3/1.8.3.jar");
38 }
39 else if (osname.contains("win")) {
40 mcJar = new File(System.getenv("AppData"), ".minecraft/versions/1.8.3/1.8.3.jar");
41 }
42 }
43 else {
44 mcJar = new File(mcDir, "versions/1.8.3/1.8.3.jar");
45 }
46
47 Deobfuscator deobfuscator = new Deobfuscator(new JarFile(mcJar));
48
49 // get all classes that aren't inner classes
50 Set<ClassEntry> classEntries = Sets.newHashSet();
51 for (ClassEntry obfClassEntry : deobfuscator.getJarIndex().getObfClassEntries()) {
52 if (!obfClassEntry.isInnerClass()) {
53 classEntries.add(obfClassEntry);
54 }
55 }
56
57 for (ClassEntry obfClassEntry : classEntries) {
58 try {
59 CompilationUnit tree = deobfuscator.getSourceTree(obfClassEntry.getName());
60 String source = deobfuscator.getSource(tree);
61 deobfuscator.getSourceIndex(tree, source);
62 } catch (Throwable t) {
63 throw new Error("Unable to index " + obfClassEntry, t);
64 }
65 }
66 }
67}
diff --git a/test/cuchaz/enigma/TestTokensConstructors.java b/test/cuchaz/enigma/TestTokensConstructors.java
new file mode 100644
index 00000000..66c6fd1b
--- /dev/null
+++ b/test/cuchaz/enigma/TestTokensConstructors.java
@@ -0,0 +1,136 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static cuchaz.enigma.TestEntryFactory.*;
14import static org.hamcrest.MatcherAssert.*;
15import static org.hamcrest.Matchers.*;
16
17import java.util.jar.JarFile;
18
19import org.junit.Test;
20
21import cuchaz.enigma.mapping.BehaviorEntry;
22
23public class TestTokensConstructors extends TokenChecker {
24
25 public TestTokensConstructors()
26 throws Exception {
27 super(new JarFile("build/test-obf/constructors.jar"));
28 }
29
30 @Test
31 public void baseDeclarations() {
32 assertThat(getDeclarationToken(newConstructor("none/a", "()V")), is("a"));
33 assertThat(getDeclarationToken(newConstructor("none/a", "(I)V")), is("a"));
34 }
35
36 @Test
37 public void subDeclarations() {
38 assertThat(getDeclarationToken(newConstructor("none/d", "()V")), is("d"));
39 assertThat(getDeclarationToken(newConstructor("none/d", "(I)V")), is("d"));
40 assertThat(getDeclarationToken(newConstructor("none/d", "(II)V")), is("d"));
41 assertThat(getDeclarationToken(newConstructor("none/d", "(III)V")), is("d"));
42 }
43
44 @Test
45 public void subsubDeclarations() {
46 assertThat(getDeclarationToken(newConstructor("none/e", "(I)V")), is("e"));
47 }
48
49 @Test
50 public void defaultDeclarations() {
51 assertThat(getDeclarationToken(newConstructor("none/c", "()V")), nullValue());
52 }
53
54 @Test
55 public void baseDefaultReferences() {
56 BehaviorEntry source = newConstructor("none/a", "()V");
57 assertThat(
58 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "a", "()V")),
59 containsInAnyOrder("a")
60 );
61 assertThat(
62 getReferenceTokens(newBehaviorReferenceByConstructor(source, "none/d", "()V")),
63 is(empty()) // implicit call, not decompiled to token
64 );
65 assertThat(
66 getReferenceTokens(newBehaviorReferenceByConstructor(source, "none/d", "(III)V")),
67 is(empty()) // implicit call, not decompiled to token
68 );
69 }
70
71 @Test
72 public void baseIntReferences() {
73 BehaviorEntry source = newConstructor("none/a", "(I)V");
74 assertThat(
75 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "b", "()V")),
76 containsInAnyOrder("a")
77 );
78 }
79
80 @Test
81 public void subDefaultReferences() {
82 BehaviorEntry source = newConstructor("none/d", "()V");
83 assertThat(
84 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "c", "()V")),
85 containsInAnyOrder("d")
86 );
87 assertThat(
88 getReferenceTokens(newBehaviorReferenceByConstructor(source, "none/d", "(I)V")),
89 containsInAnyOrder("this")
90 );
91 }
92
93 @Test
94 public void subIntReferences() {
95 BehaviorEntry source = newConstructor("none/d", "(I)V");
96 assertThat(getReferenceTokens(
97 newBehaviorReferenceByMethod(source, "none/b", "d", "()V")),
98 containsInAnyOrder("d")
99 );
100 assertThat(getReferenceTokens(
101 newBehaviorReferenceByConstructor(source, "none/d", "(II)V")),
102 containsInAnyOrder("this")
103 );
104 assertThat(getReferenceTokens(
105 newBehaviorReferenceByConstructor(source, "none/e", "(I)V")),
106 containsInAnyOrder("super")
107 );
108 }
109
110 @Test
111 public void subIntIntReferences() {
112 BehaviorEntry source = newConstructor("none/d", "(II)V");
113 assertThat(
114 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "e", "()V")),
115 containsInAnyOrder("d")
116 );
117 }
118
119 @Test
120 public void subsubIntReferences() {
121 BehaviorEntry source = newConstructor("none/e", "(I)V");
122 assertThat(
123 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "f", "()V")),
124 containsInAnyOrder("e")
125 );
126 }
127
128 @Test
129 public void defaultConstructableReferences() {
130 BehaviorEntry source = newConstructor("none/c", "()V");
131 assertThat(
132 getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "g", "()V")),
133 containsInAnyOrder("c")
134 );
135 }
136}
diff --git a/test/cuchaz/enigma/TestTranslator.java b/test/cuchaz/enigma/TestTranslator.java
new file mode 100644
index 00000000..9e58d25c
--- /dev/null
+++ b/test/cuchaz/enigma/TestTranslator.java
@@ -0,0 +1,170 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static cuchaz.enigma.TestEntryFactory.*;
14import static org.hamcrest.MatcherAssert.*;
15import static org.hamcrest.Matchers.*;
16
17import java.io.InputStream;
18import java.io.InputStreamReader;
19import java.util.jar.JarFile;
20
21import org.junit.BeforeClass;
22import org.junit.Test;
23
24import cuchaz.enigma.mapping.Entry;
25import cuchaz.enigma.mapping.Mappings;
26import cuchaz.enigma.mapping.MappingsReader;
27import cuchaz.enigma.mapping.TranslationDirection;
28import cuchaz.enigma.mapping.Translator;
29
30
31public class TestTranslator {
32
33 private static Deobfuscator m_deobfuscator;
34 private static Mappings m_mappings;
35 private static Translator m_deobfTranslator;
36 private static Translator m_obfTranslator;
37
38 @BeforeClass
39 public static void beforeClass()
40 throws Exception {
41 m_deobfuscator = new Deobfuscator(new JarFile("build/test-obf/translation.jar"));
42 try (InputStream in = TestTranslator.class.getResourceAsStream("/cuchaz/enigma/resources/translation.mappings")) {
43 m_mappings = new MappingsReader().read(new InputStreamReader(in));
44 m_deobfuscator.setMappings(m_mappings);
45 m_deobfTranslator = m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating);
46 m_obfTranslator = m_deobfuscator.getTranslator(TranslationDirection.Obfuscating);
47 }
48 }
49
50 @Test
51 public void basicClasses() {
52 assertMapping(newClass("none/a"), newClass("deobf/A_Basic"));
53 assertMapping(newClass("none/b"), newClass("deobf/B_BaseClass"));
54 assertMapping(newClass("none/c"), newClass("deobf/C_SubClass"));
55 }
56
57 @Test
58 public void basicFields() {
59 assertMapping(newField("none/a", "a", "I"), newField("deobf/A_Basic", "f1", "I"));
60 assertMapping(newField("none/a", "a", "F"), newField("deobf/A_Basic", "f2", "F"));
61 assertMapping(newField("none/a", "a", "Ljava/lang/String;"), newField("deobf/A_Basic", "f3", "Ljava/lang/String;"));
62 }
63
64 @Test
65 public void basicMethods() {
66 assertMapping(newMethod("none/a", "a", "()V"), newMethod("deobf/A_Basic", "m1", "()V"));
67 assertMapping(newMethod("none/a", "a", "()I"), newMethod("deobf/A_Basic", "m2", "()I"));
68 assertMapping(newMethod("none/a", "a", "(I)V"), newMethod("deobf/A_Basic", "m3", "(I)V"));
69 assertMapping(newMethod("none/a", "a", "(I)I"), newMethod("deobf/A_Basic", "m4", "(I)I"));
70 }
71
72 // TODO: basic constructors
73
74 @Test
75 public void inheritanceFields() {
76 assertMapping(newField("none/b", "a", "I"), newField("deobf/B_BaseClass", "f1", "I"));
77 assertMapping(newField("none/b", "a", "C"), newField("deobf/B_BaseClass", "f2", "C"));
78 assertMapping(newField("none/c", "b", "I"), newField("deobf/C_SubClass", "f3", "I"));
79 assertMapping(newField("none/c", "c", "I"), newField("deobf/C_SubClass", "f4", "I"));
80 }
81
82 @Test
83 public void inheritanceFieldsShadowing() {
84 assertMapping(newField("none/c", "b", "C"), newField("deobf/C_SubClass", "f2", "C"));
85 }
86
87 @Test
88 public void inheritanceFieldsBySubClass() {
89 assertMapping(newField("none/c", "a", "I"), newField("deobf/C_SubClass", "f1", "I"));
90 // NOTE: can't reference b.C by subclass since it's shadowed
91 }
92
93 @Test
94 public void inheritanceMethods() {
95 assertMapping(newMethod("none/b", "a", "()I"), newMethod("deobf/B_BaseClass", "m1", "()I"));
96 assertMapping(newMethod("none/b", "b", "()I"), newMethod("deobf/B_BaseClass", "m2", "()I"));
97 assertMapping(newMethod("none/c", "c", "()I"), newMethod("deobf/C_SubClass", "m3", "()I"));
98 }
99
100 @Test
101 public void inheritanceMethodsOverrides() {
102 assertMapping(newMethod("none/c", "a", "()I"), newMethod("deobf/C_SubClass", "m1", "()I"));
103 }
104
105 @Test
106 public void inheritanceMethodsBySubClass() {
107 assertMapping(newMethod("none/c", "b", "()I"), newMethod("deobf/C_SubClass", "m2", "()I"));
108 }
109
110 @Test
111 public void innerClasses() {
112
113 // classes
114 assertMapping(newClass("none/g"), newClass("deobf/G_OuterClass"));
115 assertMapping(newClass("none/g$a"), newClass("deobf/G_OuterClass$A_InnerClass"));
116 assertMapping(newClass("none/g$a$a"), newClass("deobf/G_OuterClass$A_InnerClass$A_InnerInnerClass"));
117 assertMapping(newClass("none/g$b"), newClass("deobf/G_OuterClass$b"));
118 assertMapping(newClass("none/g$b$a"), newClass("deobf/G_OuterClass$b$A_NamedInnerClass"));
119
120 // fields
121 assertMapping(newField("none/g$a", "a", "I"), newField("deobf/G_OuterClass$A_InnerClass", "f1", "I"));
122 assertMapping(newField("none/g$a", "a", "Ljava/lang/String;"), newField("deobf/G_OuterClass$A_InnerClass", "f2", "Ljava/lang/String;"));
123 assertMapping(newField("none/g$a$a", "a", "I"), newField("deobf/G_OuterClass$A_InnerClass$A_InnerInnerClass", "f3", "I"));
124 assertMapping(newField("none/g$b$a", "a", "I"), newField("deobf/G_OuterClass$b$A_NamedInnerClass", "f4", "I"));
125
126 // methods
127 assertMapping(newMethod("none/g$a", "a", "()V"), newMethod("deobf/G_OuterClass$A_InnerClass", "m1", "()V"));
128 assertMapping(newMethod("none/g$a$a", "a", "()V"), newMethod("deobf/G_OuterClass$A_InnerClass$A_InnerInnerClass", "m2", "()V"));
129 }
130
131 @Test
132 public void namelessClass() {
133 assertMapping(newClass("none/h"), newClass("none/h"));
134 }
135
136 @Test
137 public void testGenerics() {
138
139 // classes
140 assertMapping(newClass("none/i"), newClass("deobf/I_Generics"));
141 assertMapping(newClass("none/i$a"), newClass("deobf/I_Generics$A_Type"));
142 assertMapping(newClass("none/i$b"), newClass("deobf/I_Generics$B_Generic"));
143
144 // fields
145 assertMapping(newField("none/i", "a", "Ljava/util/List;"), newField("deobf/I_Generics", "f1", "Ljava/util/List;"));
146 assertMapping(newField("none/i", "b", "Ljava/util/List;"), newField("deobf/I_Generics", "f2", "Ljava/util/List;"));
147 assertMapping(newField("none/i", "a", "Ljava/util/Map;"), newField("deobf/I_Generics", "f3", "Ljava/util/Map;"));
148 assertMapping(newField("none/i$b", "a", "Ljava/lang/Object;"), newField("deobf/I_Generics$B_Generic", "f4", "Ljava/lang/Object;"));
149 assertMapping(newField("none/i", "a", "Lnone/i$b;"), newField("deobf/I_Generics", "f5", "Ldeobf/I_Generics$B_Generic;"));
150 assertMapping(newField("none/i", "b", "Lnone/i$b;"), newField("deobf/I_Generics", "f6", "Ldeobf/I_Generics$B_Generic;"));
151
152 // methods
153 assertMapping(newMethod("none/i$b", "a", "()Ljava/lang/Object;"), newMethod("deobf/I_Generics$B_Generic", "m1", "()Ljava/lang/Object;"));
154 }
155
156 private void assertMapping(Entry obf, Entry deobf) {
157 assertThat(m_deobfTranslator.translateEntry(obf), is(deobf));
158 assertThat(m_obfTranslator.translateEntry(deobf), is(obf));
159
160 String deobfName = m_deobfTranslator.translate(obf);
161 if (deobfName != null) {
162 assertThat(deobfName, is(deobf.getName()));
163 }
164
165 String obfName = m_obfTranslator.translate(deobf);
166 if (obfName != null) {
167 assertThat(obfName, is(obf.getName()));
168 }
169 }
170}
diff --git a/test/cuchaz/enigma/TestType.java b/test/cuchaz/enigma/TestType.java
new file mode 100644
index 00000000..01c235b8
--- /dev/null
+++ b/test/cuchaz/enigma/TestType.java
@@ -0,0 +1,243 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import static cuchaz.enigma.TestEntryFactory.*;
14import static org.hamcrest.MatcherAssert.*;
15import static org.hamcrest.Matchers.*;
16
17import org.junit.Test;
18
19import cuchaz.enigma.mapping.Type;
20
21
22public class TestType {
23
24 @Test
25 public void isVoid() {
26 assertThat(new Type("V").isVoid(), is(true));
27 assertThat(new Type("Z").isVoid(), is(false));
28 assertThat(new Type("B").isVoid(), is(false));
29 assertThat(new Type("C").isVoid(), is(false));
30 assertThat(new Type("I").isVoid(), is(false));
31 assertThat(new Type("J").isVoid(), is(false));
32 assertThat(new Type("F").isVoid(), is(false));
33 assertThat(new Type("D").isVoid(), is(false));
34 assertThat(new Type("LFoo;").isVoid(), is(false));
35 assertThat(new Type("[I").isVoid(), is(false));
36 }
37
38 @Test
39 public void isPrimitive() {
40 assertThat(new Type("V").isPrimitive(), is(false));
41 assertThat(new Type("Z").isPrimitive(), is(true));
42 assertThat(new Type("B").isPrimitive(), is(true));
43 assertThat(new Type("C").isPrimitive(), is(true));
44 assertThat(new Type("I").isPrimitive(), is(true));
45 assertThat(new Type("J").isPrimitive(), is(true));
46 assertThat(new Type("F").isPrimitive(), is(true));
47 assertThat(new Type("D").isPrimitive(), is(true));
48 assertThat(new Type("LFoo;").isPrimitive(), is(false));
49 assertThat(new Type("[I").isPrimitive(), is(false));
50 }
51
52 @Test
53 public void getPrimitive() {
54 assertThat(new Type("Z").getPrimitive(), is(Type.Primitive.Boolean));
55 assertThat(new Type("B").getPrimitive(), is(Type.Primitive.Byte));
56 assertThat(new Type("C").getPrimitive(), is(Type.Primitive.Character));
57 assertThat(new Type("I").getPrimitive(), is(Type.Primitive.Integer));
58 assertThat(new Type("J").getPrimitive(), is(Type.Primitive.Long));
59 assertThat(new Type("F").getPrimitive(), is(Type.Primitive.Float));
60 assertThat(new Type("D").getPrimitive(), is(Type.Primitive.Double));
61 }
62
63 @Test
64 public void isClass() {
65 assertThat(new Type("V").isClass(), is(false));
66 assertThat(new Type("Z").isClass(), is(false));
67 assertThat(new Type("B").isClass(), is(false));
68 assertThat(new Type("C").isClass(), is(false));
69 assertThat(new Type("I").isClass(), is(false));
70 assertThat(new Type("J").isClass(), is(false));
71 assertThat(new Type("F").isClass(), is(false));
72 assertThat(new Type("D").isClass(), is(false));
73 assertThat(new Type("LFoo;").isClass(), is(true));
74 assertThat(new Type("[I").isClass(), is(false));
75 }
76
77 @Test
78 public void getClassEntry() {
79 assertThat(new Type("LFoo;").getClassEntry(), is(newClass("Foo")));
80 assertThat(new Type("Ljava/lang/String;").getClassEntry(), is(newClass("java/lang/String")));
81 }
82
83 @Test
84 public void getArrayClassEntry() {
85 assertThat(new Type("[LFoo;").getClassEntry(), is(newClass("Foo")));
86 assertThat(new Type("[[[Ljava/lang/String;").getClassEntry(), is(newClass("java/lang/String")));
87 }
88
89 @Test
90 public void isArray() {
91 assertThat(new Type("V").isArray(), is(false));
92 assertThat(new Type("Z").isArray(), is(false));
93 assertThat(new Type("B").isArray(), is(false));
94 assertThat(new Type("C").isArray(), is(false));
95 assertThat(new Type("I").isArray(), is(false));
96 assertThat(new Type("J").isArray(), is(false));
97 assertThat(new Type("F").isArray(), is(false));
98 assertThat(new Type("D").isArray(), is(false));
99 assertThat(new Type("LFoo;").isArray(), is(false));
100 assertThat(new Type("[I").isArray(), is(true));
101 }
102
103 @Test
104 public void getArrayDimension() {
105 assertThat(new Type("[I").getArrayDimension(), is(1));
106 assertThat(new Type("[[I").getArrayDimension(), is(2));
107 assertThat(new Type("[[[I").getArrayDimension(), is(3));
108 }
109
110 @Test
111 public void getArrayType() {
112 assertThat(new Type("[I").getArrayType(), is(new Type("I")));
113 assertThat(new Type("[[I").getArrayType(), is(new Type("I")));
114 assertThat(new Type("[[[I").getArrayType(), is(new Type("I")));
115 assertThat(new Type("[Ljava/lang/String;").getArrayType(), is(new Type("Ljava/lang/String;")));
116 }
117
118 @Test
119 public void hasClass() {
120 assertThat(new Type("LFoo;").hasClass(), is(true));
121 assertThat(new Type("Ljava/lang/String;").hasClass(), is(true));
122 assertThat(new Type("[LBar;").hasClass(), is(true));
123 assertThat(new Type("[[[LCat;").hasClass(), is(true));
124
125 assertThat(new Type("V").hasClass(), is(false));
126 assertThat(new Type("[I").hasClass(), is(false));
127 assertThat(new Type("[[[I").hasClass(), is(false));
128 assertThat(new Type("Z").hasClass(), is(false));
129 }
130
131 @Test
132 public void parseVoid() {
133 final String answer = "V";
134 assertThat(Type.parseFirst("V"), is(answer));
135 assertThat(Type.parseFirst("VVV"), is(answer));
136 assertThat(Type.parseFirst("VIJ"), is(answer));
137 assertThat(Type.parseFirst("V[I"), is(answer));
138 assertThat(Type.parseFirst("VLFoo;"), is(answer));
139 assertThat(Type.parseFirst("V[LFoo;"), is(answer));
140 }
141
142 @Test
143 public void parsePrimitive() {
144 final String answer = "I";
145 assertThat(Type.parseFirst("I"), is(answer));
146 assertThat(Type.parseFirst("III"), is(answer));
147 assertThat(Type.parseFirst("IJZ"), is(answer));
148 assertThat(Type.parseFirst("I[I"), is(answer));
149 assertThat(Type.parseFirst("ILFoo;"), is(answer));
150 assertThat(Type.parseFirst("I[LFoo;"), is(answer));
151 }
152
153 @Test
154 public void parseClass() {
155 {
156 final String answer = "LFoo;";
157 assertThat(Type.parseFirst("LFoo;"), is(answer));
158 assertThat(Type.parseFirst("LFoo;I"), is(answer));
159 assertThat(Type.parseFirst("LFoo;JZ"), is(answer));
160 assertThat(Type.parseFirst("LFoo;[I"), is(answer));
161 assertThat(Type.parseFirst("LFoo;LFoo;"), is(answer));
162 assertThat(Type.parseFirst("LFoo;[LFoo;"), is(answer));
163 }
164 {
165 final String answer = "Ljava/lang/String;";
166 assertThat(Type.parseFirst("Ljava/lang/String;"), is(answer));
167 assertThat(Type.parseFirst("Ljava/lang/String;I"), is(answer));
168 assertThat(Type.parseFirst("Ljava/lang/String;JZ"), is(answer));
169 assertThat(Type.parseFirst("Ljava/lang/String;[I"), is(answer));
170 assertThat(Type.parseFirst("Ljava/lang/String;LFoo;"), is(answer));
171 assertThat(Type.parseFirst("Ljava/lang/String;[LFoo;"), is(answer));
172 }
173 }
174
175 @Test
176 public void parseArray() {
177 {
178 final String answer = "[I";
179 assertThat(Type.parseFirst("[I"), is(answer));
180 assertThat(Type.parseFirst("[III"), is(answer));
181 assertThat(Type.parseFirst("[IJZ"), is(answer));
182 assertThat(Type.parseFirst("[I[I"), is(answer));
183 assertThat(Type.parseFirst("[ILFoo;"), is(answer));
184 }
185 {
186 final String answer = "[[I";
187 assertThat(Type.parseFirst("[[I"), is(answer));
188 assertThat(Type.parseFirst("[[III"), is(answer));
189 assertThat(Type.parseFirst("[[IJZ"), is(answer));
190 assertThat(Type.parseFirst("[[I[I"), is(answer));
191 assertThat(Type.parseFirst("[[ILFoo;"), is(answer));
192 }
193 {
194 final String answer = "[LFoo;";
195 assertThat(Type.parseFirst("[LFoo;"), is(answer));
196 assertThat(Type.parseFirst("[LFoo;II"), is(answer));
197 assertThat(Type.parseFirst("[LFoo;JZ"), is(answer));
198 assertThat(Type.parseFirst("[LFoo;[I"), is(answer));
199 assertThat(Type.parseFirst("[LFoo;LFoo;"), is(answer));
200 }
201 }
202
203 @Test
204 public void equals() {
205 assertThat(new Type("V"), is(new Type("V")));
206 assertThat(new Type("Z"), is(new Type("Z")));
207 assertThat(new Type("B"), is(new Type("B")));
208 assertThat(new Type("C"), is(new Type("C")));
209 assertThat(new Type("I"), is(new Type("I")));
210 assertThat(new Type("J"), is(new Type("J")));
211 assertThat(new Type("F"), is(new Type("F")));
212 assertThat(new Type("D"), is(new Type("D")));
213 assertThat(new Type("LFoo;"), is(new Type("LFoo;")));
214 assertThat(new Type("[I"), is(new Type("[I")));
215 assertThat(new Type("[[[I"), is(new Type("[[[I")));
216 assertThat(new Type("[LFoo;"), is(new Type("[LFoo;")));
217
218 assertThat(new Type("V"), is(not(new Type("I"))));
219 assertThat(new Type("I"), is(not(new Type("J"))));
220 assertThat(new Type("I"), is(not(new Type("LBar;"))));
221 assertThat(new Type("I"), is(not(new Type("[I"))));
222 assertThat(new Type("LFoo;"), is(not(new Type("LBar;"))));
223 assertThat(new Type("[I"), is(not(new Type("[Z"))));
224 assertThat(new Type("[[[I"), is(not(new Type("[I"))));
225 assertThat(new Type("[LFoo;"), is(not(new Type("[LBar;"))));
226 }
227
228 @Test
229 public void testToString() {
230 assertThat(new Type("V").toString(), is("V"));
231 assertThat(new Type("Z").toString(), is("Z"));
232 assertThat(new Type("B").toString(), is("B"));
233 assertThat(new Type("C").toString(), is("C"));
234 assertThat(new Type("I").toString(), is("I"));
235 assertThat(new Type("J").toString(), is("J"));
236 assertThat(new Type("F").toString(), is("F"));
237 assertThat(new Type("D").toString(), is("D"));
238 assertThat(new Type("LFoo;").toString(), is("LFoo;"));
239 assertThat(new Type("[I").toString(), is("[I"));
240 assertThat(new Type("[[[I").toString(), is("[[[I"));
241 assertThat(new Type("[LFoo;").toString(), is("[LFoo;"));
242 }
243}
diff --git a/test/cuchaz/enigma/TokenChecker.java b/test/cuchaz/enigma/TokenChecker.java
new file mode 100644
index 00000000..7afb4cfe
--- /dev/null
+++ b/test/cuchaz/enigma/TokenChecker.java
@@ -0,0 +1,65 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma;
12
13import java.io.IOException;
14import java.util.Collection;
15import java.util.List;
16import java.util.jar.JarFile;
17
18import com.google.common.collect.Lists;
19import com.strobel.decompiler.languages.java.ast.CompilationUnit;
20
21import cuchaz.enigma.analysis.EntryReference;
22import cuchaz.enigma.analysis.SourceIndex;
23import cuchaz.enigma.analysis.Token;
24import cuchaz.enigma.mapping.Entry;
25
26public class TokenChecker {
27
28 private Deobfuscator m_deobfuscator;
29
30 protected TokenChecker(JarFile jarFile)
31 throws IOException {
32 m_deobfuscator = new Deobfuscator(jarFile);
33 }
34
35 protected String getDeclarationToken(Entry entry) {
36 // decompile the class
37 CompilationUnit tree = m_deobfuscator.getSourceTree(entry.getClassName());
38 // DEBUG
39 // tree.acceptVisitor( new TreeDumpVisitor( new File( "tree." + entry.getClassName().replace( '/', '.' ) + ".txt" ) ), null );
40 String source = m_deobfuscator.getSource(tree);
41 SourceIndex index = m_deobfuscator.getSourceIndex(tree, source);
42
43 // get the token value
44 Token token = index.getDeclarationToken(entry);
45 if (token == null) {
46 return null;
47 }
48 return source.substring(token.start, token.end);
49 }
50
51 @SuppressWarnings("unchecked")
52 protected Collection<String> getReferenceTokens(EntryReference<? extends Entry,? extends Entry> reference) {
53 // decompile the class
54 CompilationUnit tree = m_deobfuscator.getSourceTree(reference.context.getClassName());
55 String source = m_deobfuscator.getSource(tree);
56 SourceIndex index = m_deobfuscator.getSourceIndex(tree, source);
57
58 // get the token values
59 List<String> values = Lists.newArrayList();
60 for (Token token : index.getReferenceTokens((EntryReference<Entry,Entry>)reference)) {
61 values.add(source.substring(token.start, token.end));
62 }
63 return values;
64 }
65}
diff --git a/test/cuchaz/enigma/inputs/Keep.java b/test/cuchaz/enigma/inputs/Keep.java
new file mode 100644
index 00000000..f04875f5
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/Keep.java
@@ -0,0 +1,17 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs;
12
13public class Keep {
14 public static void main(String[] args) {
15 System.out.println("Keep me!");
16 }
17}
diff --git a/test/cuchaz/enigma/inputs/constructors/BaseClass.java b/test/cuchaz/enigma/inputs/constructors/BaseClass.java
new file mode 100644
index 00000000..65e782a2
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/constructors/BaseClass.java
@@ -0,0 +1,25 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.constructors;
12
13// none/a
14public class BaseClass {
15
16 // <init>()V
17 public BaseClass() {
18 System.out.println("Default constructor");
19 }
20
21 // <init>(I)V
22 public BaseClass(int i) {
23 System.out.println("Int constructor " + i);
24 }
25}
diff --git a/test/cuchaz/enigma/inputs/constructors/Caller.java b/test/cuchaz/enigma/inputs/constructors/Caller.java
new file mode 100644
index 00000000..75096ec1
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/constructors/Caller.java
@@ -0,0 +1,57 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.constructors;
12
13// none/b
14public class Caller {
15
16 // a()V
17 public void callBaseDefault() {
18 // none/a.<init>()V
19 System.out.println(new BaseClass());
20 }
21
22 // b()V
23 public void callBaseInt() {
24 // none/a.<init>(I)V
25 System.out.println(new BaseClass(5));
26 }
27
28 // c()V
29 public void callSubDefault() {
30 // none/d.<init>()V
31 System.out.println(new SubClass());
32 }
33
34 // d()V
35 public void callSubInt() {
36 // none/d.<init>(I)V
37 System.out.println(new SubClass(6));
38 }
39
40 // e()V
41 public void callSubIntInt() {
42 // none/d.<init>(II)V
43 System.out.println(new SubClass(4, 2));
44 }
45
46 // f()V
47 public void callSubSubInt() {
48 // none/e.<init>(I)V
49 System.out.println(new SubSubClass(3));
50 }
51
52 // g()V
53 public void callDefaultConstructable() {
54 // none/c.<init>()V
55 System.out.println(new DefaultConstructable());
56 }
57}
diff --git a/test/cuchaz/enigma/inputs/constructors/DefaultConstructable.java b/test/cuchaz/enigma/inputs/constructors/DefaultConstructable.java
new file mode 100644
index 00000000..655f4da3
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/constructors/DefaultConstructable.java
@@ -0,0 +1,15 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.constructors;
12
13public class DefaultConstructable {
14 // only default constructor
15}
diff --git a/test/cuchaz/enigma/inputs/constructors/SubClass.java b/test/cuchaz/enigma/inputs/constructors/SubClass.java
new file mode 100644
index 00000000..b0fb3e9b
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/constructors/SubClass.java
@@ -0,0 +1,38 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.constructors;
12
13// none/d extends none/a
14public class SubClass extends BaseClass {
15
16 // <init>()V
17 public SubClass() {
18 // none/a.<init>()V
19 }
20
21 // <init>(I)V
22 public SubClass(int num) {
23 // <init>()V
24 this();
25 System.out.println("SubClass " + num);
26 }
27
28 // <init>(II)V
29 public SubClass(int a, int b) {
30 // <init>(I)V
31 this(a + b);
32 }
33
34 // <init>(III)V
35 public SubClass(int a, int b, int c) {
36 // none/a.<init>()V
37 }
38}
diff --git a/test/cuchaz/enigma/inputs/constructors/SubSubClass.java b/test/cuchaz/enigma/inputs/constructors/SubSubClass.java
new file mode 100644
index 00000000..50314050
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/constructors/SubSubClass.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.constructors;
12
13// none/e extends none/d
14public class SubSubClass extends SubClass {
15
16 // <init>(I)V
17 public SubSubClass(int i) {
18 // none/c.<init>(I)V
19 super(i);
20 }
21}
diff --git a/test/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java b/test/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java
new file mode 100644
index 00000000..4f9c5b0e
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java
@@ -0,0 +1,31 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.inheritanceTree;
12
13// none/a
14public abstract class BaseClass {
15
16 // a
17 private String m_name;
18
19 // <init>(Ljava/lang/String;)V
20 protected BaseClass(String name) {
21 m_name = name;
22 }
23
24 // a()Ljava/lang/String;
25 public String getName() {
26 return m_name;
27 }
28
29 // a()V
30 public abstract void doBaseThings();
31}
diff --git a/test/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java b/test/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java
new file mode 100644
index 00000000..140d2a81
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java
@@ -0,0 +1,21 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.inheritanceTree;
12
13// none/b extends none/a
14public abstract class SubclassA extends BaseClass {
15
16 // <init>(Ljava/lang/String;)V
17 protected SubclassA(String name) {
18 // call to none/a.<init>(Ljava/lang/String)V
19 super(name);
20 }
21}
diff --git a/test/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java b/test/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java
new file mode 100644
index 00000000..99d149bb
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java
@@ -0,0 +1,40 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.inheritanceTree;
12
13// none/c extends none/a
14public class SubclassB extends BaseClass {
15
16 // a
17 private int m_numThings;
18
19 // <init>()V
20 protected SubclassB() {
21 // none/a.<init>(Ljava/lang/String;)V
22 super("B");
23
24 // access to a
25 m_numThings = 4;
26 }
27
28 @Override
29 // a()V
30 public void doBaseThings() {
31 // call to none/a.a()Ljava/lang/String;
32 System.out.println("Base things by B! " + getName());
33 }
34
35 // b()V
36 public void doBThings() {
37 // access to a
38 System.out.println("" + m_numThings + " B things!");
39 }
40}
diff --git a/test/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java b/test/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java
new file mode 100644
index 00000000..2e414b75
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java
@@ -0,0 +1,34 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.inheritanceTree;
12
13// none/d extends none/b
14public class SubsubclassAA extends SubclassA {
15
16 protected SubsubclassAA() {
17 // call to none/b.<init>(Ljava/lang/String;)V
18 super("AA");
19 }
20
21 @Override
22 // a()Ljava/lang/String;
23 public String getName() {
24 // call to none/b.a()Ljava/lang/String;
25 return "subsub" + super.getName();
26 }
27
28 @Override
29 // a()V
30 public void doBaseThings() {
31 // call to none/d.a()Ljava/lang/String;
32 System.out.println("Base things by " + getName());
33 }
34}
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 00000000..f6444396
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java
@@ -0,0 +1,24 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.innerClasses;
12
13public class A_Anonymous {
14
15 public void foo() {
16 Runnable runnable = new Runnable() {
17 @Override
18 public void run() {
19 // don't care
20 }
21 };
22 runnable.run();
23 }
24}
diff --git a/test/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java b/test/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java
new file mode 100644
index 00000000..d78be847
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java
@@ -0,0 +1,23 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.innerClasses;
12
13public class B_AnonymousWithScopeArgs {
14
15 public static void foo(final D_Simple arg) {
16 System.out.println(new Object() {
17 @Override
18 public String toString() {
19 return arg.toString();
20 }
21 });
22 }
23}
diff --git a/test/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java b/test/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java
new file mode 100644
index 00000000..eb03489d
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java
@@ -0,0 +1,30 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.innerClasses;
12
13@SuppressWarnings("unused")
14public class C_ConstructorArgs {
15
16 class Inner {
17
18 private int a;
19
20 public Inner(int a) {
21 this.a = a;
22 }
23 }
24
25 Inner i;
26
27 public void foo() {
28 i = new Inner(5);
29 }
30}
diff --git a/test/cuchaz/enigma/inputs/innerClasses/D_Simple.java b/test/cuchaz/enigma/inputs/innerClasses/D_Simple.java
new file mode 100644
index 00000000..0e9bf827
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/innerClasses/D_Simple.java
@@ -0,0 +1,18 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.innerClasses;
12
13public class D_Simple {
14
15 class Inner {
16 // nothing to do
17 }
18}
diff --git a/test/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java b/test/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java
new file mode 100644
index 00000000..255434d1
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java
@@ -0,0 +1,31 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.innerClasses;
12
13public class E_AnonymousWithOuterAccess {
14
15 // reproduction of error case documented at:
16 // https://bitbucket.org/cuchaz/enigma/issue/61/stackoverflowerror-when-deobfuscating
17
18 public Object makeInner() {
19 outerMethod();
20 return new Object() {
21 @Override
22 public String toString() {
23 return outerMethod();
24 }
25 };
26 }
27
28 private String outerMethod() {
29 return "foo";
30 }
31}
diff --git a/test/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java b/test/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java
new file mode 100644
index 00000000..7d1dab41
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/innerClasses/F_ClassTree.java
@@ -0,0 +1,30 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.innerClasses;
12
13
14public class F_ClassTree {
15
16 public class Level1 {
17
18 public int f1;
19
20 public class Level2 {
21
22 public int f2;
23
24 public class Level3 {
25
26 public int f3;
27 }
28 }
29 }
30}
diff --git a/test/cuchaz/enigma/inputs/loneClass/LoneClass.java b/test/cuchaz/enigma/inputs/loneClass/LoneClass.java
new file mode 100644
index 00000000..bf264fa5
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/loneClass/LoneClass.java
@@ -0,0 +1,24 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.loneClass;
12
13public class LoneClass {
14
15 private String m_name;
16
17 public LoneClass(String name) {
18 m_name = name;
19 }
20
21 public String getName() {
22 return m_name;
23 }
24}
diff --git a/test/cuchaz/enigma/inputs/translation/A_Basic.java b/test/cuchaz/enigma/inputs/translation/A_Basic.java
new file mode 100644
index 00000000..26acac8a
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/translation/A_Basic.java
@@ -0,0 +1,32 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.translation;
12
13public class A_Basic {
14
15 public int one;
16 public float two;
17 public String three;
18
19 public void m1() {
20 }
21
22 public int m2() {
23 return 42;
24 }
25
26 public void m3(int a1) {
27 }
28
29 public int m4(int a1) {
30 return 5; // chosen by fair die roll, guaranteed to be random
31 }
32}
diff --git a/test/cuchaz/enigma/inputs/translation/B_BaseClass.java b/test/cuchaz/enigma/inputs/translation/B_BaseClass.java
new file mode 100644
index 00000000..035e3299
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/translation/B_BaseClass.java
@@ -0,0 +1,25 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.translation;
12
13public class B_BaseClass {
14
15 public int f1;
16 public char f2;
17
18 public int m1() {
19 return 5;
20 }
21
22 public int m2() {
23 return 42;
24 }
25}
diff --git a/test/cuchaz/enigma/inputs/translation/C_SubClass.java b/test/cuchaz/enigma/inputs/translation/C_SubClass.java
new file mode 100644
index 00000000..6026a8d5
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/translation/C_SubClass.java
@@ -0,0 +1,27 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.translation;
12
13public class C_SubClass extends B_BaseClass {
14
15 public char f2; // shadows B_BaseClass.f2
16 public int f3;
17 public int f4;
18
19 @Override
20 public int m1() {
21 return 32;
22 }
23
24 public int m3() {
25 return 7;
26 }
27}
diff --git a/test/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java b/test/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java
new file mode 100644
index 00000000..a1827f98
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/translation/D_AnonymousTesting.java
@@ -0,0 +1,28 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.translation;
12
13import java.util.ArrayList;
14import java.util.List;
15
16public class D_AnonymousTesting {
17
18 public List<Object> getObjs() {
19 List<Object> objs = new ArrayList<Object>();
20 objs.add(new Object() {
21 @Override
22 public String toString() {
23 return "Object!";
24 }
25 });
26 return objs;
27 }
28}
diff --git a/test/cuchaz/enigma/inputs/translation/E_Bridges.java b/test/cuchaz/enigma/inputs/translation/E_Bridges.java
new file mode 100644
index 00000000..769eb70e
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/translation/E_Bridges.java
@@ -0,0 +1,32 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.translation;
12
13import java.util.Iterator;
14
15
16public class E_Bridges implements Iterator<Object> {
17
18 @Override
19 public boolean hasNext() {
20 return false;
21 }
22
23 @Override
24 public String next() {
25 // the compiler will generate a bridge for this method
26 return "foo";
27 }
28
29 @Override
30 public void remove() {
31 }
32}
diff --git a/test/cuchaz/enigma/inputs/translation/F_ObjectMethods.java b/test/cuchaz/enigma/inputs/translation/F_ObjectMethods.java
new file mode 100644
index 00000000..32c246cb
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/translation/F_ObjectMethods.java
@@ -0,0 +1,29 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.translation;
12
13public class F_ObjectMethods {
14
15 public void callEmAll()
16 throws Throwable {
17 clone();
18 equals(this);
19 finalize();
20 getClass();
21 hashCode();
22 notify();
23 notifyAll();
24 toString();
25 wait();
26 wait(0);
27 wait(0, 0);
28 }
29}
diff --git a/test/cuchaz/enigma/inputs/translation/G_OuterClass.java b/test/cuchaz/enigma/inputs/translation/G_OuterClass.java
new file mode 100644
index 00000000..a2e0dafb
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/translation/G_OuterClass.java
@@ -0,0 +1,36 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.translation;
12
13
14public class G_OuterClass {
15
16 public class A_InnerClass {
17
18 public int f1;
19 public String f2;
20
21 public void m1() {}
22
23 public class A_InnerInnerClass {
24
25 public int f3;
26
27 public void m2() {}
28 }
29 }
30
31 public class B_NamelessClass {
32 public class A_NamedInnerClass {
33 public int f4;
34 }
35 }
36}
diff --git a/test/cuchaz/enigma/inputs/translation/H_NamelessClass.java b/test/cuchaz/enigma/inputs/translation/H_NamelessClass.java
new file mode 100644
index 00000000..1b718a54
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/translation/H_NamelessClass.java
@@ -0,0 +1,38 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.translation;
12
13
14public class H_NamelessClass {
15
16 public class A_InnerClass {
17
18 public int f1;
19 public String f2;
20
21 public void m1() {}
22
23 public class A_InnerInnerClass {
24
25 public int f3;
26
27 public void m2() {}
28 }
29 }
30
31 public class B_NamelessClass {
32 public class A_NamedInnerClass {
33 public int f4;
34 public class A_AnotherInnerClass {}
35 public class B_YetAnotherInnerClass {}
36 }
37 }
38}
diff --git a/test/cuchaz/enigma/inputs/translation/I_Generics.java b/test/cuchaz/enigma/inputs/translation/I_Generics.java
new file mode 100644
index 00000000..3490f9d9
--- /dev/null
+++ b/test/cuchaz/enigma/inputs/translation/I_Generics.java
@@ -0,0 +1,35 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.inputs.translation;
12
13import java.util.List;
14import java.util.Map;
15
16
17public class I_Generics {
18
19 public class A_Type {
20 }
21
22 public List<Integer> f1;
23 public List<A_Type> f2;
24 public Map<A_Type,A_Type> f3;
25
26 public class B_Generic<T> {
27 public T f4;
28 public T m1() {
29 return null;
30 }
31 }
32
33 public B_Generic<Integer> f5;
34 public B_Generic<A_Type> f6;
35}
diff --git a/test/cuchaz/enigma/resources/translation.mappings b/test/cuchaz/enigma/resources/translation.mappings
new file mode 100644
index 00000000..db78c19d
--- /dev/null
+++ b/test/cuchaz/enigma/resources/translation.mappings
@@ -0,0 +1,41 @@
1CLASS none/a deobf/A_Basic
2 FIELD a f1 I
3 FIELD a f2 F
4 FIELD a f3 Ljava/lang/String;
5 METHOD a m1 ()V
6 METHOD a m2 ()I
7 METHOD a m3 (I)V
8 METHOD a m4 (I)I
9CLASS none/b deobf/B_BaseClass
10 FIELD a f1 I
11 FIELD a f2 C
12 METHOD a m1 ()I
13 METHOD b m2 ()I
14CLASS none/c deobf/C_SubClass
15 FIELD b f2 C
16 FIELD b f3 I
17 FIELD c f4 I
18 METHOD a m1 ()I
19 METHOD c m3 ()I
20CLASS none/g deobf/G_OuterClass
21 CLASS none/g$a A_InnerClass
22 CLASS none/g$a$a A_InnerInnerClass
23 FIELD a f3 I
24 METHOD a m2 ()V
25 FIELD a f1 I
26 FIELD a f2 Ljava/lang/String;
27 METHOD a m1 ()V
28 CLASS none/g$b
29 CLASS none/g$b$a A_NamedInnerClass
30 FIELD a f4 I
31CLASS none/h
32CLASS none/i deobf/I_Generics
33 CLASS none/i$a A_Type
34 CLASS none/i$b B_Generic
35 FIELD a f4 Ljava/lang/Object;
36 METHOD a m1 ()Ljava/lang/Object;
37 FIELD a f1 Ljava/util/List;
38 FIELD b f2 Ljava/util/List;
39 FIELD a f3 Ljava/util/Map;
40 FIELD a f5 Lnone/i$b;
41 FIELD b f6 Lnone/i$b;