diff options
167 files changed, 20750 insertions, 0 deletions
diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..389aeb5 --- /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 0000000..d691e2d --- /dev/null +++ b/.hgignore | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | |||
| 2 | syntax: glob | ||
| 3 | bin | ||
| 4 | lib | ||
| 5 | out | ||
| 6 | build | ||
| 7 | *.iml | ||
| 8 | .idea | ||
| 9 | .gradle | ||
| 10 | data | ||
| 11 | input | ||
| 12 | ivy | ||
| 13 | *.pyc \ No newline at end of file | ||
diff --git a/.project b/.project new file mode 100644 index 0000000..08dff6c --- /dev/null +++ b/.project | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | <projectDescription> | ||
| 3 | <name>Enigma</name> | ||
| 4 | <comment></comment> | ||
| 5 | <projects> | ||
| 6 | </projects> | ||
| 7 | <buildSpec> | ||
| 8 | <buildCommand> | ||
| 9 | <name>org.eclipse.jdt.core.javabuilder</name> | ||
| 10 | <arguments> | ||
| 11 | </arguments> | ||
| 12 | </buildCommand> | ||
| 13 | </buildSpec> | ||
| 14 | <natures> | ||
| 15 | <nature>org.eclipse.jdt.core.javanature</nature> | ||
| 16 | </natures> | ||
| 17 | </projectDescription> | ||
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..b5d234f --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | # | ||
| 2 | #Mon Sep 22 22:51:23 EDT 2014 | ||
| 3 | org.eclipse.jdt.core.compiler.debug.localVariable=generate | ||
| 4 | org.eclipse.jdt.core.compiler.compliance=1.7 | ||
| 5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve | ||
| 6 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate | ||
| 7 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 | ||
| 8 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error | ||
| 9 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate | ||
| 10 | eclipse.preferences.version=1 | ||
| 11 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled | ||
| 12 | org.eclipse.jdt.core.compiler.source=1.7 | ||
| 13 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error | ||
diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..48ad427 --- /dev/null +++ b/build.gradle | |||
| @@ -0,0 +1,176 @@ | |||
| 1 | // Do it this way so people with older Gradle can hopefully still build. | ||
| 2 | buildscript { | ||
| 3 | repositories { jcenter() } | ||
| 4 | |||
| 5 | dependencies { | ||
| 6 | classpath 'com.github.jengelman.gradle.plugins:shadow:1.2.1' | ||
| 7 | } | ||
| 8 | } | ||
| 9 | |||
| 10 | apply plugin: 'java' | ||
| 11 | apply plugin: 'eclipse' | ||
| 12 | apply plugin: 'com.github.johnrengelman.shadow' | ||
| 13 | apply plugin: 'maven' | ||
| 14 | |||
| 15 | group = 'cuchaz' | ||
| 16 | version = '0.10.4b' | ||
| 17 | |||
| 18 | sourceCompatibility = 1.7 | ||
| 19 | targetCompatibility = 1.7 | ||
| 20 | |||
| 21 | // Custom source layout | ||
| 22 | sourceSets { | ||
| 23 | main { | ||
| 24 | java { srcDir 'src' } | ||
| 25 | resources { srcDir 'conf' } | ||
| 26 | } | ||
| 27 | test { | ||
| 28 | java { srcDir 'test' } | ||
| 29 | resources { srcDir 'test' } | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | repositories { | ||
| 34 | mavenLocal() | ||
| 35 | mavenCentral() | ||
| 36 | |||
| 37 | maven { | ||
| 38 | name "Cuchaz Custom Repository" | ||
| 39 | url 'http://maven.cuchazinteractive.com' | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | configurations { | ||
| 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 | |||
| 50 | dependencies { | ||
| 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. | ||
| 63 | file('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. | ||
| 90 | task ('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 | } | ||
| 97 | test.dependsOn 'deobfTranslationInput' | ||
| 98 | |||
| 99 | test { | ||
| 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. | ||
| 115 | jar.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. | ||
| 119 | shadowJar { | ||
| 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). | ||
| 129 | task('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). | ||
| 147 | task('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 | } | ||
| 160 | artifacts.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 :( | ||
| 164 | configurations.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. | ||
| 175 | assemble.dependsOn thinJar | ||
| 176 | build.dependsOn install \ No newline at end of file | ||
diff --git a/build.py b/build.py new file mode 100644 index 0000000..40da327 --- /dev/null +++ b/build.py | |||
| @@ -0,0 +1,131 @@ | |||
| 1 | |||
| 2 | import os | ||
| 3 | import sys | ||
| 4 | |||
| 5 | # settings | ||
| 6 | PathSsjb = "../ssjb" | ||
| 7 | Author = "Cuchaz" | ||
| 8 | Version = "0.10.4b" | ||
| 9 | |||
| 10 | DirBin = "bin" | ||
| 11 | DirLib = "lib" | ||
| 12 | DirBuild = "build" | ||
| 13 | PathLocalMavenRepo = "../maven" | ||
| 14 | |||
| 15 | |||
| 16 | # import ssjb | ||
| 17 | sys.path.insert(0, PathSsjb) | ||
| 18 | import ssjb | ||
| 19 | import ssjb.ivy | ||
| 20 | |||
| 21 | |||
| 22 | ArtifactStandalone = ssjb.ivy.Dep("cuchaz:enigma:%s" % Version) | ||
| 23 | ArtifactLib = ssjb.ivy.Dep("cuchaz:enigma-lib:%s" % Version) | ||
| 24 | |||
| 25 | # dependencies | ||
| 26 | ExtraRepos = [ | ||
| 27 | "http://maven.cuchazinteractive.com" | ||
| 28 | ] | ||
| 29 | LibDeps = [ | ||
| 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 | ] | ||
| 34 | StandaloneDeps = LibDeps + [ | ||
| 35 | ssjb.ivy.Dep("de.sciss:syntaxpane:1.1.4") | ||
| 36 | ] | ||
| 37 | ProguardDep = ssjb.ivy.Dep("net.sf.proguard:proguard-base:5.1") | ||
| 38 | TestDeps = [ | ||
| 39 | ssjb.ivy.Dep("junit:junit:4.12"), | ||
| 40 | ssjb.ivy.Dep("org.hamcrest:hamcrest-all:1.3") | ||
| 41 | ] | ||
| 42 | |||
| 43 | # functions | ||
| 44 | |||
| 45 | def 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 | |||
| 62 | def buildDeobfTestJar(outPath, inPath): | ||
| 63 | ssjb.callJava( | ||
| 64 | [DirBin, os.path.join(DirLib, "deps.jar")], | ||
| 65 | "cuchaz.enigma.CommandMain", | ||
| 66 | ["deobfuscate", inPath, outPath] | ||
| 67 | ) | ||
| 68 | |||
| 69 | def 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 | |||
| 74 | def 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 | |||
| 92 | def 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 | |||
| 103 | def 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 | |||
| 109 | def 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 | |||
| 116 | def 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 | |||
| 120 | def taskBuild(): | ||
| 121 | ssjb.file.delete(DirBuild) | ||
| 122 | ssjb.file.mkdir(DirBuild) | ||
| 123 | buildStandaloneJar(DirBuild) | ||
| 124 | buildLibJar(DirBuild) | ||
| 125 | |||
| 126 | ssjb.registerTask("getDeps", taskGetDeps) | ||
| 127 | ssjb.registerTask("buildTestJars", taskBuildTestJars) | ||
| 128 | ssjb.registerTask("buildTranslationTestJar", taskBuildTranslationTestJar) | ||
| 129 | ssjb.registerTask("build", taskBuild) | ||
| 130 | ssjb.run() | ||
| 131 | |||
diff --git a/conf/about.html b/conf/about.html new file mode 100644 index 0000000..b75c1bf --- /dev/null +++ b/conf/about.html | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | <html> | ||
| 2 | <h1>%s</h1> | ||
| 3 | <p>A tool for debofuscation of Java code</p> | ||
| 4 | <p> | ||
| 5 | <p>Version: %s</p> | ||
| 6 | </html> \ No newline at end of file | ||
diff --git a/license.APL2.txt b/license.APL2.txt new file mode 100644 index 0000000..a453e43 --- /dev/null +++ b/license.APL2.txt | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | Apache License | ||
| 2 | Version 2.0, January 2004 | ||
| 3 | http://www.apache.org/licenses/ | ||
| 4 | |||
| 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||
| 6 | |||
| 7 | 1. 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 | |||
| 29 | 2. 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 | |||
| 31 | 3. 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 | |||
| 33 | 4. 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 | |||
| 35 | You must give any other recipients of the Work or Derivative Works a copy of this License; and | ||
| 36 | |||
| 37 | |||
| 38 | You must cause any modified files to carry prominent notices stating that You changed the files; and | ||
| 39 | |||
| 40 | |||
| 41 | You 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 | |||
| 44 | If 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. | ||
| 45 | You 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 | |||
| 47 | 5. 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 | |||
| 49 | 6. 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 | |||
| 51 | 7. 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 | |||
| 53 | 8. 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 | |||
| 55 | 9. 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 0000000..65c5ca8 --- /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 | ||
| 10 | the terms and conditions of version 3 of the GNU General Public | ||
| 11 | License, 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 | ||
| 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU | ||
| 17 | General Public License. | ||
| 18 | |||
| 19 | "The Library" refers to a covered work governed by this License, | ||
| 20 | other than an Application or a Combined Work as defined below. | ||
| 21 | |||
| 22 | An "Application" is any work that makes use of an interface provided | ||
| 23 | by the Library, but which is not otherwise based on the Library. | ||
| 24 | Defining a subclass of a class defined by the Library is deemed a mode | ||
| 25 | of using an interface provided by the Library. | ||
| 26 | |||
| 27 | A "Combined Work" is a work produced by combining or linking an | ||
| 28 | Application with the Library. The particular version of the Library | ||
| 29 | with which the Combined Work was made is also called the "Linked | ||
| 30 | Version". | ||
| 31 | |||
| 32 | The "Minimal Corresponding Source" for a Combined Work means the | ||
| 33 | Corresponding Source for the Combined Work, excluding any source code | ||
| 34 | for portions of the Combined Work that, considered in isolation, are | ||
| 35 | based on the Application, and not on the Linked Version. | ||
| 36 | |||
| 37 | The "Corresponding Application Code" for a Combined Work means the | ||
| 38 | object code and/or source code for the Application, including any data | ||
| 39 | and utility programs needed for reproducing the Combined Work from the | ||
| 40 | Application, 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 | ||
| 45 | without 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 | ||
| 50 | facility refers to a function or data to be supplied by an Application | ||
| 51 | that uses the facility (other than as an argument passed when the | ||
| 52 | facility is invoked), then you may convey a copy of the modified | ||
| 53 | version: | ||
| 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 | ||
| 66 | a header file that is part of the Library. You may convey such object | ||
| 67 | code under terms of your choice, provided that, if the incorporated | ||
| 68 | material is not limited to numerical parameters, data structure | ||
| 69 | layouts 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, | ||
| 82 | taken together, effectively do not restrict modification of the | ||
| 83 | portions of the Library contained in the Combined Work and reverse | ||
| 84 | engineering for debugging such modifications, if you also do each of | ||
| 85 | the 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 | ||
| 131 | Library side by side in a single library together with other library | ||
| 132 | facilities that are not Applications and are not covered by this | ||
| 133 | License, and convey such a combined library under terms of your | ||
| 134 | choice, 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 | ||
| 147 | of the GNU Lesser General Public License from time to time. Such new | ||
| 148 | versions will be similar in spirit to the present version, but may | ||
| 149 | differ in detail to address new problems or concerns. | ||
| 150 | |||
| 151 | Each version is given a distinguishing version number. If the | ||
| 152 | Library as you received it specifies that a certain numbered version | ||
| 153 | of the GNU Lesser General Public License "or any later version" | ||
| 154 | applies to it, you have the option of following the terms and | ||
| 155 | conditions either of that published version or of any later version | ||
| 156 | published by the Free Software Foundation. If the Library as you | ||
| 157 | received it does not specify a version number of the GNU Lesser | ||
| 158 | General Public License, you may choose any version of the GNU Lesser | ||
| 159 | General Public License ever published by the Free Software Foundation. | ||
| 160 | |||
| 161 | If the Library as you received it specifies that a proxy can decide | ||
| 162 | whether future versions of the GNU Lesser General Public License shall | ||
| 163 | apply, that proxy's public statement of acceptance of any version is | ||
| 164 | permanent authorization for you to choose that version for the | ||
| 165 | Library. | ||
diff --git a/proguard-build.conf b/proguard-build.conf new file mode 100644 index 0000000..195b84d --- /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 0000000..0d3d60e --- /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 0000000..47277d9 --- /dev/null +++ b/readme.txt | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | |||
| 2 | Enigma v0.10.4 beta | ||
| 3 | A tool for deobfuscation of Java bytecode | ||
| 4 | |||
| 5 | Copyright Jeff Martin, 2015 | ||
| 6 | |||
| 7 | |||
| 8 | LICENSE | ||
| 9 | |||
| 10 | Enigma is distributed under the GNU Lesser General Public license version 3 | ||
| 11 | |||
| 12 | Enigma includes a modified version of Procyon which is distributed under the Apache license version 2. Procyon is copyrighted by Mike Strobel, 2013 | ||
| 13 | |||
| 14 | Enigma includes unmodified versions of the following libraries which are also released under the Apache license version 2. | ||
| 15 | Guava | ||
| 16 | Javassist | ||
| 17 | JSyntaxPane | ||
| 18 | |||
| 19 | Copies of the GNU Lesser General Public license verion 3 and the Apache license v2 have been included in this distribution. | ||
| 20 | |||
| 21 | |||
| 22 | USING ENIGMA | ||
| 23 | |||
| 24 | Launch the GUI: | ||
| 25 | java -jar enigma.jar | ||
| 26 | |||
| 27 | Use Enigma on the command line: | ||
| 28 | java -cp enigma.jar cuchaz.enigma.CommandMain | ||
| 29 | |||
| 30 | |||
| 31 | OPEN SOURCE | ||
| 32 | |||
| 33 | https://bitbucket.org/cuchaz/enigma | ||
diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..8fa1712 --- /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 0000000..540cfb9 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import java.io.File; | ||
| 14 | import java.io.FileReader; | ||
| 15 | import java.util.jar.JarFile; | ||
| 16 | |||
| 17 | import cuchaz.enigma.Deobfuscator.ProgressListener; | ||
| 18 | import cuchaz.enigma.mapping.Mappings; | ||
| 19 | import cuchaz.enigma.mapping.MappingsReader; | ||
| 20 | |||
| 21 | public 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 0000000..951fa8f --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | public 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 0000000..17bd2f8 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import java.io.File; | ||
| 14 | import java.io.FileReader; | ||
| 15 | import java.io.FileWriter; | ||
| 16 | import java.io.IOException; | ||
| 17 | import java.util.jar.JarFile; | ||
| 18 | |||
| 19 | import cuchaz.enigma.convert.ClassMatches; | ||
| 20 | import cuchaz.enigma.convert.MappingsConverter; | ||
| 21 | import cuchaz.enigma.convert.MatchesReader; | ||
| 22 | import cuchaz.enigma.convert.MatchesWriter; | ||
| 23 | import cuchaz.enigma.convert.MemberMatches; | ||
| 24 | import cuchaz.enigma.gui.ClassMatchingGui; | ||
| 25 | import cuchaz.enigma.gui.MemberMatchingGui; | ||
| 26 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 27 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 28 | import cuchaz.enigma.mapping.ClassMapping; | ||
| 29 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 30 | import cuchaz.enigma.mapping.FieldMapping; | ||
| 31 | import cuchaz.enigma.mapping.MappingParseException; | ||
| 32 | import cuchaz.enigma.mapping.Mappings; | ||
| 33 | import cuchaz.enigma.mapping.MappingsChecker; | ||
| 34 | import cuchaz.enigma.mapping.MappingsReader; | ||
| 35 | import cuchaz.enigma.mapping.MappingsWriter; | ||
| 36 | import cuchaz.enigma.mapping.MethodMapping; | ||
| 37 | |||
| 38 | |||
| 39 | public 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 0000000..08a974a --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import java.io.File; | ||
| 14 | import java.io.FileOutputStream; | ||
| 15 | import java.io.FileWriter; | ||
| 16 | import java.io.IOException; | ||
| 17 | import java.io.StringWriter; | ||
| 18 | import java.util.List; | ||
| 19 | import java.util.Map; | ||
| 20 | import java.util.Set; | ||
| 21 | import java.util.jar.JarEntry; | ||
| 22 | import java.util.jar.JarFile; | ||
| 23 | import java.util.jar.JarOutputStream; | ||
| 24 | |||
| 25 | import javassist.CtClass; | ||
| 26 | import javassist.bytecode.Descriptor; | ||
| 27 | |||
| 28 | import com.google.common.collect.Maps; | ||
| 29 | import com.google.common.collect.Sets; | ||
| 30 | import com.strobel.assembler.metadata.MetadataSystem; | ||
| 31 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 32 | import com.strobel.assembler.metadata.TypeReference; | ||
| 33 | import com.strobel.decompiler.DecompilerContext; | ||
| 34 | import com.strobel.decompiler.DecompilerSettings; | ||
| 35 | import com.strobel.decompiler.PlainTextOutput; | ||
| 36 | import com.strobel.decompiler.languages.java.JavaOutputVisitor; | ||
| 37 | import com.strobel.decompiler.languages.java.ast.AstBuilder; | ||
| 38 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | ||
| 39 | import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; | ||
| 40 | |||
| 41 | import cuchaz.enigma.analysis.EntryReference; | ||
| 42 | import cuchaz.enigma.analysis.JarClassIterator; | ||
| 43 | import cuchaz.enigma.analysis.JarIndex; | ||
| 44 | import cuchaz.enigma.analysis.SourceIndex; | ||
| 45 | import cuchaz.enigma.analysis.SourceIndexVisitor; | ||
| 46 | import cuchaz.enigma.analysis.Token; | ||
| 47 | import cuchaz.enigma.bytecode.ClassProtectifier; | ||
| 48 | import cuchaz.enigma.bytecode.ClassPublifier; | ||
| 49 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 50 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 51 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 52 | import cuchaz.enigma.mapping.ClassMapping; | ||
| 53 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 54 | import cuchaz.enigma.mapping.Entry; | ||
| 55 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 56 | import cuchaz.enigma.mapping.FieldMapping; | ||
| 57 | import cuchaz.enigma.mapping.Mappings; | ||
| 58 | import cuchaz.enigma.mapping.MappingsChecker; | ||
| 59 | import cuchaz.enigma.mapping.MappingsRenamer; | ||
| 60 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 61 | import cuchaz.enigma.mapping.MethodMapping; | ||
| 62 | import cuchaz.enigma.mapping.TranslationDirection; | ||
| 63 | import cuchaz.enigma.mapping.Translator; | ||
| 64 | |||
| 65 | public 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 0000000..d8726d1 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | public 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 0000000..4842a79 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import java.io.File; | ||
| 14 | import java.util.jar.JarFile; | ||
| 15 | |||
| 16 | import cuchaz.enigma.gui.Gui; | ||
| 17 | |||
| 18 | public 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 0000000..73ee41f --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import java.io.File; | ||
| 14 | import java.io.FileReader; | ||
| 15 | import java.io.FileWriter; | ||
| 16 | import java.lang.reflect.Field; | ||
| 17 | import java.util.Map; | ||
| 18 | import java.util.jar.JarFile; | ||
| 19 | |||
| 20 | import javassist.CtClass; | ||
| 21 | import javassist.CtField; | ||
| 22 | |||
| 23 | import com.google.common.collect.Maps; | ||
| 24 | |||
| 25 | import cuchaz.enigma.analysis.JarClassIterator; | ||
| 26 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 27 | import cuchaz.enigma.mapping.ClassMapping; | ||
| 28 | import cuchaz.enigma.mapping.ClassNameReplacer; | ||
| 29 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 30 | import cuchaz.enigma.mapping.FieldMapping; | ||
| 31 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 32 | import cuchaz.enigma.mapping.Mappings; | ||
| 33 | import cuchaz.enigma.mapping.MappingsReader; | ||
| 34 | import cuchaz.enigma.mapping.MappingsWriter; | ||
| 35 | import cuchaz.enigma.mapping.Type; | ||
| 36 | |||
| 37 | public 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 0000000..a2185e5 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import java.io.ByteArrayOutputStream; | ||
| 14 | import java.io.IOException; | ||
| 15 | import java.io.InputStream; | ||
| 16 | import java.util.List; | ||
| 17 | import java.util.Map; | ||
| 18 | import java.util.jar.JarEntry; | ||
| 19 | import java.util.jar.JarFile; | ||
| 20 | |||
| 21 | import javassist.ByteArrayClassPath; | ||
| 22 | import javassist.CannotCompileException; | ||
| 23 | import javassist.ClassPool; | ||
| 24 | import javassist.CtClass; | ||
| 25 | import javassist.NotFoundException; | ||
| 26 | import javassist.bytecode.Descriptor; | ||
| 27 | |||
| 28 | import com.google.common.collect.Lists; | ||
| 29 | import com.google.common.collect.Maps; | ||
| 30 | import com.strobel.assembler.metadata.Buffer; | ||
| 31 | import com.strobel.assembler.metadata.ClasspathTypeLoader; | ||
| 32 | import com.strobel.assembler.metadata.ITypeLoader; | ||
| 33 | |||
| 34 | import cuchaz.enigma.analysis.BridgeMarker; | ||
| 35 | import cuchaz.enigma.analysis.JarIndex; | ||
| 36 | import cuchaz.enigma.bytecode.ClassRenamer; | ||
| 37 | import cuchaz.enigma.bytecode.ClassTranslator; | ||
| 38 | import cuchaz.enigma.bytecode.InnerClassWriter; | ||
| 39 | import cuchaz.enigma.bytecode.LocalVariableRenamer; | ||
| 40 | import cuchaz.enigma.bytecode.MethodParameterWriter; | ||
| 41 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 42 | import cuchaz.enigma.mapping.Translator; | ||
| 43 | |||
| 44 | public 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 0000000..c7e509f --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import java.awt.Desktop; | ||
| 14 | import java.io.Closeable; | ||
| 15 | import java.io.File; | ||
| 16 | import java.io.FileOutputStream; | ||
| 17 | import java.io.IOException; | ||
| 18 | import java.io.InputStream; | ||
| 19 | import java.io.InputStreamReader; | ||
| 20 | import java.net.URI; | ||
| 21 | import java.net.URISyntaxException; | ||
| 22 | import java.util.Arrays; | ||
| 23 | import java.util.jar.JarFile; | ||
| 24 | |||
| 25 | import javassist.CannotCompileException; | ||
| 26 | import javassist.CtClass; | ||
| 27 | import javassist.bytecode.Descriptor; | ||
| 28 | |||
| 29 | import com.google.common.io.CharStreams; | ||
| 30 | |||
| 31 | public class Util { | ||
| 32 | |||
| 33 | public static int combineHashesOrdered(Object... objs) { | ||
| 34 | return combineHashesOrdered(Arrays.asList(objs)); | ||
| 35 | } | ||
| 36 | |||
| 37 | public static int combineHashesOrdered(Iterable<Object> objs) { | ||
| 38 | final int prime = 67; | ||
| 39 | int result = 1; | ||
| 40 | for (Object obj : objs) { | ||
| 41 | result *= prime; | ||
| 42 | if (obj != null) { | ||
| 43 | result += obj.hashCode(); | ||
| 44 | } | ||
| 45 | } | ||
| 46 | return result; | ||
| 47 | } | ||
| 48 | |||
| 49 | public static void closeQuietly(Closeable closeable) { | ||
| 50 | if (closeable != null) { | ||
| 51 | try { | ||
| 52 | closeable.close(); | ||
| 53 | } catch (IOException ex) { | ||
| 54 | // just ignore any further exceptions | ||
| 55 | } | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | public static void closeQuietly(JarFile jarFile) { | ||
| 60 | // silly library should implement Closeable... | ||
| 61 | if (jarFile != null) { | ||
| 62 | try { | ||
| 63 | jarFile.close(); | ||
| 64 | } catch (IOException ex) { | ||
| 65 | // just ignore any further exceptions | ||
| 66 | } | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | public static String readStreamToString(InputStream in) throws IOException { | ||
| 71 | return CharStreams.toString(new InputStreamReader(in, "UTF-8")); | ||
| 72 | } | ||
| 73 | |||
| 74 | public static String readResourceToString(String path) throws IOException { | ||
| 75 | InputStream in = Util.class.getResourceAsStream(path); | ||
| 76 | if (in == null) { | ||
| 77 | throw new IllegalArgumentException("Resource not found! " + path); | ||
| 78 | } | ||
| 79 | return readStreamToString(in); | ||
| 80 | } | ||
| 81 | |||
| 82 | public static void openUrl(String url) { | ||
| 83 | if (Desktop.isDesktopSupported()) { | ||
| 84 | Desktop desktop = Desktop.getDesktop(); | ||
| 85 | try { | ||
| 86 | desktop.browse(new URI(url)); | ||
| 87 | } catch (IOException ex) { | ||
| 88 | throw new Error(ex); | ||
| 89 | } catch (URISyntaxException ex) { | ||
| 90 | throw new IllegalArgumentException(ex); | ||
| 91 | } | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | public static void writeClass(CtClass c) { | ||
| 96 | String name = Descriptor.toJavaName(c.getName()); | ||
| 97 | File file = new File(name + ".class"); | ||
| 98 | try (FileOutputStream out = new FileOutputStream(file)) { | ||
| 99 | out.write(c.toBytecode()); | ||
| 100 | } catch (IOException | CannotCompileException ex) { | ||
| 101 | throw new Error(ex); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | } | ||
diff --git a/src/cuchaz/enigma/analysis/Access.java b/src/cuchaz/enigma/analysis/Access.java new file mode 100644 index 0000000..1c8cfc4 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.lang.reflect.Modifier; | ||
| 14 | |||
| 15 | import javassist.CtBehavior; | ||
| 16 | import javassist.CtField; | ||
| 17 | |||
| 18 | public enum Access { | ||
| 19 | |||
| 20 | Public, | ||
| 21 | Protected, | ||
| 22 | Private; | ||
| 23 | |||
| 24 | public static Access get(CtBehavior behavior) { | ||
| 25 | return get(behavior.getModifiers()); | ||
| 26 | } | ||
| 27 | |||
| 28 | public static Access get(CtField field) { | ||
| 29 | return get(field.getModifiers()); | ||
| 30 | } | ||
| 31 | |||
| 32 | public static Access get(int modifiers) { | ||
| 33 | if (Modifier.isPublic(modifiers)) { | ||
| 34 | return Public; | ||
| 35 | } else if (Modifier.isProtected(modifiers)) { | ||
| 36 | return Protected; | ||
| 37 | } else if (Modifier.isPrivate(modifiers)) { | ||
| 38 | return Private; | ||
| 39 | } | ||
| 40 | // assume public by default | ||
| 41 | return Public; | ||
| 42 | } | ||
| 43 | } | ||
diff --git a/src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java b/src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java new file mode 100644 index 0000000..353a4bf --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.util.Set; | ||
| 14 | |||
| 15 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 16 | import javax.swing.tree.TreeNode; | ||
| 17 | |||
| 18 | import com.google.common.collect.Sets; | ||
| 19 | |||
| 20 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 21 | import cuchaz.enigma.mapping.Entry; | ||
| 22 | import cuchaz.enigma.mapping.Translator; | ||
| 23 | |||
| 24 | public 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 0000000..650b3a7 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import javassist.CtClass; | ||
| 14 | import javassist.CtMethod; | ||
| 15 | import javassist.bytecode.AccessFlag; | ||
| 16 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 17 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 18 | |||
| 19 | public 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 0000000..cc70f51 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.util.List; | ||
| 14 | |||
| 15 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 16 | |||
| 17 | import com.google.common.collect.Lists; | ||
| 18 | |||
| 19 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 20 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 21 | import cuchaz.enigma.mapping.Translator; | ||
| 22 | |||
| 23 | public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { | ||
| 24 | |||
| 25 | private static final long serialVersionUID = 3112703459157851912L; | ||
| 26 | |||
| 27 | private Translator m_deobfuscatingTranslator; | ||
| 28 | private ClassEntry m_entry; | ||
| 29 | |||
| 30 | public ClassImplementationsTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) { | ||
| 31 | m_deobfuscatingTranslator = deobfuscatingTranslator; | ||
| 32 | m_entry = entry; | ||
| 33 | } | ||
| 34 | |||
| 35 | public ClassEntry getClassEntry() { | ||
| 36 | return m_entry; | ||
| 37 | } | ||
| 38 | |||
| 39 | public String getDeobfClassName() { | ||
| 40 | return m_deobfuscatingTranslator.translateClass(m_entry.getClassName()); | ||
| 41 | } | ||
| 42 | |||
| 43 | @Override | ||
| 44 | public String toString() { | ||
| 45 | String className = getDeobfClassName(); | ||
| 46 | if (className == null) { | ||
| 47 | className = m_entry.getClassName(); | ||
| 48 | } | ||
| 49 | return className; | ||
| 50 | } | ||
| 51 | |||
| 52 | public void load(JarIndex index) { | ||
| 53 | // get all method implementations | ||
| 54 | List<ClassImplementationsTreeNode> nodes = Lists.newArrayList(); | ||
| 55 | for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) { | ||
| 56 | nodes.add(new ClassImplementationsTreeNode(m_deobfuscatingTranslator, new ClassEntry(implementingClassName))); | ||
| 57 | } | ||
| 58 | |||
| 59 | // add them to this node | ||
| 60 | for (ClassImplementationsTreeNode node : nodes) { | ||
| 61 | this.add(node); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) { | ||
| 66 | // is this the node? | ||
| 67 | if (node.m_entry.equals(entry)) { | ||
| 68 | return node; | ||
| 69 | } | ||
| 70 | |||
| 71 | // recurse | ||
| 72 | for (int i = 0; i < node.getChildCount(); i++) { | ||
| 73 | ClassImplementationsTreeNode foundNode = findNode((ClassImplementationsTreeNode)node.getChildAt(i), entry); | ||
| 74 | if (foundNode != null) { | ||
| 75 | return foundNode; | ||
| 76 | } | ||
| 77 | } | ||
| 78 | return null; | ||
| 79 | } | ||
| 80 | } | ||
diff --git a/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java b/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java new file mode 100644 index 0000000..7542bd9 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.util.List; | ||
| 14 | |||
| 15 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 16 | |||
| 17 | import com.google.common.collect.Lists; | ||
| 18 | |||
| 19 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 20 | import cuchaz.enigma.mapping.Translator; | ||
| 21 | |||
| 22 | public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { | ||
| 23 | |||
| 24 | private static final long serialVersionUID = 4432367405826178490L; | ||
| 25 | |||
| 26 | private Translator m_deobfuscatingTranslator; | ||
| 27 | private String m_obfClassName; | ||
| 28 | |||
| 29 | public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) { | ||
| 30 | m_deobfuscatingTranslator = deobfuscatingTranslator; | ||
| 31 | m_obfClassName = obfClassName; | ||
| 32 | } | ||
| 33 | |||
| 34 | public String getObfClassName() { | ||
| 35 | return m_obfClassName; | ||
| 36 | } | ||
| 37 | |||
| 38 | public String getDeobfClassName() { | ||
| 39 | return m_deobfuscatingTranslator.translateClass(m_obfClassName); | ||
| 40 | } | ||
| 41 | |||
| 42 | @Override | ||
| 43 | public String toString() { | ||
| 44 | String deobfClassName = getDeobfClassName(); | ||
| 45 | if (deobfClassName != null) { | ||
| 46 | return deobfClassName; | ||
| 47 | } | ||
| 48 | return m_obfClassName; | ||
| 49 | } | ||
| 50 | |||
| 51 | public void load(TranslationIndex ancestries, boolean recurse) { | ||
| 52 | // get all the child nodes | ||
| 53 | List<ClassInheritanceTreeNode> nodes = Lists.newArrayList(); | ||
| 54 | for (ClassEntry subclassEntry : ancestries.getSubclass(new ClassEntry(m_obfClassName))) { | ||
| 55 | nodes.add(new ClassInheritanceTreeNode(m_deobfuscatingTranslator, subclassEntry.getName())); | ||
| 56 | } | ||
| 57 | |||
| 58 | // add them to this node | ||
| 59 | for (ClassInheritanceTreeNode node : nodes) { | ||
| 60 | this.add(node); | ||
| 61 | } | ||
| 62 | |||
| 63 | if (recurse) { | ||
| 64 | for (ClassInheritanceTreeNode node : nodes) { | ||
| 65 | node.load(ancestries, true); | ||
| 66 | } | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) { | ||
| 71 | // is this the node? | ||
| 72 | if (node.getObfClassName().equals(entry.getName())) { | ||
| 73 | return node; | ||
| 74 | } | ||
| 75 | |||
| 76 | // recurse | ||
| 77 | for (int i = 0; i < node.getChildCount(); i++) { | ||
| 78 | ClassInheritanceTreeNode foundNode = findNode((ClassInheritanceTreeNode)node.getChildAt(i), entry); | ||
| 79 | if (foundNode != null) { | ||
| 80 | return foundNode; | ||
| 81 | } | ||
| 82 | } | ||
| 83 | return null; | ||
| 84 | } | ||
| 85 | } | ||
diff --git a/src/cuchaz/enigma/analysis/EntryReference.java b/src/cuchaz/enigma/analysis/EntryReference.java new file mode 100644 index 0000000..8512723 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.util.Arrays; | ||
| 14 | import java.util.List; | ||
| 15 | |||
| 16 | import cuchaz.enigma.Util; | ||
| 17 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 18 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 19 | import cuchaz.enigma.mapping.Entry; | ||
| 20 | |||
| 21 | public 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 0000000..f748274 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.util.AbstractMap; | ||
| 14 | import java.util.List; | ||
| 15 | import java.util.Map; | ||
| 16 | import java.util.Set; | ||
| 17 | |||
| 18 | import com.google.common.collect.Lists; | ||
| 19 | import com.google.common.collect.Multimap; | ||
| 20 | import com.google.common.collect.Sets; | ||
| 21 | |||
| 22 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 23 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 24 | import cuchaz.enigma.mapping.ClassNameReplacer; | ||
| 25 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 26 | import cuchaz.enigma.mapping.Entry; | ||
| 27 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 28 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 29 | import cuchaz.enigma.mapping.Signature; | ||
| 30 | import cuchaz.enigma.mapping.Type; | ||
| 31 | |||
| 32 | public 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 0000000..4ed8fee --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 14 | |||
| 15 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 16 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 17 | import cuchaz.enigma.mapping.Translator; | ||
| 18 | |||
| 19 | public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<FieldEntry,BehaviorEntry> { | ||
| 20 | |||
| 21 | private static final long serialVersionUID = -7934108091928699835L; | ||
| 22 | |||
| 23 | private Translator m_deobfuscatingTranslator; | ||
| 24 | private FieldEntry m_entry; | ||
| 25 | private EntryReference<FieldEntry,BehaviorEntry> m_reference; | ||
| 26 | private Access m_access; | ||
| 27 | |||
| 28 | public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) { | ||
| 29 | m_deobfuscatingTranslator = deobfuscatingTranslator; | ||
| 30 | m_entry = entry; | ||
| 31 | m_reference = null; | ||
| 32 | } | ||
| 33 | |||
| 34 | private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<FieldEntry,BehaviorEntry> reference, Access access) { | ||
| 35 | m_deobfuscatingTranslator = deobfuscatingTranslator; | ||
| 36 | m_entry = reference.entry; | ||
| 37 | m_reference = reference; | ||
| 38 | m_access = access; | ||
| 39 | } | ||
| 40 | |||
| 41 | @Override | ||
| 42 | public FieldEntry getEntry() { | ||
| 43 | return m_entry; | ||
| 44 | } | ||
| 45 | |||
| 46 | @Override | ||
| 47 | public EntryReference<FieldEntry,BehaviorEntry> getReference() { | ||
| 48 | return m_reference; | ||
| 49 | } | ||
| 50 | |||
| 51 | @Override | ||
| 52 | public String toString() { | ||
| 53 | if (m_reference != null) { | ||
| 54 | return String.format("%s (%s)", m_deobfuscatingTranslator.translateEntry(m_reference.context), m_access); | ||
| 55 | } | ||
| 56 | return m_deobfuscatingTranslator.translateEntry(m_entry).toString(); | ||
| 57 | } | ||
| 58 | |||
| 59 | public void load(JarIndex index, boolean recurse) { | ||
| 60 | // get all the child nodes | ||
| 61 | if (m_reference == null) { | ||
| 62 | for (EntryReference<FieldEntry,BehaviorEntry> reference : index.getFieldReferences(m_entry)) { | ||
| 63 | add(new FieldReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_entry))); | ||
| 64 | } | ||
| 65 | } else { | ||
| 66 | for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(m_reference.context)) { | ||
| 67 | add(new BehaviorReferenceTreeNode(m_deobfuscatingTranslator, reference, index.getAccess(m_reference.context))); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | if (recurse && children != null) { | ||
| 72 | for (Object node : children) { | ||
| 73 | if (node instanceof BehaviorReferenceTreeNode) { | ||
| 74 | ((BehaviorReferenceTreeNode)node).load(index, true); | ||
| 75 | } else if (node instanceof FieldReferenceTreeNode) { | ||
| 76 | ((FieldReferenceTreeNode)node).load(index, true); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | } | ||
| 80 | } | ||
| 81 | } | ||
diff --git a/src/cuchaz/enigma/analysis/JarClassIterator.java b/src/cuchaz/enigma/analysis/JarClassIterator.java new file mode 100644 index 0000000..aa58e9e --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.io.ByteArrayOutputStream; | ||
| 14 | import java.io.IOException; | ||
| 15 | import java.io.InputStream; | ||
| 16 | import java.util.Enumeration; | ||
| 17 | import java.util.Iterator; | ||
| 18 | import java.util.List; | ||
| 19 | import java.util.jar.JarEntry; | ||
| 20 | import java.util.jar.JarFile; | ||
| 21 | |||
| 22 | import javassist.ByteArrayClassPath; | ||
| 23 | import javassist.ClassPool; | ||
| 24 | import javassist.CtClass; | ||
| 25 | import javassist.NotFoundException; | ||
| 26 | import javassist.bytecode.Descriptor; | ||
| 27 | |||
| 28 | import com.google.common.collect.Lists; | ||
| 29 | |||
| 30 | import cuchaz.enigma.Constants; | ||
| 31 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 32 | |||
| 33 | public class JarClassIterator implements Iterator<CtClass> { | ||
| 34 | |||
| 35 | private JarFile m_jar; | ||
| 36 | private Iterator<JarEntry> m_iter; | ||
| 37 | |||
| 38 | public JarClassIterator(JarFile jar) { | ||
| 39 | m_jar = jar; | ||
| 40 | |||
| 41 | // get the jar entries that correspond to classes | ||
| 42 | List<JarEntry> classEntries = Lists.newArrayList(); | ||
| 43 | Enumeration<JarEntry> entries = m_jar.entries(); | ||
| 44 | while (entries.hasMoreElements()) { | ||
| 45 | JarEntry entry = entries.nextElement(); | ||
| 46 | |||
| 47 | // is this a class file? | ||
| 48 | if (entry.getName().endsWith(".class")) { | ||
| 49 | classEntries.add(entry); | ||
| 50 | } | ||
| 51 | } | ||
| 52 | m_iter = classEntries.iterator(); | ||
| 53 | } | ||
| 54 | |||
| 55 | @Override | ||
| 56 | public boolean hasNext() { | ||
| 57 | return m_iter.hasNext(); | ||
| 58 | } | ||
| 59 | |||
| 60 | @Override | ||
| 61 | public CtClass next() { | ||
| 62 | JarEntry entry = m_iter.next(); | ||
| 63 | try { | ||
| 64 | return getClass(m_jar, entry); | ||
| 65 | } catch (IOException | NotFoundException ex) { | ||
| 66 | throw new Error("Unable to load class: " + entry.getName()); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | @Override | ||
| 71 | public void remove() { | ||
| 72 | throw new UnsupportedOperationException(); | ||
| 73 | } | ||
| 74 | |||
| 75 | public static List<ClassEntry> getClassEntries(JarFile jar) { | ||
| 76 | List<ClassEntry> classEntries = Lists.newArrayList(); | ||
| 77 | Enumeration<JarEntry> entries = jar.entries(); | ||
| 78 | while (entries.hasMoreElements()) { | ||
| 79 | JarEntry entry = entries.nextElement(); | ||
| 80 | |||
| 81 | // is this a class file? | ||
| 82 | if (!entry.isDirectory() && entry.getName().endsWith(".class")) { | ||
| 83 | classEntries.add(getClassEntry(entry)); | ||
| 84 | } | ||
| 85 | } | ||
| 86 | return classEntries; | ||
| 87 | } | ||
| 88 | |||
| 89 | public static Iterable<CtClass> classes(final JarFile jar) { | ||
| 90 | return new Iterable<CtClass>() { | ||
| 91 | @Override | ||
| 92 | public Iterator<CtClass> iterator() { | ||
| 93 | return new JarClassIterator(jar); | ||
| 94 | } | ||
| 95 | }; | ||
| 96 | } | ||
| 97 | |||
| 98 | public static CtClass getClass(JarFile jar, ClassEntry classEntry) { | ||
| 99 | try { | ||
| 100 | return getClass(jar, new JarEntry(classEntry.getName() + ".class")); | ||
| 101 | } catch (IOException | NotFoundException ex) { | ||
| 102 | throw new Error("Unable to load class: " + classEntry.getName()); | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | private static CtClass getClass(JarFile jar, JarEntry entry) throws IOException, NotFoundException { | ||
| 107 | // read the class into a buffer | ||
| 108 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); | ||
| 109 | byte[] buf = new byte[Constants.KiB]; | ||
| 110 | int totalNumBytesRead = 0; | ||
| 111 | InputStream in = jar.getInputStream(entry); | ||
| 112 | while (in.available() > 0) { | ||
| 113 | int numBytesRead = in.read(buf); | ||
| 114 | if (numBytesRead < 0) { | ||
| 115 | break; | ||
| 116 | } | ||
| 117 | bos.write(buf, 0, numBytesRead); | ||
| 118 | |||
| 119 | // sanity checking | ||
| 120 | totalNumBytesRead += numBytesRead; | ||
| 121 | if (totalNumBytesRead > Constants.MiB) { | ||
| 122 | throw new Error("Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!"); | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | // get a javassist handle for the class | ||
| 127 | String className = Descriptor.toJavaName(getClassEntry(entry).getName()); | ||
| 128 | ClassPool classPool = new ClassPool(); | ||
| 129 | classPool.appendSystemPath(); | ||
| 130 | classPool.insertClassPath(new ByteArrayClassPath(className, bos.toByteArray())); | ||
| 131 | return classPool.get(className); | ||
| 132 | } | ||
| 133 | |||
| 134 | private static ClassEntry getClassEntry(JarEntry entry) { | ||
| 135 | return new ClassEntry(entry.getName().substring(0, entry.getName().length() - ".class".length())); | ||
| 136 | } | ||
| 137 | } | ||
diff --git a/src/cuchaz/enigma/analysis/JarIndex.java b/src/cuchaz/enigma/analysis/JarIndex.java new file mode 100644 index 0000000..5c8ec1c --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.lang.reflect.Modifier; | ||
| 14 | import java.util.Collection; | ||
| 15 | import java.util.Collections; | ||
| 16 | import java.util.HashSet; | ||
| 17 | import java.util.List; | ||
| 18 | import java.util.Map; | ||
| 19 | import java.util.Set; | ||
| 20 | import java.util.jar.JarFile; | ||
| 21 | |||
| 22 | import javassist.CannotCompileException; | ||
| 23 | import javassist.CtBehavior; | ||
| 24 | import javassist.CtClass; | ||
| 25 | import javassist.CtConstructor; | ||
| 26 | import javassist.CtField; | ||
| 27 | import javassist.CtMethod; | ||
| 28 | import javassist.NotFoundException; | ||
| 29 | import javassist.bytecode.AccessFlag; | ||
| 30 | import javassist.bytecode.Descriptor; | ||
| 31 | import javassist.bytecode.EnclosingMethodAttribute; | ||
| 32 | import javassist.bytecode.FieldInfo; | ||
| 33 | import javassist.bytecode.InnerClassesAttribute; | ||
| 34 | import javassist.expr.ConstructorCall; | ||
| 35 | import javassist.expr.ExprEditor; | ||
| 36 | import javassist.expr.FieldAccess; | ||
| 37 | import javassist.expr.MethodCall; | ||
| 38 | import javassist.expr.NewExpr; | ||
| 39 | |||
| 40 | import com.google.common.collect.HashMultimap; | ||
| 41 | import com.google.common.collect.Lists; | ||
| 42 | import com.google.common.collect.Maps; | ||
| 43 | import com.google.common.collect.Multimap; | ||
| 44 | import com.google.common.collect.Sets; | ||
| 45 | |||
| 46 | import cuchaz.enigma.Constants; | ||
| 47 | import cuchaz.enigma.bytecode.ClassRenamer; | ||
| 48 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 49 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 50 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 51 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 52 | import cuchaz.enigma.mapping.Entry; | ||
| 53 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 54 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 55 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 56 | import cuchaz.enigma.mapping.Translator; | ||
| 57 | |||
| 58 | public 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 0000000..aa0aeca --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.util.List; | ||
| 14 | |||
| 15 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 16 | |||
| 17 | import com.google.common.collect.Lists; | ||
| 18 | |||
| 19 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 20 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 21 | import cuchaz.enigma.mapping.Translator; | ||
| 22 | |||
| 23 | public 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 0000000..0da3c8c --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.util.List; | ||
| 14 | |||
| 15 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 16 | |||
| 17 | import com.google.common.collect.Lists; | ||
| 18 | |||
| 19 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 20 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 21 | import cuchaz.enigma.mapping.Translator; | ||
| 22 | |||
| 23 | public class MethodInheritanceTreeNode extends DefaultMutableTreeNode { | ||
| 24 | |||
| 25 | private static final long serialVersionUID = 1096677030991810007L; | ||
| 26 | |||
| 27 | private Translator m_deobfuscatingTranslator; | ||
| 28 | private MethodEntry m_entry; | ||
| 29 | private boolean m_isImplemented; | ||
| 30 | |||
| 31 | public MethodInheritanceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry, boolean isImplemented) { | ||
| 32 | m_deobfuscatingTranslator = deobfuscatingTranslator; | ||
| 33 | m_entry = entry; | ||
| 34 | m_isImplemented = isImplemented; | ||
| 35 | } | ||
| 36 | |||
| 37 | public MethodEntry getMethodEntry() { | ||
| 38 | return m_entry; | ||
| 39 | } | ||
| 40 | |||
| 41 | public String getDeobfClassName() { | ||
| 42 | return m_deobfuscatingTranslator.translateClass(m_entry.getClassName()); | ||
| 43 | } | ||
| 44 | |||
| 45 | public String getDeobfMethodName() { | ||
| 46 | return m_deobfuscatingTranslator.translate(m_entry); | ||
| 47 | } | ||
| 48 | |||
| 49 | public boolean isImplemented() { | ||
| 50 | return m_isImplemented; | ||
| 51 | } | ||
| 52 | |||
| 53 | @Override | ||
| 54 | public String toString() { | ||
| 55 | String className = getDeobfClassName(); | ||
| 56 | if (className == null) { | ||
| 57 | className = m_entry.getClassName(); | ||
| 58 | } | ||
| 59 | |||
| 60 | if (!m_isImplemented) { | ||
| 61 | return className; | ||
| 62 | } else { | ||
| 63 | String methodName = getDeobfMethodName(); | ||
| 64 | if (methodName == null) { | ||
| 65 | methodName = m_entry.getName(); | ||
| 66 | } | ||
| 67 | return className + "." + methodName + "()"; | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | public void load(JarIndex index, boolean recurse) { | ||
| 72 | // get all the child nodes | ||
| 73 | List<MethodInheritanceTreeNode> nodes = Lists.newArrayList(); | ||
| 74 | for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(m_entry.getClassEntry())) { | ||
| 75 | MethodEntry methodEntry = new MethodEntry( | ||
| 76 | subclassEntry, | ||
| 77 | m_entry.getName(), | ||
| 78 | m_entry.getSignature() | ||
| 79 | ); | ||
| 80 | nodes.add(new MethodInheritanceTreeNode( | ||
| 81 | m_deobfuscatingTranslator, | ||
| 82 | methodEntry, | ||
| 83 | index.containsObfBehavior(methodEntry) | ||
| 84 | )); | ||
| 85 | } | ||
| 86 | |||
| 87 | // add them to this node | ||
| 88 | for (MethodInheritanceTreeNode node : nodes) { | ||
| 89 | this.add(node); | ||
| 90 | } | ||
| 91 | |||
| 92 | if (recurse) { | ||
| 93 | for (MethodInheritanceTreeNode node : nodes) { | ||
| 94 | node.load(index, true); | ||
| 95 | } | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | public static MethodInheritanceTreeNode findNode(MethodInheritanceTreeNode node, MethodEntry entry) { | ||
| 100 | // is this the node? | ||
| 101 | if (node.getMethodEntry().equals(entry)) { | ||
| 102 | return node; | ||
| 103 | } | ||
| 104 | |||
| 105 | // recurse | ||
| 106 | for (int i = 0; i < node.getChildCount(); i++) { | ||
| 107 | MethodInheritanceTreeNode foundNode = findNode((MethodInheritanceTreeNode)node.getChildAt(i), entry); | ||
| 108 | if (foundNode != null) { | ||
| 109 | return foundNode; | ||
| 110 | } | ||
| 111 | } | ||
| 112 | return null; | ||
| 113 | } | ||
| 114 | } | ||
diff --git a/src/cuchaz/enigma/analysis/ReferenceTreeNode.java b/src/cuchaz/enigma/analysis/ReferenceTreeNode.java new file mode 100644 index 0000000..4d81bf1 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import cuchaz.enigma.mapping.Entry; | ||
| 14 | |||
| 15 | public 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 0000000..e592a1c --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.util.Map; | ||
| 14 | import java.util.Set; | ||
| 15 | |||
| 16 | import com.google.common.collect.Maps; | ||
| 17 | import com.google.common.collect.Sets; | ||
| 18 | |||
| 19 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 20 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 21 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 22 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 23 | import cuchaz.enigma.mapping.MethodMapping; | ||
| 24 | |||
| 25 | public 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 0000000..3c4ac46 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.util.Collection; | ||
| 14 | import java.util.List; | ||
| 15 | import java.util.Map; | ||
| 16 | import java.util.TreeMap; | ||
| 17 | |||
| 18 | import com.google.common.collect.HashMultimap; | ||
| 19 | import com.google.common.collect.Lists; | ||
| 20 | import com.google.common.collect.Maps; | ||
| 21 | import com.google.common.collect.Multimap; | ||
| 22 | import com.strobel.decompiler.languages.Region; | ||
| 23 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 24 | import com.strobel.decompiler.languages.java.ast.Identifier; | ||
| 25 | |||
| 26 | import cuchaz.enigma.mapping.Entry; | ||
| 27 | |||
| 28 | public 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 0000000..a660a37 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import com.strobel.assembler.metadata.MemberReference; | ||
| 14 | import com.strobel.assembler.metadata.MethodDefinition; | ||
| 15 | import com.strobel.assembler.metadata.MethodReference; | ||
| 16 | import com.strobel.assembler.metadata.ParameterDefinition; | ||
| 17 | import com.strobel.assembler.metadata.TypeReference; | ||
| 18 | import com.strobel.decompiler.languages.TextLocation; | ||
| 19 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 20 | import com.strobel.decompiler.languages.java.ast.IdentifierExpression; | ||
| 21 | import com.strobel.decompiler.languages.java.ast.InvocationExpression; | ||
| 22 | import com.strobel.decompiler.languages.java.ast.Keys; | ||
| 23 | import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; | ||
| 24 | import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; | ||
| 25 | import com.strobel.decompiler.languages.java.ast.ParameterDeclaration; | ||
| 26 | import com.strobel.decompiler.languages.java.ast.SimpleType; | ||
| 27 | import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression; | ||
| 28 | import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression; | ||
| 29 | |||
| 30 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 31 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 32 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 33 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 34 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 35 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 36 | import cuchaz.enigma.mapping.ProcyonEntryFactory; | ||
| 37 | import cuchaz.enigma.mapping.Signature; | ||
| 38 | import cuchaz.enigma.mapping.Type; | ||
| 39 | |||
| 40 | public 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 0000000..db0bc0b --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import com.strobel.assembler.metadata.FieldDefinition; | ||
| 14 | import com.strobel.assembler.metadata.MethodDefinition; | ||
| 15 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 16 | import com.strobel.assembler.metadata.TypeReference; | ||
| 17 | import com.strobel.decompiler.languages.TextLocation; | ||
| 18 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 19 | import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; | ||
| 20 | import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration; | ||
| 21 | import com.strobel.decompiler.languages.java.ast.FieldDeclaration; | ||
| 22 | import com.strobel.decompiler.languages.java.ast.Keys; | ||
| 23 | import com.strobel.decompiler.languages.java.ast.MethodDeclaration; | ||
| 24 | import com.strobel.decompiler.languages.java.ast.SimpleType; | ||
| 25 | import com.strobel.decompiler.languages.java.ast.TypeDeclaration; | ||
| 26 | import com.strobel.decompiler.languages.java.ast.VariableInitializer; | ||
| 27 | |||
| 28 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 29 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 30 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 31 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 32 | import cuchaz.enigma.mapping.ProcyonEntryFactory; | ||
| 33 | |||
| 34 | public 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 0000000..0869826 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 14 | import com.strobel.decompiler.languages.java.ast.Annotation; | ||
| 15 | import com.strobel.decompiler.languages.java.ast.AnonymousObjectCreationExpression; | ||
| 16 | import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression; | ||
| 17 | import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression; | ||
| 18 | import com.strobel.decompiler.languages.java.ast.ArraySpecifier; | ||
| 19 | import com.strobel.decompiler.languages.java.ast.AssertStatement; | ||
| 20 | import com.strobel.decompiler.languages.java.ast.AssignmentExpression; | ||
| 21 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 22 | import com.strobel.decompiler.languages.java.ast.BinaryOperatorExpression; | ||
| 23 | import com.strobel.decompiler.languages.java.ast.BlockStatement; | ||
| 24 | import com.strobel.decompiler.languages.java.ast.BreakStatement; | ||
| 25 | import com.strobel.decompiler.languages.java.ast.CaseLabel; | ||
| 26 | import com.strobel.decompiler.languages.java.ast.CastExpression; | ||
| 27 | import com.strobel.decompiler.languages.java.ast.CatchClause; | ||
| 28 | import com.strobel.decompiler.languages.java.ast.ClassOfExpression; | ||
| 29 | import com.strobel.decompiler.languages.java.ast.Comment; | ||
| 30 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | ||
| 31 | import com.strobel.decompiler.languages.java.ast.ComposedType; | ||
| 32 | import com.strobel.decompiler.languages.java.ast.ConditionalExpression; | ||
| 33 | import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; | ||
| 34 | import com.strobel.decompiler.languages.java.ast.ContinueStatement; | ||
| 35 | import com.strobel.decompiler.languages.java.ast.DoWhileStatement; | ||
| 36 | import com.strobel.decompiler.languages.java.ast.EmptyStatement; | ||
| 37 | import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration; | ||
| 38 | import com.strobel.decompiler.languages.java.ast.ExpressionStatement; | ||
| 39 | import com.strobel.decompiler.languages.java.ast.FieldDeclaration; | ||
| 40 | import com.strobel.decompiler.languages.java.ast.ForEachStatement; | ||
| 41 | import com.strobel.decompiler.languages.java.ast.ForStatement; | ||
| 42 | import com.strobel.decompiler.languages.java.ast.GotoStatement; | ||
| 43 | import com.strobel.decompiler.languages.java.ast.IAstVisitor; | ||
| 44 | import com.strobel.decompiler.languages.java.ast.Identifier; | ||
| 45 | import com.strobel.decompiler.languages.java.ast.IdentifierExpression; | ||
| 46 | import com.strobel.decompiler.languages.java.ast.IfElseStatement; | ||
| 47 | import com.strobel.decompiler.languages.java.ast.ImportDeclaration; | ||
| 48 | import com.strobel.decompiler.languages.java.ast.IndexerExpression; | ||
| 49 | import com.strobel.decompiler.languages.java.ast.InstanceInitializer; | ||
| 50 | import com.strobel.decompiler.languages.java.ast.InstanceOfExpression; | ||
| 51 | import com.strobel.decompiler.languages.java.ast.InvocationExpression; | ||
| 52 | import com.strobel.decompiler.languages.java.ast.JavaTokenNode; | ||
| 53 | import com.strobel.decompiler.languages.java.ast.Keys; | ||
| 54 | import com.strobel.decompiler.languages.java.ast.LabelStatement; | ||
| 55 | import com.strobel.decompiler.languages.java.ast.LabeledStatement; | ||
| 56 | import com.strobel.decompiler.languages.java.ast.LambdaExpression; | ||
| 57 | import com.strobel.decompiler.languages.java.ast.LocalTypeDeclarationStatement; | ||
| 58 | import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; | ||
| 59 | import com.strobel.decompiler.languages.java.ast.MethodDeclaration; | ||
| 60 | import com.strobel.decompiler.languages.java.ast.MethodGroupExpression; | ||
| 61 | import com.strobel.decompiler.languages.java.ast.NewLineNode; | ||
| 62 | import com.strobel.decompiler.languages.java.ast.NullReferenceExpression; | ||
| 63 | import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; | ||
| 64 | import com.strobel.decompiler.languages.java.ast.PackageDeclaration; | ||
| 65 | import com.strobel.decompiler.languages.java.ast.ParameterDeclaration; | ||
| 66 | import com.strobel.decompiler.languages.java.ast.ParenthesizedExpression; | ||
| 67 | import com.strobel.decompiler.languages.java.ast.PrimitiveExpression; | ||
| 68 | import com.strobel.decompiler.languages.java.ast.ReturnStatement; | ||
| 69 | import com.strobel.decompiler.languages.java.ast.SimpleType; | ||
| 70 | import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression; | ||
| 71 | import com.strobel.decompiler.languages.java.ast.SwitchSection; | ||
| 72 | import com.strobel.decompiler.languages.java.ast.SwitchStatement; | ||
| 73 | import com.strobel.decompiler.languages.java.ast.SynchronizedStatement; | ||
| 74 | import com.strobel.decompiler.languages.java.ast.TextNode; | ||
| 75 | import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression; | ||
| 76 | import com.strobel.decompiler.languages.java.ast.ThrowStatement; | ||
| 77 | import com.strobel.decompiler.languages.java.ast.TryCatchStatement; | ||
| 78 | import com.strobel.decompiler.languages.java.ast.TypeDeclaration; | ||
| 79 | import com.strobel.decompiler.languages.java.ast.TypeParameterDeclaration; | ||
| 80 | import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression; | ||
| 81 | import com.strobel.decompiler.languages.java.ast.UnaryOperatorExpression; | ||
| 82 | import com.strobel.decompiler.languages.java.ast.VariableDeclarationStatement; | ||
| 83 | import com.strobel.decompiler.languages.java.ast.VariableInitializer; | ||
| 84 | import com.strobel.decompiler.languages.java.ast.WhileStatement; | ||
| 85 | import com.strobel.decompiler.languages.java.ast.WildcardType; | ||
| 86 | import com.strobel.decompiler.patterns.Pattern; | ||
| 87 | |||
| 88 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 89 | |||
| 90 | public class SourceIndexVisitor implements IAstVisitor<SourceIndex,Void> { | ||
| 91 | |||
| 92 | @Override | ||
| 93 | public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { | ||
| 94 | TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); | ||
| 95 | ClassEntry classEntry = new ClassEntry(def.getInternalName()); | ||
| 96 | index.addDeclaration(node.getNameToken(), classEntry); | ||
| 97 | |||
| 98 | return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); | ||
| 99 | } | ||
| 100 | |||
| 101 | protected Void recurse(AstNode node, SourceIndex index) { | ||
| 102 | for (final AstNode child : node.getChildren()) { | ||
| 103 | child.acceptVisitor(this, index); | ||
| 104 | } | ||
| 105 | return null; | ||
| 106 | } | ||
| 107 | |||
| 108 | @Override | ||
| 109 | public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { | ||
| 110 | return recurse(node, index); | ||
| 111 | } | ||
| 112 | |||
| 113 | @Override | ||
| 114 | public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { | ||
| 115 | return recurse(node, index); | ||
| 116 | } | ||
| 117 | |||
| 118 | @Override | ||
| 119 | public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { | ||
| 120 | return recurse(node, index); | ||
| 121 | } | ||
| 122 | |||
| 123 | @Override | ||
| 124 | public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { | ||
| 125 | return recurse(node, index); | ||
| 126 | } | ||
| 127 | |||
| 128 | @Override | ||
| 129 | public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { | ||
| 130 | return recurse(node, index); | ||
| 131 | } | ||
| 132 | |||
| 133 | @Override | ||
| 134 | public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { | ||
| 135 | return recurse(node, index); | ||
| 136 | } | ||
| 137 | |||
| 138 | @Override | ||
| 139 | public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { | ||
| 140 | return recurse(node, index); | ||
| 141 | } | ||
| 142 | |||
| 143 | @Override | ||
| 144 | public Void visitSimpleType(SimpleType node, SourceIndex index) { | ||
| 145 | return recurse(node, index); | ||
| 146 | } | ||
| 147 | |||
| 148 | @Override | ||
| 149 | public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { | ||
| 150 | return recurse(node, index); | ||
| 151 | } | ||
| 152 | |||
| 153 | @Override | ||
| 154 | public Void visitComment(Comment node, SourceIndex index) { | ||
| 155 | return recurse(node, index); | ||
| 156 | } | ||
| 157 | |||
| 158 | @Override | ||
| 159 | public Void visitPatternPlaceholder(AstNode node, Pattern pattern, SourceIndex index) { | ||
| 160 | return recurse(node, index); | ||
| 161 | } | ||
| 162 | |||
| 163 | @Override | ||
| 164 | public Void visitTypeReference(TypeReferenceExpression node, SourceIndex index) { | ||
| 165 | return recurse(node, index); | ||
| 166 | } | ||
| 167 | |||
| 168 | @Override | ||
| 169 | public Void visitJavaTokenNode(JavaTokenNode node, SourceIndex index) { | ||
| 170 | return recurse(node, index); | ||
| 171 | } | ||
| 172 | |||
| 173 | @Override | ||
| 174 | public Void visitIdentifier(Identifier node, SourceIndex index) { | ||
| 175 | return recurse(node, index); | ||
| 176 | } | ||
| 177 | |||
| 178 | @Override | ||
| 179 | public Void visitNullReferenceExpression(NullReferenceExpression node, SourceIndex index) { | ||
| 180 | return recurse(node, index); | ||
| 181 | } | ||
| 182 | |||
| 183 | @Override | ||
| 184 | public Void visitThisReferenceExpression(ThisReferenceExpression node, SourceIndex index) { | ||
| 185 | return recurse(node, index); | ||
| 186 | } | ||
| 187 | |||
| 188 | @Override | ||
| 189 | public Void visitSuperReferenceExpression(SuperReferenceExpression node, SourceIndex index) { | ||
| 190 | return recurse(node, index); | ||
| 191 | } | ||
| 192 | |||
| 193 | @Override | ||
| 194 | public Void visitClassOfExpression(ClassOfExpression node, SourceIndex index) { | ||
| 195 | return recurse(node, index); | ||
| 196 | } | ||
| 197 | |||
| 198 | @Override | ||
| 199 | public Void visitBlockStatement(BlockStatement node, SourceIndex index) { | ||
| 200 | return recurse(node, index); | ||
| 201 | } | ||
| 202 | |||
| 203 | @Override | ||
| 204 | public Void visitExpressionStatement(ExpressionStatement node, SourceIndex index) { | ||
| 205 | return recurse(node, index); | ||
| 206 | } | ||
| 207 | |||
| 208 | @Override | ||
| 209 | public Void visitBreakStatement(BreakStatement node, SourceIndex index) { | ||
| 210 | return recurse(node, index); | ||
| 211 | } | ||
| 212 | |||
| 213 | @Override | ||
| 214 | public Void visitContinueStatement(ContinueStatement node, SourceIndex index) { | ||
| 215 | return recurse(node, index); | ||
| 216 | } | ||
| 217 | |||
| 218 | @Override | ||
| 219 | public Void visitDoWhileStatement(DoWhileStatement node, SourceIndex index) { | ||
| 220 | return recurse(node, index); | ||
| 221 | } | ||
| 222 | |||
| 223 | @Override | ||
| 224 | public Void visitEmptyStatement(EmptyStatement node, SourceIndex index) { | ||
| 225 | return recurse(node, index); | ||
| 226 | } | ||
| 227 | |||
| 228 | @Override | ||
| 229 | public Void visitIfElseStatement(IfElseStatement node, SourceIndex index) { | ||
| 230 | return recurse(node, index); | ||
| 231 | } | ||
| 232 | |||
| 233 | @Override | ||
| 234 | public Void visitLabelStatement(LabelStatement node, SourceIndex index) { | ||
| 235 | return recurse(node, index); | ||
| 236 | } | ||
| 237 | |||
| 238 | @Override | ||
| 239 | public Void visitLabeledStatement(LabeledStatement node, SourceIndex index) { | ||
| 240 | return recurse(node, index); | ||
| 241 | } | ||
| 242 | |||
| 243 | @Override | ||
| 244 | public Void visitReturnStatement(ReturnStatement node, SourceIndex index) { | ||
| 245 | return recurse(node, index); | ||
| 246 | } | ||
| 247 | |||
| 248 | @Override | ||
| 249 | public Void visitSwitchStatement(SwitchStatement node, SourceIndex index) { | ||
| 250 | return recurse(node, index); | ||
| 251 | } | ||
| 252 | |||
| 253 | @Override | ||
| 254 | public Void visitSwitchSection(SwitchSection node, SourceIndex index) { | ||
| 255 | return recurse(node, index); | ||
| 256 | } | ||
| 257 | |||
| 258 | @Override | ||
| 259 | public Void visitCaseLabel(CaseLabel node, SourceIndex index) { | ||
| 260 | return recurse(node, index); | ||
| 261 | } | ||
| 262 | |||
| 263 | @Override | ||
| 264 | public Void visitThrowStatement(ThrowStatement node, SourceIndex index) { | ||
| 265 | return recurse(node, index); | ||
| 266 | } | ||
| 267 | |||
| 268 | @Override | ||
| 269 | public Void visitCatchClause(CatchClause node, SourceIndex index) { | ||
| 270 | return recurse(node, index); | ||
| 271 | } | ||
| 272 | |||
| 273 | @Override | ||
| 274 | public Void visitAnnotation(Annotation node, SourceIndex index) { | ||
| 275 | return recurse(node, index); | ||
| 276 | } | ||
| 277 | |||
| 278 | @Override | ||
| 279 | public Void visitNewLine(NewLineNode node, SourceIndex index) { | ||
| 280 | return recurse(node, index); | ||
| 281 | } | ||
| 282 | |||
| 283 | @Override | ||
| 284 | public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) { | ||
| 285 | return recurse(node, index); | ||
| 286 | } | ||
| 287 | |||
| 288 | @Override | ||
| 289 | public Void visitVariableInitializer(VariableInitializer node, SourceIndex index) { | ||
| 290 | return recurse(node, index); | ||
| 291 | } | ||
| 292 | |||
| 293 | @Override | ||
| 294 | public Void visitText(TextNode node, SourceIndex index) { | ||
| 295 | return recurse(node, index); | ||
| 296 | } | ||
| 297 | |||
| 298 | @Override | ||
| 299 | public Void visitImportDeclaration(ImportDeclaration node, SourceIndex index) { | ||
| 300 | return recurse(node, index); | ||
| 301 | } | ||
| 302 | |||
| 303 | @Override | ||
| 304 | public Void visitInitializerBlock(InstanceInitializer node, SourceIndex index) { | ||
| 305 | return recurse(node, index); | ||
| 306 | } | ||
| 307 | |||
| 308 | @Override | ||
| 309 | public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, SourceIndex index) { | ||
| 310 | return recurse(node, index); | ||
| 311 | } | ||
| 312 | |||
| 313 | @Override | ||
| 314 | public Void visitCompilationUnit(CompilationUnit node, SourceIndex index) { | ||
| 315 | return recurse(node, index); | ||
| 316 | } | ||
| 317 | |||
| 318 | @Override | ||
| 319 | public Void visitPackageDeclaration(PackageDeclaration node, SourceIndex index) { | ||
| 320 | return recurse(node, index); | ||
| 321 | } | ||
| 322 | |||
| 323 | @Override | ||
| 324 | public Void visitArraySpecifier(ArraySpecifier node, SourceIndex index) { | ||
| 325 | return recurse(node, index); | ||
| 326 | } | ||
| 327 | |||
| 328 | @Override | ||
| 329 | public Void visitComposedType(ComposedType node, SourceIndex index) { | ||
| 330 | return recurse(node, index); | ||
| 331 | } | ||
| 332 | |||
| 333 | @Override | ||
| 334 | public Void visitWhileStatement(WhileStatement node, SourceIndex index) { | ||
| 335 | return recurse(node, index); | ||
| 336 | } | ||
| 337 | |||
| 338 | @Override | ||
| 339 | public Void visitPrimitiveExpression(PrimitiveExpression node, SourceIndex index) { | ||
| 340 | return recurse(node, index); | ||
| 341 | } | ||
| 342 | |||
| 343 | @Override | ||
| 344 | public Void visitCastExpression(CastExpression node, SourceIndex index) { | ||
| 345 | return recurse(node, index); | ||
| 346 | } | ||
| 347 | |||
| 348 | @Override | ||
| 349 | public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, SourceIndex index) { | ||
| 350 | return recurse(node, index); | ||
| 351 | } | ||
| 352 | |||
| 353 | @Override | ||
| 354 | public Void visitInstanceOfExpression(InstanceOfExpression node, SourceIndex index) { | ||
| 355 | return recurse(node, index); | ||
| 356 | } | ||
| 357 | |||
| 358 | @Override | ||
| 359 | public Void visitIndexerExpression(IndexerExpression node, SourceIndex index) { | ||
| 360 | return recurse(node, index); | ||
| 361 | } | ||
| 362 | |||
| 363 | @Override | ||
| 364 | public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, SourceIndex index) { | ||
| 365 | return recurse(node, index); | ||
| 366 | } | ||
| 367 | |||
| 368 | @Override | ||
| 369 | public Void visitConditionalExpression(ConditionalExpression node, SourceIndex index) { | ||
| 370 | return recurse(node, index); | ||
| 371 | } | ||
| 372 | |||
| 373 | @Override | ||
| 374 | public Void visitArrayInitializerExpression(ArrayInitializerExpression node, SourceIndex index) { | ||
| 375 | return recurse(node, index); | ||
| 376 | } | ||
| 377 | |||
| 378 | @Override | ||
| 379 | public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { | ||
| 380 | return recurse(node, index); | ||
| 381 | } | ||
| 382 | |||
| 383 | @Override | ||
| 384 | public Void visitArrayCreationExpression(ArrayCreationExpression node, SourceIndex index) { | ||
| 385 | return recurse(node, index); | ||
| 386 | } | ||
| 387 | |||
| 388 | @Override | ||
| 389 | public Void visitAssignmentExpression(AssignmentExpression node, SourceIndex index) { | ||
| 390 | return recurse(node, index); | ||
| 391 | } | ||
| 392 | |||
| 393 | @Override | ||
| 394 | public Void visitForStatement(ForStatement node, SourceIndex index) { | ||
| 395 | return recurse(node, index); | ||
| 396 | } | ||
| 397 | |||
| 398 | @Override | ||
| 399 | public Void visitForEachStatement(ForEachStatement node, SourceIndex index) { | ||
| 400 | return recurse(node, index); | ||
| 401 | } | ||
| 402 | |||
| 403 | @Override | ||
| 404 | public Void visitTryCatchStatement(TryCatchStatement node, SourceIndex index) { | ||
| 405 | return recurse(node, index); | ||
| 406 | } | ||
| 407 | |||
| 408 | @Override | ||
| 409 | public Void visitGotoStatement(GotoStatement node, SourceIndex index) { | ||
| 410 | return recurse(node, index); | ||
| 411 | } | ||
| 412 | |||
| 413 | @Override | ||
| 414 | public Void visitParenthesizedExpression(ParenthesizedExpression node, SourceIndex index) { | ||
| 415 | return recurse(node, index); | ||
| 416 | } | ||
| 417 | |||
| 418 | @Override | ||
| 419 | public Void visitSynchronizedStatement(SynchronizedStatement node, SourceIndex index) { | ||
| 420 | return recurse(node, index); | ||
| 421 | } | ||
| 422 | |||
| 423 | @Override | ||
| 424 | public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, SourceIndex index) { | ||
| 425 | return recurse(node, index); | ||
| 426 | } | ||
| 427 | |||
| 428 | @Override | ||
| 429 | public Void visitWildcardType(WildcardType node, SourceIndex index) { | ||
| 430 | return recurse(node, index); | ||
| 431 | } | ||
| 432 | |||
| 433 | @Override | ||
| 434 | public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) { | ||
| 435 | return recurse(node, index); | ||
| 436 | } | ||
| 437 | |||
| 438 | @Override | ||
| 439 | public Void visitAssertStatement(AssertStatement node, SourceIndex index) { | ||
| 440 | return recurse(node, index); | ||
| 441 | } | ||
| 442 | |||
| 443 | @Override | ||
| 444 | public Void visitLambdaExpression(LambdaExpression node, SourceIndex index) { | ||
| 445 | return recurse(node, index); | ||
| 446 | } | ||
| 447 | |||
| 448 | @Override | ||
| 449 | public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, SourceIndex index) { | ||
| 450 | return recurse(node, index); | ||
| 451 | } | ||
| 452 | } | ||
diff --git a/src/cuchaz/enigma/analysis/Token.java b/src/cuchaz/enigma/analysis/Token.java new file mode 100644 index 0000000..76d6327 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | public class Token implements Comparable<Token> { | ||
| 14 | |||
| 15 | public int start; | ||
| 16 | public int end; | ||
| 17 | public String text; | ||
| 18 | |||
| 19 | public Token(int start, int end) { | ||
| 20 | this(start, end, null); | ||
| 21 | } | ||
| 22 | |||
| 23 | public Token(int start, int end, String source) { | ||
| 24 | this.start = start; | ||
| 25 | this.end = end; | ||
| 26 | if (source != null) { | ||
| 27 | this.text = source.substring(start, end); | ||
| 28 | } | ||
| 29 | } | ||
| 30 | |||
| 31 | public boolean contains(int pos) { | ||
| 32 | return pos >= start && pos <= end; | ||
| 33 | } | ||
| 34 | |||
| 35 | @Override | ||
| 36 | public int compareTo(Token other) { | ||
| 37 | return start - other.start; | ||
| 38 | } | ||
| 39 | |||
| 40 | @Override | ||
| 41 | public boolean equals(Object other) { | ||
| 42 | if (other instanceof Token) { | ||
| 43 | return equals((Token)other); | ||
| 44 | } | ||
| 45 | return false; | ||
| 46 | } | ||
| 47 | |||
| 48 | public boolean equals(Token other) { | ||
| 49 | return start == other.start && end == other.end; | ||
| 50 | } | ||
| 51 | |||
| 52 | @Override | ||
| 53 | public String toString() { | ||
| 54 | return String.format("[%d,%d]", start, end); | ||
| 55 | } | ||
| 56 | } | ||
diff --git a/src/cuchaz/enigma/analysis/TranslationIndex.java b/src/cuchaz/enigma/analysis/TranslationIndex.java new file mode 100644 index 0000000..a491cfc --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.io.IOException; | ||
| 14 | import java.io.InputStream; | ||
| 15 | import java.io.ObjectInputStream; | ||
| 16 | import java.io.ObjectOutputStream; | ||
| 17 | import java.io.OutputStream; | ||
| 18 | import java.io.Serializable; | ||
| 19 | import java.util.Collection; | ||
| 20 | import java.util.HashMap; | ||
| 21 | import java.util.List; | ||
| 22 | import java.util.Map; | ||
| 23 | import java.util.Set; | ||
| 24 | import java.util.zip.GZIPInputStream; | ||
| 25 | import java.util.zip.GZIPOutputStream; | ||
| 26 | |||
| 27 | import javassist.CtBehavior; | ||
| 28 | import javassist.CtClass; | ||
| 29 | import javassist.CtField; | ||
| 30 | import javassist.bytecode.Descriptor; | ||
| 31 | |||
| 32 | import com.google.common.collect.HashMultimap; | ||
| 33 | import com.google.common.collect.Lists; | ||
| 34 | import com.google.common.collect.Maps; | ||
| 35 | import com.google.common.collect.Multimap; | ||
| 36 | |||
| 37 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 38 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 39 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 40 | import cuchaz.enigma.mapping.Entry; | ||
| 41 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 42 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 43 | import cuchaz.enigma.mapping.Translator; | ||
| 44 | |||
| 45 | public 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 0000000..0a90bac --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.io.File; | ||
| 14 | import java.io.FileWriter; | ||
| 15 | import java.io.IOException; | ||
| 16 | import java.io.Writer; | ||
| 17 | |||
| 18 | import com.strobel.componentmodel.Key; | ||
| 19 | import com.strobel.decompiler.languages.java.ast.Annotation; | ||
| 20 | import com.strobel.decompiler.languages.java.ast.AnonymousObjectCreationExpression; | ||
| 21 | import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression; | ||
| 22 | import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression; | ||
| 23 | import com.strobel.decompiler.languages.java.ast.ArraySpecifier; | ||
| 24 | import com.strobel.decompiler.languages.java.ast.AssertStatement; | ||
| 25 | import com.strobel.decompiler.languages.java.ast.AssignmentExpression; | ||
| 26 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 27 | import com.strobel.decompiler.languages.java.ast.BinaryOperatorExpression; | ||
| 28 | import com.strobel.decompiler.languages.java.ast.BlockStatement; | ||
| 29 | import com.strobel.decompiler.languages.java.ast.BreakStatement; | ||
| 30 | import com.strobel.decompiler.languages.java.ast.CaseLabel; | ||
| 31 | import com.strobel.decompiler.languages.java.ast.CastExpression; | ||
| 32 | import com.strobel.decompiler.languages.java.ast.CatchClause; | ||
| 33 | import com.strobel.decompiler.languages.java.ast.ClassOfExpression; | ||
| 34 | import com.strobel.decompiler.languages.java.ast.Comment; | ||
| 35 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | ||
| 36 | import com.strobel.decompiler.languages.java.ast.ComposedType; | ||
| 37 | import com.strobel.decompiler.languages.java.ast.ConditionalExpression; | ||
| 38 | import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; | ||
| 39 | import com.strobel.decompiler.languages.java.ast.ContinueStatement; | ||
| 40 | import com.strobel.decompiler.languages.java.ast.DoWhileStatement; | ||
| 41 | import com.strobel.decompiler.languages.java.ast.EmptyStatement; | ||
| 42 | import com.strobel.decompiler.languages.java.ast.EnumValueDeclaration; | ||
| 43 | import com.strobel.decompiler.languages.java.ast.ExpressionStatement; | ||
| 44 | import com.strobel.decompiler.languages.java.ast.FieldDeclaration; | ||
| 45 | import com.strobel.decompiler.languages.java.ast.ForEachStatement; | ||
| 46 | import com.strobel.decompiler.languages.java.ast.ForStatement; | ||
| 47 | import com.strobel.decompiler.languages.java.ast.GotoStatement; | ||
| 48 | import com.strobel.decompiler.languages.java.ast.IAstVisitor; | ||
| 49 | import com.strobel.decompiler.languages.java.ast.Identifier; | ||
| 50 | import com.strobel.decompiler.languages.java.ast.IdentifierExpression; | ||
| 51 | import com.strobel.decompiler.languages.java.ast.IfElseStatement; | ||
| 52 | import com.strobel.decompiler.languages.java.ast.ImportDeclaration; | ||
| 53 | import com.strobel.decompiler.languages.java.ast.IndexerExpression; | ||
| 54 | import com.strobel.decompiler.languages.java.ast.InstanceInitializer; | ||
| 55 | import com.strobel.decompiler.languages.java.ast.InstanceOfExpression; | ||
| 56 | import com.strobel.decompiler.languages.java.ast.InvocationExpression; | ||
| 57 | import com.strobel.decompiler.languages.java.ast.JavaTokenNode; | ||
| 58 | import com.strobel.decompiler.languages.java.ast.Keys; | ||
| 59 | import com.strobel.decompiler.languages.java.ast.LabelStatement; | ||
| 60 | import com.strobel.decompiler.languages.java.ast.LabeledStatement; | ||
| 61 | import com.strobel.decompiler.languages.java.ast.LambdaExpression; | ||
| 62 | import com.strobel.decompiler.languages.java.ast.LocalTypeDeclarationStatement; | ||
| 63 | import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; | ||
| 64 | import com.strobel.decompiler.languages.java.ast.MethodDeclaration; | ||
| 65 | import com.strobel.decompiler.languages.java.ast.MethodGroupExpression; | ||
| 66 | import com.strobel.decompiler.languages.java.ast.NewLineNode; | ||
| 67 | import com.strobel.decompiler.languages.java.ast.NullReferenceExpression; | ||
| 68 | import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; | ||
| 69 | import com.strobel.decompiler.languages.java.ast.PackageDeclaration; | ||
| 70 | import com.strobel.decompiler.languages.java.ast.ParameterDeclaration; | ||
| 71 | import com.strobel.decompiler.languages.java.ast.ParenthesizedExpression; | ||
| 72 | import com.strobel.decompiler.languages.java.ast.PrimitiveExpression; | ||
| 73 | import com.strobel.decompiler.languages.java.ast.ReturnStatement; | ||
| 74 | import com.strobel.decompiler.languages.java.ast.SimpleType; | ||
| 75 | import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression; | ||
| 76 | import com.strobel.decompiler.languages.java.ast.SwitchSection; | ||
| 77 | import com.strobel.decompiler.languages.java.ast.SwitchStatement; | ||
| 78 | import com.strobel.decompiler.languages.java.ast.SynchronizedStatement; | ||
| 79 | import com.strobel.decompiler.languages.java.ast.TextNode; | ||
| 80 | import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression; | ||
| 81 | import com.strobel.decompiler.languages.java.ast.ThrowStatement; | ||
| 82 | import com.strobel.decompiler.languages.java.ast.TryCatchStatement; | ||
| 83 | import com.strobel.decompiler.languages.java.ast.TypeDeclaration; | ||
| 84 | import com.strobel.decompiler.languages.java.ast.TypeParameterDeclaration; | ||
| 85 | import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression; | ||
| 86 | import com.strobel.decompiler.languages.java.ast.UnaryOperatorExpression; | ||
| 87 | import com.strobel.decompiler.languages.java.ast.VariableDeclarationStatement; | ||
| 88 | import com.strobel.decompiler.languages.java.ast.VariableInitializer; | ||
| 89 | import com.strobel.decompiler.languages.java.ast.WhileStatement; | ||
| 90 | import com.strobel.decompiler.languages.java.ast.WildcardType; | ||
| 91 | import com.strobel.decompiler.patterns.Pattern; | ||
| 92 | |||
| 93 | public class TreeDumpVisitor implements IAstVisitor<Void,Void> { | ||
| 94 | |||
| 95 | private File m_file; | ||
| 96 | private Writer m_out; | ||
| 97 | |||
| 98 | public TreeDumpVisitor(File file) { | ||
| 99 | m_file = file; | ||
| 100 | m_out = null; | ||
| 101 | } | ||
| 102 | |||
| 103 | @Override | ||
| 104 | public Void visitCompilationUnit(CompilationUnit node, Void ignored) { | ||
| 105 | try { | ||
| 106 | m_out = new FileWriter(m_file); | ||
| 107 | recurse(node, ignored); | ||
| 108 | m_out.close(); | ||
| 109 | return null; | ||
| 110 | } catch (IOException ex) { | ||
| 111 | throw new Error(ex); | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | private Void recurse(AstNode node, Void ignored) { | ||
| 116 | // show the tree | ||
| 117 | try { | ||
| 118 | m_out.write(getIndent(node) + node.getClass().getSimpleName() + " " + getText(node) + " " + dumpUserData(node) + " " + node.getRegion() + "\n"); | ||
| 119 | } catch (IOException ex) { | ||
| 120 | throw new Error(ex); | ||
| 121 | } | ||
| 122 | |||
| 123 | // recurse | ||
| 124 | for (final AstNode child : node.getChildren()) { | ||
| 125 | child.acceptVisitor(this, ignored); | ||
| 126 | } | ||
| 127 | return null; | ||
| 128 | } | ||
| 129 | |||
| 130 | private String getText(AstNode node) { | ||
| 131 | if (node instanceof Identifier) { | ||
| 132 | return "\"" + ((Identifier)node).getName() + "\""; | ||
| 133 | } | ||
| 134 | return ""; | ||
| 135 | } | ||
| 136 | |||
| 137 | private String dumpUserData(AstNode node) { | ||
| 138 | StringBuilder buf = new StringBuilder(); | ||
| 139 | for (Key<?> key : Keys.ALL_KEYS) { | ||
| 140 | Object val = node.getUserData(key); | ||
| 141 | if (val != null) { | ||
| 142 | buf.append(String.format(" [%s=%s]", key, val)); | ||
| 143 | } | ||
| 144 | } | ||
| 145 | return buf.toString(); | ||
| 146 | } | ||
| 147 | |||
| 148 | private String getIndent(AstNode node) { | ||
| 149 | StringBuilder buf = new StringBuilder(); | ||
| 150 | int depth = getDepth(node); | ||
| 151 | for (int i = 0; i < depth; i++) { | ||
| 152 | buf.append("\t"); | ||
| 153 | } | ||
| 154 | return buf.toString(); | ||
| 155 | } | ||
| 156 | |||
| 157 | private int getDepth(AstNode node) { | ||
| 158 | int depth = -1; | ||
| 159 | while (node != null) { | ||
| 160 | depth++; | ||
| 161 | node = node.getParent(); | ||
| 162 | } | ||
| 163 | return depth; | ||
| 164 | } | ||
| 165 | |||
| 166 | // OVERRIDES WE DON'T CARE ABOUT | ||
| 167 | |||
| 168 | @Override | ||
| 169 | public Void visitInvocationExpression(InvocationExpression node, Void ignored) { | ||
| 170 | return recurse(node, ignored); | ||
| 171 | } | ||
| 172 | |||
| 173 | @Override | ||
| 174 | public Void visitMemberReferenceExpression(MemberReferenceExpression node, Void ignored) { | ||
| 175 | return recurse(node, ignored); | ||
| 176 | } | ||
| 177 | |||
| 178 | @Override | ||
| 179 | public Void visitSimpleType(SimpleType node, Void ignored) { | ||
| 180 | return recurse(node, ignored); | ||
| 181 | } | ||
| 182 | |||
| 183 | @Override | ||
| 184 | public Void visitMethodDeclaration(MethodDeclaration node, Void ignored) { | ||
| 185 | return recurse(node, ignored); | ||
| 186 | } | ||
| 187 | |||
| 188 | @Override | ||
| 189 | public Void visitConstructorDeclaration(ConstructorDeclaration node, Void ignored) { | ||
| 190 | return recurse(node, ignored); | ||
| 191 | } | ||
| 192 | |||
| 193 | @Override | ||
| 194 | public Void visitParameterDeclaration(ParameterDeclaration node, Void ignored) { | ||
| 195 | return recurse(node, ignored); | ||
| 196 | } | ||
| 197 | |||
| 198 | @Override | ||
| 199 | public Void visitFieldDeclaration(FieldDeclaration node, Void ignored) { | ||
| 200 | return recurse(node, ignored); | ||
| 201 | } | ||
| 202 | |||
| 203 | @Override | ||
| 204 | public Void visitTypeDeclaration(TypeDeclaration node, Void ignored) { | ||
| 205 | return recurse(node, ignored); | ||
| 206 | } | ||
| 207 | |||
| 208 | @Override | ||
| 209 | public Void visitComment(Comment node, Void ignored) { | ||
| 210 | return recurse(node, ignored); | ||
| 211 | } | ||
| 212 | |||
| 213 | @Override | ||
| 214 | public Void visitPatternPlaceholder(AstNode node, Pattern pattern, Void ignored) { | ||
| 215 | return recurse(node, ignored); | ||
| 216 | } | ||
| 217 | |||
| 218 | @Override | ||
| 219 | public Void visitTypeReference(TypeReferenceExpression node, Void ignored) { | ||
| 220 | return recurse(node, ignored); | ||
| 221 | } | ||
| 222 | |||
| 223 | @Override | ||
| 224 | public Void visitJavaTokenNode(JavaTokenNode node, Void ignored) { | ||
| 225 | return recurse(node, ignored); | ||
| 226 | } | ||
| 227 | |||
| 228 | @Override | ||
| 229 | public Void visitIdentifier(Identifier node, Void ignored) { | ||
| 230 | return recurse(node, ignored); | ||
| 231 | } | ||
| 232 | |||
| 233 | @Override | ||
| 234 | public Void visitNullReferenceExpression(NullReferenceExpression node, Void ignored) { | ||
| 235 | return recurse(node, ignored); | ||
| 236 | } | ||
| 237 | |||
| 238 | @Override | ||
| 239 | public Void visitThisReferenceExpression(ThisReferenceExpression node, Void ignored) { | ||
| 240 | return recurse(node, ignored); | ||
| 241 | } | ||
| 242 | |||
| 243 | @Override | ||
| 244 | public Void visitSuperReferenceExpression(SuperReferenceExpression node, Void ignored) { | ||
| 245 | return recurse(node, ignored); | ||
| 246 | } | ||
| 247 | |||
| 248 | @Override | ||
| 249 | public Void visitClassOfExpression(ClassOfExpression node, Void ignored) { | ||
| 250 | return recurse(node, ignored); | ||
| 251 | } | ||
| 252 | |||
| 253 | @Override | ||
| 254 | public Void visitBlockStatement(BlockStatement node, Void ignored) { | ||
| 255 | return recurse(node, ignored); | ||
| 256 | } | ||
| 257 | |||
| 258 | @Override | ||
| 259 | public Void visitExpressionStatement(ExpressionStatement node, Void ignored) { | ||
| 260 | return recurse(node, ignored); | ||
| 261 | } | ||
| 262 | |||
| 263 | @Override | ||
| 264 | public Void visitBreakStatement(BreakStatement node, Void ignored) { | ||
| 265 | return recurse(node, ignored); | ||
| 266 | } | ||
| 267 | |||
| 268 | @Override | ||
| 269 | public Void visitContinueStatement(ContinueStatement node, Void ignored) { | ||
| 270 | return recurse(node, ignored); | ||
| 271 | } | ||
| 272 | |||
| 273 | @Override | ||
| 274 | public Void visitDoWhileStatement(DoWhileStatement node, Void ignored) { | ||
| 275 | return recurse(node, ignored); | ||
| 276 | } | ||
| 277 | |||
| 278 | @Override | ||
| 279 | public Void visitEmptyStatement(EmptyStatement node, Void ignored) { | ||
| 280 | return recurse(node, ignored); | ||
| 281 | } | ||
| 282 | |||
| 283 | @Override | ||
| 284 | public Void visitIfElseStatement(IfElseStatement node, Void ignored) { | ||
| 285 | return recurse(node, ignored); | ||
| 286 | } | ||
| 287 | |||
| 288 | @Override | ||
| 289 | public Void visitLabelStatement(LabelStatement node, Void ignored) { | ||
| 290 | return recurse(node, ignored); | ||
| 291 | } | ||
| 292 | |||
| 293 | @Override | ||
| 294 | public Void visitLabeledStatement(LabeledStatement node, Void ignored) { | ||
| 295 | return recurse(node, ignored); | ||
| 296 | } | ||
| 297 | |||
| 298 | @Override | ||
| 299 | public Void visitReturnStatement(ReturnStatement node, Void ignored) { | ||
| 300 | return recurse(node, ignored); | ||
| 301 | } | ||
| 302 | |||
| 303 | @Override | ||
| 304 | public Void visitSwitchStatement(SwitchStatement node, Void ignored) { | ||
| 305 | return recurse(node, ignored); | ||
| 306 | } | ||
| 307 | |||
| 308 | @Override | ||
| 309 | public Void visitSwitchSection(SwitchSection node, Void ignored) { | ||
| 310 | return recurse(node, ignored); | ||
| 311 | } | ||
| 312 | |||
| 313 | @Override | ||
| 314 | public Void visitCaseLabel(CaseLabel node, Void ignored) { | ||
| 315 | return recurse(node, ignored); | ||
| 316 | } | ||
| 317 | |||
| 318 | @Override | ||
| 319 | public Void visitThrowStatement(ThrowStatement node, Void ignored) { | ||
| 320 | return recurse(node, ignored); | ||
| 321 | } | ||
| 322 | |||
| 323 | @Override | ||
| 324 | public Void visitCatchClause(CatchClause node, Void ignored) { | ||
| 325 | return recurse(node, ignored); | ||
| 326 | } | ||
| 327 | |||
| 328 | @Override | ||
| 329 | public Void visitAnnotation(Annotation node, Void ignored) { | ||
| 330 | return recurse(node, ignored); | ||
| 331 | } | ||
| 332 | |||
| 333 | @Override | ||
| 334 | public Void visitNewLine(NewLineNode node, Void ignored) { | ||
| 335 | return recurse(node, ignored); | ||
| 336 | } | ||
| 337 | |||
| 338 | @Override | ||
| 339 | public Void visitVariableDeclaration(VariableDeclarationStatement node, Void ignored) { | ||
| 340 | return recurse(node, ignored); | ||
| 341 | } | ||
| 342 | |||
| 343 | @Override | ||
| 344 | public Void visitVariableInitializer(VariableInitializer node, Void ignored) { | ||
| 345 | return recurse(node, ignored); | ||
| 346 | } | ||
| 347 | |||
| 348 | @Override | ||
| 349 | public Void visitText(TextNode node, Void ignored) { | ||
| 350 | return recurse(node, ignored); | ||
| 351 | } | ||
| 352 | |||
| 353 | @Override | ||
| 354 | public Void visitImportDeclaration(ImportDeclaration node, Void ignored) { | ||
| 355 | return recurse(node, ignored); | ||
| 356 | } | ||
| 357 | |||
| 358 | @Override | ||
| 359 | public Void visitInitializerBlock(InstanceInitializer node, Void ignored) { | ||
| 360 | return recurse(node, ignored); | ||
| 361 | } | ||
| 362 | |||
| 363 | @Override | ||
| 364 | public Void visitTypeParameterDeclaration(TypeParameterDeclaration node, Void ignored) { | ||
| 365 | return recurse(node, ignored); | ||
| 366 | } | ||
| 367 | |||
| 368 | @Override | ||
| 369 | public Void visitPackageDeclaration(PackageDeclaration node, Void ignored) { | ||
| 370 | return recurse(node, ignored); | ||
| 371 | } | ||
| 372 | |||
| 373 | @Override | ||
| 374 | public Void visitArraySpecifier(ArraySpecifier node, Void ignored) { | ||
| 375 | return recurse(node, ignored); | ||
| 376 | } | ||
| 377 | |||
| 378 | @Override | ||
| 379 | public Void visitComposedType(ComposedType node, Void ignored) { | ||
| 380 | return recurse(node, ignored); | ||
| 381 | } | ||
| 382 | |||
| 383 | @Override | ||
| 384 | public Void visitWhileStatement(WhileStatement node, Void ignored) { | ||
| 385 | return recurse(node, ignored); | ||
| 386 | } | ||
| 387 | |||
| 388 | @Override | ||
| 389 | public Void visitPrimitiveExpression(PrimitiveExpression node, Void ignored) { | ||
| 390 | return recurse(node, ignored); | ||
| 391 | } | ||
| 392 | |||
| 393 | @Override | ||
| 394 | public Void visitCastExpression(CastExpression node, Void ignored) { | ||
| 395 | return recurse(node, ignored); | ||
| 396 | } | ||
| 397 | |||
| 398 | @Override | ||
| 399 | public Void visitBinaryOperatorExpression(BinaryOperatorExpression node, Void ignored) { | ||
| 400 | return recurse(node, ignored); | ||
| 401 | } | ||
| 402 | |||
| 403 | @Override | ||
| 404 | public Void visitInstanceOfExpression(InstanceOfExpression node, Void ignored) { | ||
| 405 | return recurse(node, ignored); | ||
| 406 | } | ||
| 407 | |||
| 408 | @Override | ||
| 409 | public Void visitIndexerExpression(IndexerExpression node, Void ignored) { | ||
| 410 | return recurse(node, ignored); | ||
| 411 | } | ||
| 412 | |||
| 413 | @Override | ||
| 414 | public Void visitIdentifierExpression(IdentifierExpression node, Void ignored) { | ||
| 415 | return recurse(node, ignored); | ||
| 416 | } | ||
| 417 | |||
| 418 | @Override | ||
| 419 | public Void visitUnaryOperatorExpression(UnaryOperatorExpression node, Void ignored) { | ||
| 420 | return recurse(node, ignored); | ||
| 421 | } | ||
| 422 | |||
| 423 | @Override | ||
| 424 | public Void visitConditionalExpression(ConditionalExpression node, Void ignored) { | ||
| 425 | return recurse(node, ignored); | ||
| 426 | } | ||
| 427 | |||
| 428 | @Override | ||
| 429 | public Void visitArrayInitializerExpression(ArrayInitializerExpression node, Void ignored) { | ||
| 430 | return recurse(node, ignored); | ||
| 431 | } | ||
| 432 | |||
| 433 | @Override | ||
| 434 | public Void visitObjectCreationExpression(ObjectCreationExpression node, Void ignored) { | ||
| 435 | return recurse(node, ignored); | ||
| 436 | } | ||
| 437 | |||
| 438 | @Override | ||
| 439 | public Void visitArrayCreationExpression(ArrayCreationExpression node, Void ignored) { | ||
| 440 | return recurse(node, ignored); | ||
| 441 | } | ||
| 442 | |||
| 443 | @Override | ||
| 444 | public Void visitAssignmentExpression(AssignmentExpression node, Void ignored) { | ||
| 445 | return recurse(node, ignored); | ||
| 446 | } | ||
| 447 | |||
| 448 | @Override | ||
| 449 | public Void visitForStatement(ForStatement node, Void ignored) { | ||
| 450 | return recurse(node, ignored); | ||
| 451 | } | ||
| 452 | |||
| 453 | @Override | ||
| 454 | public Void visitForEachStatement(ForEachStatement node, Void ignored) { | ||
| 455 | return recurse(node, ignored); | ||
| 456 | } | ||
| 457 | |||
| 458 | @Override | ||
| 459 | public Void visitTryCatchStatement(TryCatchStatement node, Void ignored) { | ||
| 460 | return recurse(node, ignored); | ||
| 461 | } | ||
| 462 | |||
| 463 | @Override | ||
| 464 | public Void visitGotoStatement(GotoStatement node, Void ignored) { | ||
| 465 | return recurse(node, ignored); | ||
| 466 | } | ||
| 467 | |||
| 468 | @Override | ||
| 469 | public Void visitParenthesizedExpression(ParenthesizedExpression node, Void ignored) { | ||
| 470 | return recurse(node, ignored); | ||
| 471 | } | ||
| 472 | |||
| 473 | @Override | ||
| 474 | public Void visitSynchronizedStatement(SynchronizedStatement node, Void ignored) { | ||
| 475 | return recurse(node, ignored); | ||
| 476 | } | ||
| 477 | |||
| 478 | @Override | ||
| 479 | public Void visitAnonymousObjectCreationExpression(AnonymousObjectCreationExpression node, Void ignored) { | ||
| 480 | return recurse(node, ignored); | ||
| 481 | } | ||
| 482 | |||
| 483 | @Override | ||
| 484 | public Void visitWildcardType(WildcardType node, Void ignored) { | ||
| 485 | return recurse(node, ignored); | ||
| 486 | } | ||
| 487 | |||
| 488 | @Override | ||
| 489 | public Void visitMethodGroupExpression(MethodGroupExpression node, Void ignored) { | ||
| 490 | return recurse(node, ignored); | ||
| 491 | } | ||
| 492 | |||
| 493 | @Override | ||
| 494 | public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void ignored) { | ||
| 495 | return recurse(node, ignored); | ||
| 496 | } | ||
| 497 | |||
| 498 | @Override | ||
| 499 | public Void visitAssertStatement(AssertStatement node, Void ignored) { | ||
| 500 | return recurse(node, ignored); | ||
| 501 | } | ||
| 502 | |||
| 503 | @Override | ||
| 504 | public Void visitLambdaExpression(LambdaExpression node, Void ignored) { | ||
| 505 | return recurse(node, ignored); | ||
| 506 | } | ||
| 507 | |||
| 508 | @Override | ||
| 509 | public Void visitLocalTypeDeclarationStatement(LocalTypeDeclarationStatement node, Void ignored) { | ||
| 510 | return recurse(node, ignored); | ||
| 511 | } | ||
| 512 | } | ||
diff --git a/src/cuchaz/enigma/bytecode/CheckCastIterator.java b/src/cuchaz/enigma/bytecode/CheckCastIterator.java new file mode 100644 index 0000000..517b9d6 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import java.util.Iterator; | ||
| 14 | |||
| 15 | import javassist.bytecode.BadBytecode; | ||
| 16 | import javassist.bytecode.CodeAttribute; | ||
| 17 | import javassist.bytecode.CodeIterator; | ||
| 18 | import javassist.bytecode.ConstPool; | ||
| 19 | import javassist.bytecode.Descriptor; | ||
| 20 | import javassist.bytecode.Opcode; | ||
| 21 | import cuchaz.enigma.bytecode.CheckCastIterator.CheckCast; | ||
| 22 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 23 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 24 | import cuchaz.enigma.mapping.Signature; | ||
| 25 | |||
| 26 | public 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 0000000..f1ee4e7 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import javassist.CtBehavior; | ||
| 14 | import javassist.CtClass; | ||
| 15 | import javassist.CtField; | ||
| 16 | import javassist.bytecode.AccessFlag; | ||
| 17 | import javassist.bytecode.InnerClassesAttribute; | ||
| 18 | |||
| 19 | |||
| 20 | public 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 0000000..dbefd42 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import javassist.CtBehavior; | ||
| 14 | import javassist.CtClass; | ||
| 15 | import javassist.CtField; | ||
| 16 | import javassist.bytecode.AccessFlag; | ||
| 17 | import javassist.bytecode.InnerClassesAttribute; | ||
| 18 | |||
| 19 | |||
| 20 | public 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 0000000..4d95f30 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import java.lang.reflect.InvocationTargetException; | ||
| 14 | import java.lang.reflect.Method; | ||
| 15 | import java.util.Arrays; | ||
| 16 | import java.util.HashMap; | ||
| 17 | import java.util.List; | ||
| 18 | import java.util.Map; | ||
| 19 | |||
| 20 | import javassist.CtClass; | ||
| 21 | import javassist.bytecode.AttributeInfo; | ||
| 22 | import javassist.bytecode.BadBytecode; | ||
| 23 | import javassist.bytecode.ByteArray; | ||
| 24 | import javassist.bytecode.ClassFile; | ||
| 25 | import javassist.bytecode.CodeAttribute; | ||
| 26 | import javassist.bytecode.ConstPool; | ||
| 27 | import javassist.bytecode.Descriptor; | ||
| 28 | import javassist.bytecode.FieldInfo; | ||
| 29 | import javassist.bytecode.InnerClassesAttribute; | ||
| 30 | import javassist.bytecode.LocalVariableTypeAttribute; | ||
| 31 | import javassist.bytecode.MethodInfo; | ||
| 32 | import javassist.bytecode.SignatureAttribute; | ||
| 33 | import javassist.bytecode.SignatureAttribute.ArrayType; | ||
| 34 | import javassist.bytecode.SignatureAttribute.BaseType; | ||
| 35 | import javassist.bytecode.SignatureAttribute.ClassSignature; | ||
| 36 | import javassist.bytecode.SignatureAttribute.ClassType; | ||
| 37 | import javassist.bytecode.SignatureAttribute.MethodSignature; | ||
| 38 | import javassist.bytecode.SignatureAttribute.NestedClassType; | ||
| 39 | import javassist.bytecode.SignatureAttribute.ObjectType; | ||
| 40 | import javassist.bytecode.SignatureAttribute.Type; | ||
| 41 | import javassist.bytecode.SignatureAttribute.TypeArgument; | ||
| 42 | import javassist.bytecode.SignatureAttribute.TypeParameter; | ||
| 43 | import javassist.bytecode.SignatureAttribute.TypeVariable; | ||
| 44 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 45 | import cuchaz.enigma.mapping.ClassNameReplacer; | ||
| 46 | import cuchaz.enigma.mapping.Translator; | ||
| 47 | |||
| 48 | public 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 0000000..7402459 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import javassist.CtBehavior; | ||
| 14 | import javassist.CtClass; | ||
| 15 | import javassist.CtField; | ||
| 16 | import javassist.CtMethod; | ||
| 17 | import javassist.bytecode.ConstPool; | ||
| 18 | import javassist.bytecode.Descriptor; | ||
| 19 | import javassist.bytecode.EnclosingMethodAttribute; | ||
| 20 | import javassist.bytecode.SourceFileAttribute; | ||
| 21 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 22 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 23 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 24 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 25 | import cuchaz.enigma.mapping.Signature; | ||
| 26 | import cuchaz.enigma.mapping.Translator; | ||
| 27 | import cuchaz.enigma.mapping.Type; | ||
| 28 | |||
| 29 | public 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 0000000..a00b86b --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import java.io.DataInputStream; | ||
| 14 | import java.io.DataOutputStream; | ||
| 15 | import java.lang.reflect.Constructor; | ||
| 16 | import java.lang.reflect.Field; | ||
| 17 | import java.lang.reflect.Method; | ||
| 18 | import java.util.HashMap; | ||
| 19 | |||
| 20 | import javassist.bytecode.ConstPool; | ||
| 21 | import javassist.bytecode.Descriptor; | ||
| 22 | import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor; | ||
| 23 | import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; | ||
| 24 | import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor; | ||
| 25 | |||
| 26 | public class ConstPoolEditor { | ||
| 27 | |||
| 28 | private static Method m_getItem; | ||
| 29 | private static Method m_addItem; | ||
| 30 | private static Method m_addItem0; | ||
| 31 | private static Field m_items; | ||
| 32 | private static Field m_cache; | ||
| 33 | private static Field m_numItems; | ||
| 34 | private static Field m_objects; | ||
| 35 | private static Field m_elements; | ||
| 36 | private static Method m_methodWritePool; | ||
| 37 | private static Constructor<ConstPool> m_constructorPool; | ||
| 38 | |||
| 39 | static { | ||
| 40 | try { | ||
| 41 | m_getItem = ConstPool.class.getDeclaredMethod("getItem", int.class); | ||
| 42 | m_getItem.setAccessible(true); | ||
| 43 | |||
| 44 | m_addItem = ConstPool.class.getDeclaredMethod("addItem", Class.forName("javassist.bytecode.ConstInfo")); | ||
| 45 | m_addItem.setAccessible(true); | ||
| 46 | |||
| 47 | m_addItem0 = ConstPool.class.getDeclaredMethod("addItem0", Class.forName("javassist.bytecode.ConstInfo")); | ||
| 48 | m_addItem0.setAccessible(true); | ||
| 49 | |||
| 50 | m_items = ConstPool.class.getDeclaredField("items"); | ||
| 51 | m_items.setAccessible(true); | ||
| 52 | |||
| 53 | m_cache = ConstPool.class.getDeclaredField("itemsCache"); | ||
| 54 | m_cache.setAccessible(true); | ||
| 55 | |||
| 56 | m_numItems = ConstPool.class.getDeclaredField("numOfItems"); | ||
| 57 | m_numItems.setAccessible(true); | ||
| 58 | |||
| 59 | m_objects = Class.forName("javassist.bytecode.LongVector").getDeclaredField("objects"); | ||
| 60 | m_objects.setAccessible(true); | ||
| 61 | |||
| 62 | m_elements = Class.forName("javassist.bytecode.LongVector").getDeclaredField("elements"); | ||
| 63 | m_elements.setAccessible(true); | ||
| 64 | |||
| 65 | m_methodWritePool = ConstPool.class.getDeclaredMethod("write", DataOutputStream.class); | ||
| 66 | m_methodWritePool.setAccessible(true); | ||
| 67 | |||
| 68 | m_constructorPool = ConstPool.class.getDeclaredConstructor(DataInputStream.class); | ||
| 69 | m_constructorPool.setAccessible(true); | ||
| 70 | } catch (Exception ex) { | ||
| 71 | throw new Error(ex); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | private ConstPool m_pool; | ||
| 76 | |||
| 77 | public ConstPoolEditor(ConstPool pool) { | ||
| 78 | m_pool = pool; | ||
| 79 | } | ||
| 80 | |||
| 81 | public void writePool(DataOutputStream out) { | ||
| 82 | try { | ||
| 83 | m_methodWritePool.invoke(m_pool, out); | ||
| 84 | } catch (Exception ex) { | ||
| 85 | throw new Error(ex); | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | public static ConstPool readPool(DataInputStream in) { | ||
| 90 | try { | ||
| 91 | return m_constructorPool.newInstance(in); | ||
| 92 | } catch (Exception ex) { | ||
| 93 | throw new Error(ex); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | public String getMemberrefClassname(int memberrefIndex) { | ||
| 98 | return Descriptor.toJvmName(m_pool.getClassInfo(m_pool.getMemberClass(memberrefIndex))); | ||
| 99 | } | ||
| 100 | |||
| 101 | public String getMemberrefName(int memberrefIndex) { | ||
| 102 | return m_pool.getUtf8Info(m_pool.getNameAndTypeName(m_pool.getMemberNameAndType(memberrefIndex))); | ||
| 103 | } | ||
| 104 | |||
| 105 | public String getMemberrefType(int memberrefIndex) { | ||
| 106 | return m_pool.getUtf8Info(m_pool.getNameAndTypeDescriptor(m_pool.getMemberNameAndType(memberrefIndex))); | ||
| 107 | } | ||
| 108 | |||
| 109 | public ConstInfoAccessor getItem(int index) { | ||
| 110 | try { | ||
| 111 | Object entry = m_getItem.invoke(m_pool, index); | ||
| 112 | if (entry == null) { | ||
| 113 | return null; | ||
| 114 | } | ||
| 115 | return new ConstInfoAccessor(entry); | ||
| 116 | } catch (Exception ex) { | ||
| 117 | throw new Error(ex); | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | public int addItem(Object item) { | ||
| 122 | try { | ||
| 123 | return (Integer)m_addItem.invoke(m_pool, item); | ||
| 124 | } catch (Exception ex) { | ||
| 125 | throw new Error(ex); | ||
| 126 | } | ||
| 127 | } | ||
| 128 | |||
| 129 | public int addItemForceNew(Object item) { | ||
| 130 | try { | ||
| 131 | return (Integer)m_addItem0.invoke(m_pool, item); | ||
| 132 | } catch (Exception ex) { | ||
| 133 | throw new Error(ex); | ||
| 134 | } | ||
| 135 | } | ||
| 136 | |||
| 137 | @SuppressWarnings("rawtypes") | ||
| 138 | public void removeLastItem() { | ||
| 139 | try { | ||
| 140 | // remove the item from the cache | ||
| 141 | HashMap cache = getCache(); | ||
| 142 | if (cache != null) { | ||
| 143 | Object item = getItem(m_pool.getSize() - 1); | ||
| 144 | cache.remove(item); | ||
| 145 | } | ||
| 146 | |||
| 147 | // remove the actual item | ||
| 148 | // based off of LongVector.addElement() | ||
| 149 | Object items = m_items.get(m_pool); | ||
| 150 | Object[][] objects = (Object[][])m_objects.get(items); | ||
| 151 | int numElements = (Integer)m_elements.get(items) - 1; | ||
| 152 | int nth = numElements >> 7; | ||
| 153 | int offset = numElements & (128 - 1); | ||
| 154 | objects[nth][offset] = null; | ||
| 155 | |||
| 156 | // decrement the number of items | ||
| 157 | m_elements.set(items, numElements); | ||
| 158 | m_numItems.set(m_pool, (Integer)m_numItems.get(m_pool) - 1); | ||
| 159 | } catch (Exception ex) { | ||
| 160 | throw new Error(ex); | ||
| 161 | } | ||
| 162 | } | ||
| 163 | |||
| 164 | @SuppressWarnings("rawtypes") | ||
| 165 | public HashMap getCache() { | ||
| 166 | try { | ||
| 167 | return (HashMap)m_cache.get(m_pool); | ||
| 168 | } catch (Exception ex) { | ||
| 169 | throw new Error(ex); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | @SuppressWarnings({ "rawtypes", "unchecked" }) | ||
| 174 | public void changeMemberrefNameAndType(int memberrefIndex, String newName, String newType) { | ||
| 175 | // NOTE: when changing values, we always need to copy-on-write | ||
| 176 | try { | ||
| 177 | // get the memberref item | ||
| 178 | Object item = getItem(memberrefIndex).getItem(); | ||
| 179 | |||
| 180 | // update the cache | ||
| 181 | HashMap cache = getCache(); | ||
| 182 | if (cache != null) { | ||
| 183 | cache.remove(item); | ||
| 184 | } | ||
| 185 | |||
| 186 | new MemberRefInfoAccessor(item).setNameAndTypeIndex(m_pool.addNameAndTypeInfo(newName, newType)); | ||
| 187 | |||
| 188 | // update the cache | ||
| 189 | if (cache != null) { | ||
| 190 | cache.put(item, item); | ||
| 191 | } | ||
| 192 | } catch (Exception ex) { | ||
| 193 | throw new Error(ex); | ||
| 194 | } | ||
| 195 | |||
| 196 | // make sure the change worked | ||
| 197 | assert (newName.equals(getMemberrefName(memberrefIndex))); | ||
| 198 | assert (newType.equals(getMemberrefType(memberrefIndex))); | ||
| 199 | } | ||
| 200 | |||
| 201 | @SuppressWarnings({ "rawtypes", "unchecked" }) | ||
| 202 | public void changeClassName(int classNameIndex, String newName) { | ||
| 203 | // NOTE: when changing values, we always need to copy-on-write | ||
| 204 | try { | ||
| 205 | // get the class item | ||
| 206 | Object item = getItem(classNameIndex).getItem(); | ||
| 207 | |||
| 208 | // update the cache | ||
| 209 | HashMap cache = getCache(); | ||
| 210 | if (cache != null) { | ||
| 211 | cache.remove(item); | ||
| 212 | } | ||
| 213 | |||
| 214 | // add the new name and repoint the name-and-type to it | ||
| 215 | new ClassInfoAccessor(item).setNameIndex(m_pool.addUtf8Info(newName)); | ||
| 216 | |||
| 217 | // update the cache | ||
| 218 | if (cache != null) { | ||
| 219 | cache.put(item, item); | ||
| 220 | } | ||
| 221 | } catch (Exception ex) { | ||
| 222 | throw new Error(ex); | ||
| 223 | } | ||
| 224 | } | ||
| 225 | |||
| 226 | public static ConstPool newConstPool() { | ||
| 227 | // const pool expects the name of a class to initialize itself | ||
| 228 | // but we want an empty pool | ||
| 229 | // so give it a bogus name, and then clear the entries afterwards | ||
| 230 | ConstPool pool = new ConstPool("a"); | ||
| 231 | |||
| 232 | ConstPoolEditor editor = new ConstPoolEditor(pool); | ||
| 233 | int size = pool.getSize(); | ||
| 234 | for (int i = 0; i < size - 1; i++) { | ||
| 235 | editor.removeLastItem(); | ||
| 236 | } | ||
| 237 | |||
| 238 | // make sure the pool is actually empty | ||
| 239 | // although, in this case "empty" means one thing in it | ||
| 240 | // the JVM spec says index 0 should be reserved | ||
| 241 | assert (pool.getSize() == 1); | ||
| 242 | assert (editor.getItem(0) == null); | ||
| 243 | assert (editor.getItem(1) == null); | ||
| 244 | assert (editor.getItem(2) == null); | ||
| 245 | assert (editor.getItem(3) == null); | ||
| 246 | |||
| 247 | // also, clear the cache | ||
| 248 | editor.getCache().clear(); | ||
| 249 | |||
| 250 | return pool; | ||
| 251 | } | ||
| 252 | |||
| 253 | public String dump() { | ||
| 254 | StringBuilder buf = new StringBuilder(); | ||
| 255 | for (int i = 1; i < m_pool.getSize(); i++) { | ||
| 256 | buf.append(String.format("%4d", i)); | ||
| 257 | buf.append(" "); | ||
| 258 | buf.append(getItem(i).toString()); | ||
| 259 | buf.append("\n"); | ||
| 260 | } | ||
| 261 | return buf.toString(); | ||
| 262 | } | ||
| 263 | } | ||
diff --git a/src/cuchaz/enigma/bytecode/InfoType.java b/src/cuchaz/enigma/bytecode/InfoType.java new file mode 100644 index 0000000..08f2b3e --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import java.util.Collection; | ||
| 14 | import java.util.List; | ||
| 15 | import java.util.Map; | ||
| 16 | |||
| 17 | import com.google.common.collect.Lists; | ||
| 18 | import com.google.common.collect.Maps; | ||
| 19 | |||
| 20 | import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor; | ||
| 21 | import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; | ||
| 22 | import cuchaz.enigma.bytecode.accessors.InvokeDynamicInfoAccessor; | ||
| 23 | import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor; | ||
| 24 | import cuchaz.enigma.bytecode.accessors.MethodHandleInfoAccessor; | ||
| 25 | import cuchaz.enigma.bytecode.accessors.MethodTypeInfoAccessor; | ||
| 26 | import cuchaz.enigma.bytecode.accessors.NameAndTypeInfoAccessor; | ||
| 27 | import cuchaz.enigma.bytecode.accessors.StringInfoAccessor; | ||
| 28 | |||
| 29 | public enum InfoType { | ||
| 30 | |||
| 31 | Utf8Info( 1, 0 ), | ||
| 32 | IntegerInfo( 3, 0 ), | ||
| 33 | FloatInfo( 4, 0 ), | ||
| 34 | LongInfo( 5, 0 ), | ||
| 35 | DoubleInfo( 6, 0 ), | ||
| 36 | ClassInfo( 7, 1 ) { | ||
| 37 | |||
| 38 | @Override | ||
| 39 | public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) { | ||
| 40 | ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); | ||
| 41 | gatherIndexTree(indices, editor, accessor.getNameIndex()); | ||
| 42 | } | ||
| 43 | |||
| 44 | @Override | ||
| 45 | public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) { | ||
| 46 | ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); | ||
| 47 | accessor.setNameIndex(remapIndex(map, accessor.getNameIndex())); | ||
| 48 | } | ||
| 49 | |||
| 50 | @Override | ||
| 51 | public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { | ||
| 52 | ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); | ||
| 53 | ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex()); | ||
| 54 | return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag(); | ||
| 55 | } | ||
| 56 | }, | ||
| 57 | StringInfo( 8, 1 ) { | ||
| 58 | |||
| 59 | @Override | ||
| 60 | public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) { | ||
| 61 | StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); | ||
| 62 | gatherIndexTree(indices, editor, accessor.getStringIndex()); | ||
| 63 | } | ||
| 64 | |||
| 65 | @Override | ||
| 66 | public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) { | ||
| 67 | StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); | ||
| 68 | accessor.setStringIndex(remapIndex(map, accessor.getStringIndex())); | ||
| 69 | } | ||
| 70 | |||
| 71 | @Override | ||
| 72 | public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { | ||
| 73 | StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); | ||
| 74 | ConstInfoAccessor stringEntry = pool.getItem(accessor.getStringIndex()); | ||
| 75 | return stringEntry != null && stringEntry.getTag() == Utf8Info.getTag(); | ||
| 76 | } | ||
| 77 | }, | ||
| 78 | FieldRefInfo( 9, 2 ) { | ||
| 79 | |||
| 80 | @Override | ||
| 81 | public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) { | ||
| 82 | MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); | ||
| 83 | gatherIndexTree(indices, editor, accessor.getClassIndex()); | ||
| 84 | gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex()); | ||
| 85 | } | ||
| 86 | |||
| 87 | @Override | ||
| 88 | public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) { | ||
| 89 | MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); | ||
| 90 | accessor.setClassIndex(remapIndex(map, accessor.getClassIndex())); | ||
| 91 | accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex())); | ||
| 92 | } | ||
| 93 | |||
| 94 | @Override | ||
| 95 | public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { | ||
| 96 | MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); | ||
| 97 | ConstInfoAccessor classEntry = pool.getItem(accessor.getClassIndex()); | ||
| 98 | ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex()); | ||
| 99 | return classEntry != null && classEntry.getTag() == ClassInfo.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag(); | ||
| 100 | } | ||
| 101 | }, | ||
| 102 | // same as FieldRefInfo | ||
| 103 | MethodRefInfo( 10, 2 ) { | ||
| 104 | |||
| 105 | @Override | ||
| 106 | public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) { | ||
| 107 | FieldRefInfo.gatherIndexTree(indices, editor, entry); | ||
| 108 | } | ||
| 109 | |||
| 110 | @Override | ||
| 111 | public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) { | ||
| 112 | FieldRefInfo.remapIndices(map, entry); | ||
| 113 | } | ||
| 114 | |||
| 115 | @Override | ||
| 116 | public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { | ||
| 117 | return FieldRefInfo.subIndicesAreValid(entry, pool); | ||
| 118 | } | ||
| 119 | }, | ||
| 120 | // same as FieldRefInfo | ||
| 121 | InterfaceMethodRefInfo( 11, 2 ) { | ||
| 122 | |||
| 123 | @Override | ||
| 124 | public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) { | ||
| 125 | FieldRefInfo.gatherIndexTree(indices, editor, entry); | ||
| 126 | } | ||
| 127 | |||
| 128 | @Override | ||
| 129 | public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) { | ||
| 130 | FieldRefInfo.remapIndices(map, entry); | ||
| 131 | } | ||
| 132 | |||
| 133 | @Override | ||
| 134 | public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { | ||
| 135 | return FieldRefInfo.subIndicesAreValid(entry, pool); | ||
| 136 | } | ||
| 137 | }, | ||
| 138 | NameAndTypeInfo( 12, 1 ) { | ||
| 139 | |||
| 140 | @Override | ||
| 141 | public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) { | ||
| 142 | NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); | ||
| 143 | gatherIndexTree(indices, editor, accessor.getNameIndex()); | ||
| 144 | gatherIndexTree(indices, editor, accessor.getTypeIndex()); | ||
| 145 | } | ||
| 146 | |||
| 147 | @Override | ||
| 148 | public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) { | ||
| 149 | NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); | ||
| 150 | accessor.setNameIndex(remapIndex(map, accessor.getNameIndex())); | ||
| 151 | accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); | ||
| 152 | } | ||
| 153 | |||
| 154 | @Override | ||
| 155 | public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { | ||
| 156 | NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); | ||
| 157 | ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex()); | ||
| 158 | ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); | ||
| 159 | return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag() && typeEntry != null && typeEntry.getTag() == Utf8Info.getTag(); | ||
| 160 | } | ||
| 161 | }, | ||
| 162 | MethodHandleInfo( 15, 3 ) { | ||
| 163 | |||
| 164 | @Override | ||
| 165 | public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) { | ||
| 166 | MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); | ||
| 167 | gatherIndexTree(indices, editor, accessor.getTypeIndex()); | ||
| 168 | gatherIndexTree(indices, editor, accessor.getMethodRefIndex()); | ||
| 169 | } | ||
| 170 | |||
| 171 | @Override | ||
| 172 | public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) { | ||
| 173 | MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); | ||
| 174 | accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); | ||
| 175 | accessor.setMethodRefIndex(remapIndex(map, accessor.getMethodRefIndex())); | ||
| 176 | } | ||
| 177 | |||
| 178 | @Override | ||
| 179 | public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { | ||
| 180 | MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); | ||
| 181 | ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); | ||
| 182 | ConstInfoAccessor methodRefEntry = pool.getItem(accessor.getMethodRefIndex()); | ||
| 183 | return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag() && methodRefEntry != null && methodRefEntry.getTag() == MethodRefInfo.getTag(); | ||
| 184 | } | ||
| 185 | }, | ||
| 186 | MethodTypeInfo( 16, 1 ) { | ||
| 187 | |||
| 188 | @Override | ||
| 189 | public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) { | ||
| 190 | MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); | ||
| 191 | gatherIndexTree(indices, editor, accessor.getTypeIndex()); | ||
| 192 | } | ||
| 193 | |||
| 194 | @Override | ||
| 195 | public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) { | ||
| 196 | MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); | ||
| 197 | accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); | ||
| 198 | } | ||
| 199 | |||
| 200 | @Override | ||
| 201 | public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { | ||
| 202 | MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); | ||
| 203 | ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); | ||
| 204 | return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag(); | ||
| 205 | } | ||
| 206 | }, | ||
| 207 | InvokeDynamicInfo( 18, 2 ) { | ||
| 208 | |||
| 209 | @Override | ||
| 210 | public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) { | ||
| 211 | InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); | ||
| 212 | gatherIndexTree(indices, editor, accessor.getBootstrapIndex()); | ||
| 213 | gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex()); | ||
| 214 | } | ||
| 215 | |||
| 216 | @Override | ||
| 217 | public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) { | ||
| 218 | InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); | ||
| 219 | accessor.setBootstrapIndex(remapIndex(map, accessor.getBootstrapIndex())); | ||
| 220 | accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex())); | ||
| 221 | } | ||
| 222 | |||
| 223 | @Override | ||
| 224 | public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { | ||
| 225 | InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); | ||
| 226 | ConstInfoAccessor bootstrapEntry = pool.getItem(accessor.getBootstrapIndex()); | ||
| 227 | ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex()); | ||
| 228 | return bootstrapEntry != null && bootstrapEntry.getTag() == Utf8Info.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag(); | ||
| 229 | } | ||
| 230 | }; | ||
| 231 | |||
| 232 | private static Map<Integer,InfoType> m_types; | ||
| 233 | |||
| 234 | static { | ||
| 235 | m_types = Maps.newTreeMap(); | ||
| 236 | for (InfoType type : values()) { | ||
| 237 | m_types.put(type.getTag(), type); | ||
| 238 | } | ||
| 239 | } | ||
| 240 | |||
| 241 | private int m_tag; | ||
| 242 | private int m_level; | ||
| 243 | |||
| 244 | private InfoType(int tag, int level) { | ||
| 245 | m_tag = tag; | ||
| 246 | m_level = level; | ||
| 247 | } | ||
| 248 | |||
| 249 | public int getTag() { | ||
| 250 | return m_tag; | ||
| 251 | } | ||
| 252 | |||
| 253 | public int getLevel() { | ||
| 254 | return m_level; | ||
| 255 | } | ||
| 256 | |||
| 257 | public void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, ConstInfoAccessor entry) { | ||
| 258 | // by default, do nothing | ||
| 259 | } | ||
| 260 | |||
| 261 | public void remapIndices(Map<Integer,Integer> map, ConstInfoAccessor entry) { | ||
| 262 | // by default, do nothing | ||
| 263 | } | ||
| 264 | |||
| 265 | public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { | ||
| 266 | // by default, everything is good | ||
| 267 | return true; | ||
| 268 | } | ||
| 269 | |||
| 270 | public boolean selfIndexIsValid(ConstInfoAccessor entry, ConstPoolEditor pool) { | ||
| 271 | ConstInfoAccessor entryCheck = pool.getItem(entry.getIndex()); | ||
| 272 | if (entryCheck == null) { | ||
| 273 | return false; | ||
| 274 | } | ||
| 275 | return entryCheck.getItem().equals(entry.getItem()); | ||
| 276 | } | ||
| 277 | |||
| 278 | public static InfoType getByTag(int tag) { | ||
| 279 | return m_types.get(tag); | ||
| 280 | } | ||
| 281 | |||
| 282 | public static List<InfoType> getByLevel(int level) { | ||
| 283 | List<InfoType> types = Lists.newArrayList(); | ||
| 284 | for (InfoType type : values()) { | ||
| 285 | if (type.getLevel() == level) { | ||
| 286 | types.add(type); | ||
| 287 | } | ||
| 288 | } | ||
| 289 | return types; | ||
| 290 | } | ||
| 291 | |||
| 292 | public static List<InfoType> getSortedByLevel() { | ||
| 293 | List<InfoType> types = Lists.newArrayList(); | ||
| 294 | types.addAll(getByLevel(0)); | ||
| 295 | types.addAll(getByLevel(1)); | ||
| 296 | types.addAll(getByLevel(2)); | ||
| 297 | types.addAll(getByLevel(3)); | ||
| 298 | return types; | ||
| 299 | } | ||
| 300 | |||
| 301 | public static void gatherIndexTree(Collection<Integer> indices, ConstPoolEditor editor, int index) { | ||
| 302 | // add own index | ||
| 303 | indices.add(index); | ||
| 304 | |||
| 305 | // recurse | ||
| 306 | ConstInfoAccessor entry = editor.getItem(index); | ||
| 307 | entry.getType().gatherIndexTree(indices, editor, entry); | ||
| 308 | } | ||
| 309 | |||
| 310 | private static int remapIndex(Map<Integer,Integer> map, int index) { | ||
| 311 | Integer newIndex = map.get(index); | ||
| 312 | if (newIndex == null) { | ||
| 313 | newIndex = index; | ||
| 314 | } | ||
| 315 | return newIndex; | ||
| 316 | } | ||
| 317 | } | ||
diff --git a/src/cuchaz/enigma/bytecode/InnerClassWriter.java b/src/cuchaz/enigma/bytecode/InnerClassWriter.java new file mode 100644 index 0000000..bdb1b5d --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import java.util.Collection; | ||
| 14 | import java.util.List; | ||
| 15 | |||
| 16 | import com.google.common.collect.Lists; | ||
| 17 | |||
| 18 | import javassist.CtClass; | ||
| 19 | import javassist.bytecode.AccessFlag; | ||
| 20 | import javassist.bytecode.ConstPool; | ||
| 21 | import javassist.bytecode.EnclosingMethodAttribute; | ||
| 22 | import javassist.bytecode.InnerClassesAttribute; | ||
| 23 | import cuchaz.enigma.analysis.JarIndex; | ||
| 24 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 25 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 26 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 27 | |||
| 28 | public 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 0000000..ae0455f --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import javassist.CtBehavior; | ||
| 14 | import javassist.CtClass; | ||
| 15 | import javassist.bytecode.ByteArray; | ||
| 16 | import javassist.bytecode.CodeAttribute; | ||
| 17 | import javassist.bytecode.ConstPool; | ||
| 18 | import javassist.bytecode.LocalVariableAttribute; | ||
| 19 | import javassist.bytecode.LocalVariableTypeAttribute; | ||
| 20 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 21 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 22 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 23 | import cuchaz.enigma.mapping.Translator; | ||
| 24 | |||
| 25 | |||
| 26 | public 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 0000000..0bdf47a --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import java.util.ArrayList; | ||
| 14 | import java.util.List; | ||
| 15 | |||
| 16 | import javassist.CtBehavior; | ||
| 17 | import javassist.CtClass; | ||
| 18 | import javassist.bytecode.CodeAttribute; | ||
| 19 | import javassist.bytecode.LocalVariableAttribute; | ||
| 20 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 21 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 22 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 23 | import cuchaz.enigma.mapping.Signature; | ||
| 24 | import cuchaz.enigma.mapping.Translator; | ||
| 25 | |||
| 26 | public 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 0000000..512e65a --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import java.io.ByteArrayOutputStream; | ||
| 14 | import java.io.DataOutputStream; | ||
| 15 | import java.io.IOException; | ||
| 16 | import java.util.ArrayList; | ||
| 17 | import java.util.List; | ||
| 18 | |||
| 19 | import javassist.bytecode.AttributeInfo; | ||
| 20 | import javassist.bytecode.ConstPool; | ||
| 21 | import javassist.bytecode.MethodInfo; | ||
| 22 | |||
| 23 | public 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 0000000..9072c29 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode.accessors; | ||
| 12 | |||
| 13 | import java.lang.reflect.Field; | ||
| 14 | |||
| 15 | public class ClassInfoAccessor { | ||
| 16 | |||
| 17 | private static Class<?> m_class; | ||
| 18 | private static Field m_nameIndex; | ||
| 19 | |||
| 20 | static { | ||
| 21 | try { | ||
| 22 | m_class = Class.forName("javassist.bytecode.ClassInfo"); | ||
| 23 | m_nameIndex = m_class.getDeclaredField("name"); | ||
| 24 | m_nameIndex.setAccessible(true); | ||
| 25 | } catch (Exception ex) { | ||
| 26 | throw new Error(ex); | ||
| 27 | } | ||
| 28 | } | ||
| 29 | |||
| 30 | public static boolean isType(ConstInfoAccessor accessor) { | ||
| 31 | return m_class.isAssignableFrom(accessor.getItem().getClass()); | ||
| 32 | } | ||
| 33 | |||
| 34 | private Object m_item; | ||
| 35 | |||
| 36 | public ClassInfoAccessor(Object item) { | ||
| 37 | m_item = item; | ||
| 38 | } | ||
| 39 | |||
| 40 | public int getNameIndex() { | ||
| 41 | try { | ||
| 42 | return (Integer)m_nameIndex.get(m_item); | ||
| 43 | } catch (Exception ex) { | ||
| 44 | throw new Error(ex); | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | public void setNameIndex(int val) { | ||
| 49 | try { | ||
| 50 | m_nameIndex.set(m_item, val); | ||
| 51 | } catch (Exception ex) { | ||
| 52 | throw new Error(ex); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | } | ||
diff --git a/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java new file mode 100644 index 0000000..ede0473 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode.accessors; | ||
| 12 | |||
| 13 | import java.io.ByteArrayInputStream; | ||
| 14 | import java.io.ByteArrayOutputStream; | ||
| 15 | import java.io.DataInputStream; | ||
| 16 | import java.io.DataOutputStream; | ||
| 17 | import java.io.IOException; | ||
| 18 | import java.io.PrintWriter; | ||
| 19 | import java.lang.reflect.Constructor; | ||
| 20 | import java.lang.reflect.Field; | ||
| 21 | import java.lang.reflect.Method; | ||
| 22 | |||
| 23 | import cuchaz.enigma.bytecode.InfoType; | ||
| 24 | |||
| 25 | public class ConstInfoAccessor { | ||
| 26 | |||
| 27 | private static Class<?> m_class; | ||
| 28 | private static Field m_index; | ||
| 29 | private static Method m_getTag; | ||
| 30 | |||
| 31 | static { | ||
| 32 | try { | ||
| 33 | m_class = Class.forName("javassist.bytecode.ConstInfo"); | ||
| 34 | m_index = m_class.getDeclaredField("index"); | ||
| 35 | m_index.setAccessible(true); | ||
| 36 | m_getTag = m_class.getMethod("getTag"); | ||
| 37 | m_getTag.setAccessible(true); | ||
| 38 | } catch (Exception ex) { | ||
| 39 | throw new Error(ex); | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | private Object m_item; | ||
| 44 | |||
| 45 | public ConstInfoAccessor(Object item) { | ||
| 46 | if (item == null) { | ||
| 47 | throw new IllegalArgumentException("item cannot be null!"); | ||
| 48 | } | ||
| 49 | m_item = item; | ||
| 50 | } | ||
| 51 | |||
| 52 | public ConstInfoAccessor(DataInputStream in) throws IOException { | ||
| 53 | try { | ||
| 54 | // read the entry | ||
| 55 | String className = in.readUTF(); | ||
| 56 | int oldIndex = in.readInt(); | ||
| 57 | |||
| 58 | // NOTE: ConstInfo instances write a type id (a "tag"), but they don't read it back | ||
| 59 | // so we have to read it here | ||
| 60 | in.readByte(); | ||
| 61 | |||
| 62 | Constructor<?> constructor = Class.forName(className).getConstructor(DataInputStream.class, int.class); | ||
| 63 | constructor.setAccessible(true); | ||
| 64 | m_item = constructor.newInstance(in, oldIndex); | ||
| 65 | } catch (IOException ex) { | ||
| 66 | throw ex; | ||
| 67 | } catch (Exception ex) { | ||
| 68 | throw new Error(ex); | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | public Object getItem() { | ||
| 73 | return m_item; | ||
| 74 | } | ||
| 75 | |||
| 76 | public int getIndex() { | ||
| 77 | try { | ||
| 78 | return (Integer)m_index.get(m_item); | ||
| 79 | } catch (Exception ex) { | ||
| 80 | throw new Error(ex); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | public void setIndex(int val) { | ||
| 85 | try { | ||
| 86 | m_index.set(m_item, val); | ||
| 87 | } catch (Exception ex) { | ||
| 88 | throw new Error(ex); | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | public int getTag() { | ||
| 93 | try { | ||
| 94 | return (Integer)m_getTag.invoke(m_item); | ||
| 95 | } catch (Exception ex) { | ||
| 96 | throw new Error(ex); | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | public ConstInfoAccessor copy() { | ||
| 101 | return new ConstInfoAccessor(copyItem()); | ||
| 102 | } | ||
| 103 | |||
| 104 | public Object copyItem() { | ||
| 105 | // I don't know of a simpler way to copy one of these silly things... | ||
| 106 | try { | ||
| 107 | // serialize the item | ||
| 108 | ByteArrayOutputStream buf = new ByteArrayOutputStream(); | ||
| 109 | DataOutputStream out = new DataOutputStream(buf); | ||
| 110 | write(out); | ||
| 111 | |||
| 112 | // deserialize the item | ||
| 113 | DataInputStream in = new DataInputStream(new ByteArrayInputStream(buf.toByteArray())); | ||
| 114 | Object item = new ConstInfoAccessor(in).getItem(); | ||
| 115 | in.close(); | ||
| 116 | |||
| 117 | return item; | ||
| 118 | } catch (Exception ex) { | ||
| 119 | throw new Error(ex); | ||
| 120 | } | ||
| 121 | } | ||
| 122 | |||
| 123 | public void write(DataOutputStream out) throws IOException { | ||
| 124 | try { | ||
| 125 | out.writeUTF(m_item.getClass().getName()); | ||
| 126 | out.writeInt(getIndex()); | ||
| 127 | |||
| 128 | Method method = m_item.getClass().getMethod("write", DataOutputStream.class); | ||
| 129 | method.setAccessible(true); | ||
| 130 | method.invoke(m_item, out); | ||
| 131 | } catch (IOException ex) { | ||
| 132 | throw ex; | ||
| 133 | } catch (Exception ex) { | ||
| 134 | throw new Error(ex); | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | @Override | ||
| 139 | public String toString() { | ||
| 140 | try { | ||
| 141 | ByteArrayOutputStream buf = new ByteArrayOutputStream(); | ||
| 142 | PrintWriter out = new PrintWriter(buf); | ||
| 143 | Method print = m_item.getClass().getMethod("print", PrintWriter.class); | ||
| 144 | print.setAccessible(true); | ||
| 145 | print.invoke(m_item, out); | ||
| 146 | out.close(); | ||
| 147 | return buf.toString().replace("\n", ""); | ||
| 148 | } catch (Exception ex) { | ||
| 149 | throw new Error(ex); | ||
| 150 | } | ||
| 151 | } | ||
| 152 | |||
| 153 | public InfoType getType() { | ||
| 154 | return InfoType.getByTag(getTag()); | ||
| 155 | } | ||
| 156 | } | ||
diff --git a/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java new file mode 100644 index 0000000..82af0b9 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode.accessors; | ||
| 12 | |||
| 13 | import java.lang.reflect.Field; | ||
| 14 | |||
| 15 | public class InvokeDynamicInfoAccessor { | ||
| 16 | |||
| 17 | private static Class<?> m_class; | ||
| 18 | private static Field m_bootstrapIndex; | ||
| 19 | private static Field m_nameAndTypeIndex; | ||
| 20 | |||
| 21 | static { | ||
| 22 | try { | ||
| 23 | m_class = Class.forName("javassist.bytecode.InvokeDynamicInfo"); | ||
| 24 | m_bootstrapIndex = m_class.getDeclaredField("bootstrap"); | ||
| 25 | m_bootstrapIndex.setAccessible(true); | ||
| 26 | m_nameAndTypeIndex = m_class.getDeclaredField("nameAndType"); | ||
| 27 | m_nameAndTypeIndex.setAccessible(true); | ||
| 28 | } catch (Exception ex) { | ||
| 29 | throw new Error(ex); | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | public static boolean isType(ConstInfoAccessor accessor) { | ||
| 34 | return m_class.isAssignableFrom(accessor.getItem().getClass()); | ||
| 35 | } | ||
| 36 | |||
| 37 | private Object m_item; | ||
| 38 | |||
| 39 | public InvokeDynamicInfoAccessor(Object item) { | ||
| 40 | m_item = item; | ||
| 41 | } | ||
| 42 | |||
| 43 | public int getBootstrapIndex() { | ||
| 44 | try { | ||
| 45 | return (Integer)m_bootstrapIndex.get(m_item); | ||
| 46 | } catch (Exception ex) { | ||
| 47 | throw new Error(ex); | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | public void setBootstrapIndex(int val) { | ||
| 52 | try { | ||
| 53 | m_bootstrapIndex.set(m_item, val); | ||
| 54 | } catch (Exception ex) { | ||
| 55 | throw new Error(ex); | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | public int getNameAndTypeIndex() { | ||
| 60 | try { | ||
| 61 | return (Integer)m_nameAndTypeIndex.get(m_item); | ||
| 62 | } catch (Exception ex) { | ||
| 63 | throw new Error(ex); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | public void setNameAndTypeIndex(int val) { | ||
| 68 | try { | ||
| 69 | m_nameAndTypeIndex.set(m_item, val); | ||
| 70 | } catch (Exception ex) { | ||
| 71 | throw new Error(ex); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | } | ||
diff --git a/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java new file mode 100644 index 0000000..71ee5b7 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode.accessors; | ||
| 12 | |||
| 13 | import java.lang.reflect.Field; | ||
| 14 | |||
| 15 | public class MemberRefInfoAccessor { | ||
| 16 | |||
| 17 | private static Class<?> m_class; | ||
| 18 | private static Field m_classIndex; | ||
| 19 | private static Field m_nameAndTypeIndex; | ||
| 20 | |||
| 21 | static { | ||
| 22 | try { | ||
| 23 | m_class = Class.forName("javassist.bytecode.MemberrefInfo"); | ||
| 24 | m_classIndex = m_class.getDeclaredField("classIndex"); | ||
| 25 | m_classIndex.setAccessible(true); | ||
| 26 | m_nameAndTypeIndex = m_class.getDeclaredField("nameAndTypeIndex"); | ||
| 27 | m_nameAndTypeIndex.setAccessible(true); | ||
| 28 | } catch (Exception ex) { | ||
| 29 | throw new Error(ex); | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | public static boolean isType(ConstInfoAccessor accessor) { | ||
| 34 | return m_class.isAssignableFrom(accessor.getItem().getClass()); | ||
| 35 | } | ||
| 36 | |||
| 37 | private Object m_item; | ||
| 38 | |||
| 39 | public MemberRefInfoAccessor(Object item) { | ||
| 40 | m_item = item; | ||
| 41 | } | ||
| 42 | |||
| 43 | public int getClassIndex() { | ||
| 44 | try { | ||
| 45 | return (Integer)m_classIndex.get(m_item); | ||
| 46 | } catch (Exception ex) { | ||
| 47 | throw new Error(ex); | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | public void setClassIndex(int val) { | ||
| 52 | try { | ||
| 53 | m_classIndex.set(m_item, val); | ||
| 54 | } catch (Exception ex) { | ||
| 55 | throw new Error(ex); | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | public int getNameAndTypeIndex() { | ||
| 60 | try { | ||
| 61 | return (Integer)m_nameAndTypeIndex.get(m_item); | ||
| 62 | } catch (Exception ex) { | ||
| 63 | throw new Error(ex); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | public void setNameAndTypeIndex(int val) { | ||
| 68 | try { | ||
| 69 | m_nameAndTypeIndex.set(m_item, val); | ||
| 70 | } catch (Exception ex) { | ||
| 71 | throw new Error(ex); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | } | ||
diff --git a/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java new file mode 100644 index 0000000..172b0c5 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode.accessors; | ||
| 12 | |||
| 13 | import java.lang.reflect.Field; | ||
| 14 | |||
| 15 | public class MethodHandleInfoAccessor { | ||
| 16 | |||
| 17 | private static Class<?> m_class; | ||
| 18 | private static Field m_kindIndex; | ||
| 19 | private static Field m_indexIndex; | ||
| 20 | |||
| 21 | static { | ||
| 22 | try { | ||
| 23 | m_class = Class.forName("javassist.bytecode.MethodHandleInfo"); | ||
| 24 | m_kindIndex = m_class.getDeclaredField("refKind"); | ||
| 25 | m_kindIndex.setAccessible(true); | ||
| 26 | m_indexIndex = m_class.getDeclaredField("refIndex"); | ||
| 27 | m_indexIndex.setAccessible(true); | ||
| 28 | } catch (Exception ex) { | ||
| 29 | throw new Error(ex); | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | public static boolean isType(ConstInfoAccessor accessor) { | ||
| 34 | return m_class.isAssignableFrom(accessor.getItem().getClass()); | ||
| 35 | } | ||
| 36 | |||
| 37 | private Object m_item; | ||
| 38 | |||
| 39 | public MethodHandleInfoAccessor(Object item) { | ||
| 40 | m_item = item; | ||
| 41 | } | ||
| 42 | |||
| 43 | public int getTypeIndex() { | ||
| 44 | try { | ||
| 45 | return (Integer)m_kindIndex.get(m_item); | ||
| 46 | } catch (Exception ex) { | ||
| 47 | throw new Error(ex); | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | public void setTypeIndex(int val) { | ||
| 52 | try { | ||
| 53 | m_kindIndex.set(m_item, val); | ||
| 54 | } catch (Exception ex) { | ||
| 55 | throw new Error(ex); | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | public int getMethodRefIndex() { | ||
| 60 | try { | ||
| 61 | return (Integer)m_indexIndex.get(m_item); | ||
| 62 | } catch (Exception ex) { | ||
| 63 | throw new Error(ex); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | public void setMethodRefIndex(int val) { | ||
| 68 | try { | ||
| 69 | m_indexIndex.set(m_item, val); | ||
| 70 | } catch (Exception ex) { | ||
| 71 | throw new Error(ex); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | } | ||
diff --git a/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java new file mode 100644 index 0000000..0099a84 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode.accessors; | ||
| 12 | |||
| 13 | import java.lang.reflect.Field; | ||
| 14 | |||
| 15 | public class MethodTypeInfoAccessor { | ||
| 16 | |||
| 17 | private static Class<?> m_class; | ||
| 18 | private static Field m_descriptorIndex; | ||
| 19 | |||
| 20 | static { | ||
| 21 | try { | ||
| 22 | m_class = Class.forName("javassist.bytecode.MethodTypeInfo"); | ||
| 23 | m_descriptorIndex = m_class.getDeclaredField("descriptor"); | ||
| 24 | m_descriptorIndex.setAccessible(true); | ||
| 25 | } catch (Exception ex) { | ||
| 26 | throw new Error(ex); | ||
| 27 | } | ||
| 28 | } | ||
| 29 | |||
| 30 | public static boolean isType(ConstInfoAccessor accessor) { | ||
| 31 | return m_class.isAssignableFrom(accessor.getItem().getClass()); | ||
| 32 | } | ||
| 33 | |||
| 34 | private Object m_item; | ||
| 35 | |||
| 36 | public MethodTypeInfoAccessor(Object item) { | ||
| 37 | m_item = item; | ||
| 38 | } | ||
| 39 | |||
| 40 | public int getTypeIndex() { | ||
| 41 | try { | ||
| 42 | return (Integer)m_descriptorIndex.get(m_item); | ||
| 43 | } catch (Exception ex) { | ||
| 44 | throw new Error(ex); | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | public void setTypeIndex(int val) { | ||
| 49 | try { | ||
| 50 | m_descriptorIndex.set(m_item, val); | ||
| 51 | } catch (Exception ex) { | ||
| 52 | throw new Error(ex); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | } | ||
diff --git a/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java new file mode 100644 index 0000000..3ecc129 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode.accessors; | ||
| 12 | |||
| 13 | import java.lang.reflect.Field; | ||
| 14 | |||
| 15 | public class NameAndTypeInfoAccessor { | ||
| 16 | |||
| 17 | private static Class<?> m_class; | ||
| 18 | private static Field m_nameIndex; | ||
| 19 | private static Field m_typeIndex; | ||
| 20 | |||
| 21 | static { | ||
| 22 | try { | ||
| 23 | m_class = Class.forName("javassist.bytecode.NameAndTypeInfo"); | ||
| 24 | m_nameIndex = m_class.getDeclaredField("memberName"); | ||
| 25 | m_nameIndex.setAccessible(true); | ||
| 26 | m_typeIndex = m_class.getDeclaredField("typeDescriptor"); | ||
| 27 | m_typeIndex.setAccessible(true); | ||
| 28 | } catch (Exception ex) { | ||
| 29 | throw new Error(ex); | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | public static boolean isType(ConstInfoAccessor accessor) { | ||
| 34 | return m_class.isAssignableFrom(accessor.getItem().getClass()); | ||
| 35 | } | ||
| 36 | |||
| 37 | private Object m_item; | ||
| 38 | |||
| 39 | public NameAndTypeInfoAccessor(Object item) { | ||
| 40 | m_item = item; | ||
| 41 | } | ||
| 42 | |||
| 43 | public int getNameIndex() { | ||
| 44 | try { | ||
| 45 | return (Integer)m_nameIndex.get(m_item); | ||
| 46 | } catch (Exception ex) { | ||
| 47 | throw new Error(ex); | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | public void setNameIndex(int val) { | ||
| 52 | try { | ||
| 53 | m_nameIndex.set(m_item, val); | ||
| 54 | } catch (Exception ex) { | ||
| 55 | throw new Error(ex); | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | public int getTypeIndex() { | ||
| 60 | try { | ||
| 61 | return (Integer)m_typeIndex.get(m_item); | ||
| 62 | } catch (Exception ex) { | ||
| 63 | throw new Error(ex); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | public void setTypeIndex(int val) { | ||
| 68 | try { | ||
| 69 | m_typeIndex.set(m_item, val); | ||
| 70 | } catch (Exception ex) { | ||
| 71 | throw new Error(ex); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | } | ||
diff --git a/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java new file mode 100644 index 0000000..f150612 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode.accessors; | ||
| 12 | |||
| 13 | import java.lang.reflect.Field; | ||
| 14 | |||
| 15 | public class StringInfoAccessor { | ||
| 16 | |||
| 17 | private static Class<?> m_class; | ||
| 18 | private static Field m_stringIndex; | ||
| 19 | |||
| 20 | static { | ||
| 21 | try { | ||
| 22 | m_class = Class.forName("javassist.bytecode.StringInfo"); | ||
| 23 | m_stringIndex = m_class.getDeclaredField("string"); | ||
| 24 | m_stringIndex.setAccessible(true); | ||
| 25 | } catch (Exception ex) { | ||
| 26 | throw new Error(ex); | ||
| 27 | } | ||
| 28 | } | ||
| 29 | |||
| 30 | public static boolean isType(ConstInfoAccessor accessor) { | ||
| 31 | return m_class.isAssignableFrom(accessor.getItem().getClass()); | ||
| 32 | } | ||
| 33 | |||
| 34 | private Object m_item; | ||
| 35 | |||
| 36 | public StringInfoAccessor(Object item) { | ||
| 37 | m_item = item; | ||
| 38 | } | ||
| 39 | |||
| 40 | public int getStringIndex() { | ||
| 41 | try { | ||
| 42 | return (Integer)m_stringIndex.get(m_item); | ||
| 43 | } catch (Exception ex) { | ||
| 44 | throw new Error(ex); | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | public void setStringIndex(int val) { | ||
| 49 | try { | ||
| 50 | m_stringIndex.set(m_item, val); | ||
| 51 | } catch (Exception ex) { | ||
| 52 | throw new Error(ex); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | } | ||
diff --git a/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java new file mode 100644 index 0000000..38e8ff9 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode.accessors; | ||
| 12 | |||
| 13 | public 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 0000000..0407730 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.Collection; | ||
| 14 | |||
| 15 | import com.google.common.collect.HashMultimap; | ||
| 16 | import com.google.common.collect.Multimap; | ||
| 17 | |||
| 18 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 19 | |||
| 20 | |||
| 21 | public 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 0000000..ee5e903 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.Map; | ||
| 14 | import java.util.jar.JarFile; | ||
| 15 | |||
| 16 | import com.google.common.collect.Maps; | ||
| 17 | |||
| 18 | import javassist.CtClass; | ||
| 19 | import cuchaz.enigma.TranslatingTypeLoader; | ||
| 20 | import cuchaz.enigma.analysis.JarIndex; | ||
| 21 | import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; | ||
| 22 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 23 | |||
| 24 | |||
| 25 | public 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 0000000..2e164ae --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.io.UnsupportedEncodingException; | ||
| 14 | import java.security.MessageDigest; | ||
| 15 | import java.security.NoSuchAlgorithmException; | ||
| 16 | import java.util.Enumeration; | ||
| 17 | import java.util.List; | ||
| 18 | import java.util.Map; | ||
| 19 | import java.util.Set; | ||
| 20 | |||
| 21 | import javassist.CannotCompileException; | ||
| 22 | import javassist.CtBehavior; | ||
| 23 | import javassist.CtClass; | ||
| 24 | import javassist.CtConstructor; | ||
| 25 | import javassist.CtField; | ||
| 26 | import javassist.CtMethod; | ||
| 27 | import javassist.bytecode.BadBytecode; | ||
| 28 | import javassist.bytecode.CodeIterator; | ||
| 29 | import javassist.bytecode.ConstPool; | ||
| 30 | import javassist.bytecode.Descriptor; | ||
| 31 | import javassist.bytecode.Opcode; | ||
| 32 | import javassist.expr.ConstructorCall; | ||
| 33 | import javassist.expr.ExprEditor; | ||
| 34 | import javassist.expr.FieldAccess; | ||
| 35 | import javassist.expr.MethodCall; | ||
| 36 | import javassist.expr.NewExpr; | ||
| 37 | |||
| 38 | import com.google.common.collect.HashMultiset; | ||
| 39 | import com.google.common.collect.Lists; | ||
| 40 | import com.google.common.collect.Maps; | ||
| 41 | import com.google.common.collect.Multiset; | ||
| 42 | import com.google.common.collect.Sets; | ||
| 43 | |||
| 44 | import cuchaz.enigma.Constants; | ||
| 45 | import cuchaz.enigma.Util; | ||
| 46 | import cuchaz.enigma.analysis.ClassImplementationsTreeNode; | ||
| 47 | import cuchaz.enigma.analysis.EntryReference; | ||
| 48 | import cuchaz.enigma.analysis.JarIndex; | ||
| 49 | import cuchaz.enigma.bytecode.ConstPoolEditor; | ||
| 50 | import cuchaz.enigma.bytecode.InfoType; | ||
| 51 | import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; | ||
| 52 | import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; | ||
| 53 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 54 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 55 | import cuchaz.enigma.mapping.ClassNameReplacer; | ||
| 56 | import cuchaz.enigma.mapping.Entry; | ||
| 57 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 58 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 59 | import cuchaz.enigma.mapping.Signature; | ||
| 60 | import cuchaz.enigma.mapping.Type; | ||
| 61 | |||
| 62 | public 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 0000000..8c50a62 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.Collection; | ||
| 14 | import java.util.Set; | ||
| 15 | |||
| 16 | import com.google.common.collect.Sets; | ||
| 17 | |||
| 18 | import cuchaz.enigma.Util; | ||
| 19 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 20 | |||
| 21 | |||
| 22 | public 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 0000000..f70c180 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.ArrayList; | ||
| 14 | import java.util.Collection; | ||
| 15 | import java.util.Iterator; | ||
| 16 | import java.util.Map; | ||
| 17 | import java.util.Set; | ||
| 18 | |||
| 19 | import com.google.common.collect.BiMap; | ||
| 20 | import com.google.common.collect.HashBiMap; | ||
| 21 | import com.google.common.collect.Maps; | ||
| 22 | import com.google.common.collect.Sets; | ||
| 23 | |||
| 24 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 25 | |||
| 26 | |||
| 27 | public 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 0000000..633d1ac --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.ArrayList; | ||
| 14 | import java.util.Collection; | ||
| 15 | import java.util.List; | ||
| 16 | import java.util.Map.Entry; | ||
| 17 | import java.util.Set; | ||
| 18 | |||
| 19 | import com.google.common.collect.BiMap; | ||
| 20 | import com.google.common.collect.HashBiMap; | ||
| 21 | import com.google.common.collect.Lists; | ||
| 22 | import com.google.common.collect.Sets; | ||
| 23 | |||
| 24 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 25 | |||
| 26 | public 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 0000000..e8fa730 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.Map; | ||
| 14 | |||
| 15 | import com.google.common.collect.BiMap; | ||
| 16 | import com.google.common.collect.Maps; | ||
| 17 | |||
| 18 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 19 | |||
| 20 | public 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 0000000..8439a84 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.Collection; | ||
| 14 | import java.util.Set; | ||
| 15 | |||
| 16 | import com.google.common.collect.BiMap; | ||
| 17 | import com.google.common.collect.HashBiMap; | ||
| 18 | import com.google.common.collect.HashMultimap; | ||
| 19 | import com.google.common.collect.Multimap; | ||
| 20 | import com.google.common.collect.Sets; | ||
| 21 | |||
| 22 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 23 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 24 | |||
| 25 | |||
| 26 | public 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 0000000..b457d6c --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.Arrays; | ||
| 14 | import java.util.Collection; | ||
| 15 | import java.util.Collections; | ||
| 16 | import java.util.Iterator; | ||
| 17 | import java.util.LinkedHashMap; | ||
| 18 | import java.util.List; | ||
| 19 | import java.util.Map; | ||
| 20 | import java.util.Set; | ||
| 21 | import java.util.jar.JarFile; | ||
| 22 | |||
| 23 | import com.google.common.collect.BiMap; | ||
| 24 | import com.google.common.collect.HashMultimap; | ||
| 25 | import com.google.common.collect.Lists; | ||
| 26 | import com.google.common.collect.Maps; | ||
| 27 | import com.google.common.collect.Multimap; | ||
| 28 | import com.google.common.collect.Sets; | ||
| 29 | |||
| 30 | import cuchaz.enigma.Deobfuscator; | ||
| 31 | import cuchaz.enigma.analysis.JarIndex; | ||
| 32 | import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; | ||
| 33 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 34 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 35 | import cuchaz.enigma.mapping.ClassMapping; | ||
| 36 | import cuchaz.enigma.mapping.ClassNameReplacer; | ||
| 37 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 38 | import cuchaz.enigma.mapping.Entry; | ||
| 39 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 40 | import cuchaz.enigma.mapping.FieldMapping; | ||
| 41 | import cuchaz.enigma.mapping.Mappings; | ||
| 42 | import cuchaz.enigma.mapping.MappingsChecker; | ||
| 43 | import cuchaz.enigma.mapping.MemberMapping; | ||
| 44 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 45 | import cuchaz.enigma.mapping.MethodMapping; | ||
| 46 | import cuchaz.enigma.mapping.Signature; | ||
| 47 | import cuchaz.enigma.mapping.Type; | ||
| 48 | |||
| 49 | public 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 0000000..7514e2a --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.io.BufferedReader; | ||
| 14 | import java.io.File; | ||
| 15 | import java.io.FileReader; | ||
| 16 | import java.io.IOException; | ||
| 17 | import java.util.Collection; | ||
| 18 | import java.util.List; | ||
| 19 | |||
| 20 | import com.google.common.collect.Lists; | ||
| 21 | |||
| 22 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 23 | import cuchaz.enigma.mapping.Entry; | ||
| 24 | import cuchaz.enigma.mapping.EntryFactory; | ||
| 25 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 26 | import cuchaz.enigma.mapping.Type; | ||
| 27 | |||
| 28 | |||
| 29 | public 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 0000000..42c6b61 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.io.File; | ||
| 14 | import java.io.FileWriter; | ||
| 15 | import java.io.IOException; | ||
| 16 | import java.util.Map; | ||
| 17 | |||
| 18 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 19 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 20 | import cuchaz.enigma.mapping.Entry; | ||
| 21 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 22 | |||
| 23 | |||
| 24 | public 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 0000000..29def15 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.Collection; | ||
| 14 | import java.util.Set; | ||
| 15 | |||
| 16 | import com.google.common.collect.BiMap; | ||
| 17 | import com.google.common.collect.HashBiMap; | ||
| 18 | import com.google.common.collect.HashMultimap; | ||
| 19 | import com.google.common.collect.Multimap; | ||
| 20 | import com.google.common.collect.Sets; | ||
| 21 | |||
| 22 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 23 | import cuchaz.enigma.mapping.Entry; | ||
| 24 | |||
| 25 | |||
| 26 | public 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 0000000..3eba1e5 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Color; | ||
| 14 | import java.awt.Container; | ||
| 15 | import java.awt.Cursor; | ||
| 16 | import java.awt.FlowLayout; | ||
| 17 | import java.awt.event.ActionEvent; | ||
| 18 | import java.awt.event.ActionListener; | ||
| 19 | import java.io.IOException; | ||
| 20 | |||
| 21 | import javax.swing.JButton; | ||
| 22 | import javax.swing.JFrame; | ||
| 23 | import javax.swing.JLabel; | ||
| 24 | import javax.swing.JPanel; | ||
| 25 | import javax.swing.WindowConstants; | ||
| 26 | |||
| 27 | import cuchaz.enigma.Constants; | ||
| 28 | import cuchaz.enigma.Util; | ||
| 29 | |||
| 30 | public class AboutDialog { | ||
| 31 | |||
| 32 | public static void show(JFrame parent) { | ||
| 33 | // init frame | ||
| 34 | final JFrame frame = new JFrame(Constants.Name + " - About"); | ||
| 35 | final Container pane = frame.getContentPane(); | ||
| 36 | pane.setLayout(new FlowLayout()); | ||
| 37 | |||
| 38 | // load the content | ||
| 39 | try { | ||
| 40 | String html = Util.readResourceToString("/about.html"); | ||
| 41 | html = String.format(html, Constants.Name, Constants.Version); | ||
| 42 | JLabel label = new JLabel(html); | ||
| 43 | label.setHorizontalAlignment(JLabel.CENTER); | ||
| 44 | pane.add(label); | ||
| 45 | } catch (IOException ex) { | ||
| 46 | throw new Error(ex); | ||
| 47 | } | ||
| 48 | |||
| 49 | // show the link | ||
| 50 | String html = "<html><a href=\"%s\">%s</a></html>"; | ||
| 51 | html = String.format(html, Constants.Url, Constants.Url); | ||
| 52 | JButton link = new JButton(html); | ||
| 53 | link.addActionListener(new ActionListener() { | ||
| 54 | @Override | ||
| 55 | public void actionPerformed(ActionEvent event) { | ||
| 56 | Util.openUrl(Constants.Url); | ||
| 57 | } | ||
| 58 | }); | ||
| 59 | link.setBorderPainted(false); | ||
| 60 | link.setOpaque(false); | ||
| 61 | link.setBackground(Color.WHITE); | ||
| 62 | link.setCursor(new Cursor(Cursor.HAND_CURSOR)); | ||
| 63 | link.setFocusable(false); | ||
| 64 | JPanel linkPanel = new JPanel(); | ||
| 65 | linkPanel.add(link); | ||
| 66 | pane.add(linkPanel); | ||
| 67 | |||
| 68 | // show ok button | ||
| 69 | JButton okButton = new JButton("Ok"); | ||
| 70 | pane.add(okButton); | ||
| 71 | okButton.addActionListener(new ActionListener() { | ||
| 72 | @Override | ||
| 73 | public void actionPerformed(ActionEvent arg0) { | ||
| 74 | frame.dispose(); | ||
| 75 | } | ||
| 76 | }); | ||
| 77 | |||
| 78 | // show the frame | ||
| 79 | pane.doLayout(); | ||
| 80 | frame.setSize(400, 220); | ||
| 81 | frame.setResizable(false); | ||
| 82 | frame.setLocationRelativeTo(parent); | ||
| 83 | frame.setVisible(true); | ||
| 84 | frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); | ||
| 85 | } | ||
| 86 | } | ||
diff --git a/src/cuchaz/enigma/gui/BoxHighlightPainter.java b/src/cuchaz/enigma/gui/BoxHighlightPainter.java new file mode 100644 index 0000000..e5e0557 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Color; | ||
| 14 | import java.awt.Graphics; | ||
| 15 | import java.awt.Rectangle; | ||
| 16 | import java.awt.Shape; | ||
| 17 | |||
| 18 | import javax.swing.text.BadLocationException; | ||
| 19 | import javax.swing.text.Highlighter; | ||
| 20 | import javax.swing.text.JTextComponent; | ||
| 21 | |||
| 22 | public abstract class BoxHighlightPainter implements Highlighter.HighlightPainter { | ||
| 23 | |||
| 24 | private Color m_fillColor; | ||
| 25 | private Color m_borderColor; | ||
| 26 | |||
| 27 | protected BoxHighlightPainter(Color fillColor, Color borderColor) { | ||
| 28 | m_fillColor = fillColor; | ||
| 29 | m_borderColor = borderColor; | ||
| 30 | } | ||
| 31 | |||
| 32 | @Override | ||
| 33 | public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { | ||
| 34 | Rectangle bounds = getBounds(text, start, end); | ||
| 35 | |||
| 36 | // fill the area | ||
| 37 | if (m_fillColor != null) { | ||
| 38 | g.setColor(m_fillColor); | ||
| 39 | g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); | ||
| 40 | } | ||
| 41 | |||
| 42 | // draw a box around the area | ||
| 43 | g.setColor(m_borderColor); | ||
| 44 | g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); | ||
| 45 | } | ||
| 46 | |||
| 47 | protected static Rectangle getBounds(JTextComponent text, int start, int end) { | ||
| 48 | try { | ||
| 49 | // determine the bounds of the text | ||
| 50 | Rectangle bounds = text.modelToView(start).union(text.modelToView(end)); | ||
| 51 | |||
| 52 | // adjust the box so it looks nice | ||
| 53 | bounds.x -= 2; | ||
| 54 | bounds.width += 2; | ||
| 55 | bounds.y += 1; | ||
| 56 | bounds.height -= 2; | ||
| 57 | |||
| 58 | return bounds; | ||
| 59 | } catch (BadLocationException ex) { | ||
| 60 | // don't care... just return something | ||
| 61 | return new Rectangle(0, 0, 0, 0); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | } | ||
diff --git a/src/cuchaz/enigma/gui/BrowserCaret.java b/src/cuchaz/enigma/gui/BrowserCaret.java new file mode 100644 index 0000000..6af4d24 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Graphics; | ||
| 14 | import java.awt.Shape; | ||
| 15 | |||
| 16 | import javax.swing.text.DefaultCaret; | ||
| 17 | import javax.swing.text.Highlighter; | ||
| 18 | import javax.swing.text.JTextComponent; | ||
| 19 | |||
| 20 | public class BrowserCaret extends DefaultCaret { | ||
| 21 | |||
| 22 | private static final long serialVersionUID = 1158977422507969940L; | ||
| 23 | |||
| 24 | private static final Highlighter.HighlightPainter m_selectionPainter = new Highlighter.HighlightPainter() { | ||
| 25 | @Override | ||
| 26 | public void paint(Graphics g, int p0, int p1, Shape bounds, JTextComponent c) { | ||
| 27 | // don't paint anything | ||
| 28 | } | ||
| 29 | }; | ||
| 30 | |||
| 31 | @Override | ||
| 32 | public boolean isSelectionVisible() { | ||
| 33 | return false; | ||
| 34 | } | ||
| 35 | |||
| 36 | @Override | ||
| 37 | public boolean isVisible() { | ||
| 38 | return true; | ||
| 39 | } | ||
| 40 | |||
| 41 | @Override | ||
| 42 | public Highlighter.HighlightPainter getSelectionPainter() { | ||
| 43 | return m_selectionPainter; | ||
| 44 | } | ||
| 45 | } | ||
diff --git a/src/cuchaz/enigma/gui/ClassListCellRenderer.java b/src/cuchaz/enigma/gui/ClassListCellRenderer.java new file mode 100644 index 0000000..cde3e4c --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Component; | ||
| 14 | |||
| 15 | import javassist.bytecode.Descriptor; | ||
| 16 | |||
| 17 | import javax.swing.DefaultListCellRenderer; | ||
| 18 | import javax.swing.JLabel; | ||
| 19 | import javax.swing.JList; | ||
| 20 | import javax.swing.ListCellRenderer; | ||
| 21 | |||
| 22 | public 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 0000000..89b19c3 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.BorderLayout; | ||
| 14 | import java.awt.Container; | ||
| 15 | import java.awt.Dimension; | ||
| 16 | import java.awt.FlowLayout; | ||
| 17 | import java.awt.event.ActionEvent; | ||
| 18 | import java.awt.event.ActionListener; | ||
| 19 | import java.util.Collection; | ||
| 20 | import java.util.List; | ||
| 21 | import java.util.Map; | ||
| 22 | |||
| 23 | import javax.swing.BoxLayout; | ||
| 24 | import javax.swing.ButtonGroup; | ||
| 25 | import javax.swing.JButton; | ||
| 26 | import javax.swing.JCheckBox; | ||
| 27 | import javax.swing.JFrame; | ||
| 28 | import javax.swing.JLabel; | ||
| 29 | import javax.swing.JPanel; | ||
| 30 | import javax.swing.JRadioButton; | ||
| 31 | import javax.swing.JScrollPane; | ||
| 32 | import javax.swing.JSplitPane; | ||
| 33 | import javax.swing.SwingConstants; | ||
| 34 | import javax.swing.WindowConstants; | ||
| 35 | |||
| 36 | import com.google.common.collect.BiMap; | ||
| 37 | import com.google.common.collect.Lists; | ||
| 38 | import com.google.common.collect.Maps; | ||
| 39 | |||
| 40 | import cuchaz.enigma.Constants; | ||
| 41 | import cuchaz.enigma.Deobfuscator; | ||
| 42 | import cuchaz.enigma.convert.ClassIdentifier; | ||
| 43 | import cuchaz.enigma.convert.ClassIdentity; | ||
| 44 | import cuchaz.enigma.convert.ClassMatch; | ||
| 45 | import cuchaz.enigma.convert.ClassMatches; | ||
| 46 | import cuchaz.enigma.convert.ClassMatching; | ||
| 47 | import cuchaz.enigma.convert.ClassNamer; | ||
| 48 | import cuchaz.enigma.convert.MappingsConverter; | ||
| 49 | import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; | ||
| 50 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 51 | import cuchaz.enigma.mapping.Mappings; | ||
| 52 | import cuchaz.enigma.mapping.MappingsChecker; | ||
| 53 | import de.sciss.syntaxpane.DefaultSyntaxKit; | ||
| 54 | |||
| 55 | |||
| 56 | public 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 0000000..11333a9 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.event.MouseAdapter; | ||
| 14 | import java.awt.event.MouseEvent; | ||
| 15 | import java.util.Collection; | ||
| 16 | import java.util.Collections; | ||
| 17 | import java.util.Comparator; | ||
| 18 | import java.util.Enumeration; | ||
| 19 | import java.util.List; | ||
| 20 | import java.util.Map; | ||
| 21 | |||
| 22 | import javax.swing.JTree; | ||
| 23 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 24 | import javax.swing.tree.DefaultTreeModel; | ||
| 25 | import javax.swing.tree.TreePath; | ||
| 26 | |||
| 27 | import com.google.common.collect.ArrayListMultimap; | ||
| 28 | import com.google.common.collect.Lists; | ||
| 29 | import com.google.common.collect.Maps; | ||
| 30 | import com.google.common.collect.Multimap; | ||
| 31 | |||
| 32 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 33 | |||
| 34 | public 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 0000000..1219e89 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 14 | |||
| 15 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 16 | |||
| 17 | public 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 0000000..7259f54 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 14 | |||
| 15 | public 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 0000000..5033a2c --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Rectangle; | ||
| 14 | import java.awt.event.ActionEvent; | ||
| 15 | import java.awt.event.ActionListener; | ||
| 16 | |||
| 17 | import javax.swing.JEditorPane; | ||
| 18 | import javax.swing.SwingUtilities; | ||
| 19 | import javax.swing.Timer; | ||
| 20 | import javax.swing.event.CaretEvent; | ||
| 21 | import javax.swing.event.CaretListener; | ||
| 22 | import javax.swing.text.BadLocationException; | ||
| 23 | import javax.swing.text.Highlighter.HighlightPainter; | ||
| 24 | |||
| 25 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | ||
| 26 | |||
| 27 | import cuchaz.enigma.Deobfuscator; | ||
| 28 | import cuchaz.enigma.analysis.EntryReference; | ||
| 29 | import cuchaz.enigma.analysis.SourceIndex; | ||
| 30 | import cuchaz.enigma.analysis.Token; | ||
| 31 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 32 | import cuchaz.enigma.mapping.Entry; | ||
| 33 | import de.sciss.syntaxpane.DefaultSyntaxKit; | ||
| 34 | |||
| 35 | |||
| 36 | public 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 0000000..904273c --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.BorderLayout; | ||
| 14 | import java.awt.Container; | ||
| 15 | import java.awt.FlowLayout; | ||
| 16 | import java.awt.event.ActionEvent; | ||
| 17 | import java.awt.event.ActionListener; | ||
| 18 | import java.io.PrintWriter; | ||
| 19 | import java.io.StringWriter; | ||
| 20 | |||
| 21 | import javax.swing.BorderFactory; | ||
| 22 | import javax.swing.JButton; | ||
| 23 | import javax.swing.JFrame; | ||
| 24 | import javax.swing.JLabel; | ||
| 25 | import javax.swing.JPanel; | ||
| 26 | import javax.swing.JScrollPane; | ||
| 27 | import javax.swing.JTextArea; | ||
| 28 | import javax.swing.WindowConstants; | ||
| 29 | |||
| 30 | import cuchaz.enigma.Constants; | ||
| 31 | |||
| 32 | public class CrashDialog { | ||
| 33 | |||
| 34 | private static CrashDialog m_instance = null; | ||
| 35 | |||
| 36 | private JFrame m_frame; | ||
| 37 | private JTextArea m_text; | ||
| 38 | |||
| 39 | private CrashDialog(JFrame parent) { | ||
| 40 | // init frame | ||
| 41 | m_frame = new JFrame(Constants.Name + " - Crash Report"); | ||
| 42 | final Container pane = m_frame.getContentPane(); | ||
| 43 | pane.setLayout(new BorderLayout()); | ||
| 44 | |||
| 45 | JLabel label = new JLabel(Constants.Name + " has crashed! =("); | ||
| 46 | label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); | ||
| 47 | pane.add(label, BorderLayout.NORTH); | ||
| 48 | |||
| 49 | // report panel | ||
| 50 | m_text = new JTextArea(); | ||
| 51 | m_text.setTabSize(2); | ||
| 52 | pane.add(new JScrollPane(m_text), BorderLayout.CENTER); | ||
| 53 | |||
| 54 | // buttons panel | ||
| 55 | JPanel buttonsPanel = new JPanel(); | ||
| 56 | FlowLayout buttonsLayout = new FlowLayout(); | ||
| 57 | buttonsLayout.setAlignment(FlowLayout.RIGHT); | ||
| 58 | buttonsPanel.setLayout(buttonsLayout); | ||
| 59 | buttonsPanel.add(GuiTricks.unboldLabel(new JLabel("If you choose exit, you will lose any unsaved work."))); | ||
| 60 | JButton ignoreButton = new JButton("Ignore"); | ||
| 61 | ignoreButton.addActionListener(new ActionListener() { | ||
| 62 | @Override | ||
| 63 | public void actionPerformed(ActionEvent event) { | ||
| 64 | // close (hide) the dialog | ||
| 65 | m_frame.setVisible(false); | ||
| 66 | } | ||
| 67 | }); | ||
| 68 | buttonsPanel.add(ignoreButton); | ||
| 69 | JButton exitButton = new JButton("Exit"); | ||
| 70 | exitButton.addActionListener(new ActionListener() { | ||
| 71 | @Override | ||
| 72 | public void actionPerformed(ActionEvent event) { | ||
| 73 | // exit enigma | ||
| 74 | System.exit(1); | ||
| 75 | } | ||
| 76 | }); | ||
| 77 | buttonsPanel.add(exitButton); | ||
| 78 | pane.add(buttonsPanel, BorderLayout.SOUTH); | ||
| 79 | |||
| 80 | // show the frame | ||
| 81 | m_frame.setSize(600, 400); | ||
| 82 | m_frame.setLocationRelativeTo(parent); | ||
| 83 | m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); | ||
| 84 | } | ||
| 85 | |||
| 86 | public static void init(JFrame parent) { | ||
| 87 | m_instance = new CrashDialog(parent); | ||
| 88 | } | ||
| 89 | |||
| 90 | public static void show(Throwable ex) { | ||
| 91 | // get the error report | ||
| 92 | StringWriter buf = new StringWriter(); | ||
| 93 | ex.printStackTrace(new PrintWriter(buf)); | ||
| 94 | String report = buf.toString(); | ||
| 95 | |||
| 96 | // show it! | ||
| 97 | m_instance.m_text.setText(report); | ||
| 98 | m_instance.m_frame.doLayout(); | ||
| 99 | m_instance.m_frame.setVisible(true); | ||
| 100 | } | ||
| 101 | } | ||
diff --git a/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java b/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java new file mode 100644 index 0000000..57210a8 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Color; | ||
| 14 | |||
| 15 | public class DeobfuscatedHighlightPainter extends BoxHighlightPainter { | ||
| 16 | |||
| 17 | public DeobfuscatedHighlightPainter() { | ||
| 18 | // green ish | ||
| 19 | super(new Color(220, 255, 220), new Color(80, 160, 80)); | ||
| 20 | } | ||
| 21 | } | ||
diff --git a/src/cuchaz/enigma/gui/Gui.java b/src/cuchaz/enigma/gui/Gui.java new file mode 100644 index 0000000..f9192d3 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.BorderLayout; | ||
| 14 | import java.awt.Color; | ||
| 15 | import java.awt.Container; | ||
| 16 | import java.awt.Dimension; | ||
| 17 | import java.awt.FlowLayout; | ||
| 18 | import java.awt.GridLayout; | ||
| 19 | import java.awt.event.ActionEvent; | ||
| 20 | import java.awt.event.ActionListener; | ||
| 21 | import java.awt.event.InputEvent; | ||
| 22 | import java.awt.event.KeyAdapter; | ||
| 23 | import java.awt.event.KeyEvent; | ||
| 24 | import java.awt.event.MouseAdapter; | ||
| 25 | import java.awt.event.MouseEvent; | ||
| 26 | import java.awt.event.WindowAdapter; | ||
| 27 | import java.awt.event.WindowEvent; | ||
| 28 | import java.io.File; | ||
| 29 | import java.io.IOException; | ||
| 30 | import java.lang.Thread.UncaughtExceptionHandler; | ||
| 31 | import java.util.Collection; | ||
| 32 | import java.util.Collections; | ||
| 33 | import java.util.List; | ||
| 34 | import java.util.Vector; | ||
| 35 | import java.util.jar.JarFile; | ||
| 36 | |||
| 37 | import javax.swing.BorderFactory; | ||
| 38 | import javax.swing.JEditorPane; | ||
| 39 | import javax.swing.JFileChooser; | ||
| 40 | import javax.swing.JFrame; | ||
| 41 | import javax.swing.JLabel; | ||
| 42 | import javax.swing.JList; | ||
| 43 | import javax.swing.JMenu; | ||
| 44 | import javax.swing.JMenuBar; | ||
| 45 | import javax.swing.JMenuItem; | ||
| 46 | import javax.swing.JOptionPane; | ||
| 47 | import javax.swing.JPanel; | ||
| 48 | import javax.swing.JPopupMenu; | ||
| 49 | import javax.swing.JScrollPane; | ||
| 50 | import javax.swing.JSplitPane; | ||
| 51 | import javax.swing.JTabbedPane; | ||
| 52 | import javax.swing.JTextField; | ||
| 53 | import javax.swing.JTree; | ||
| 54 | import javax.swing.KeyStroke; | ||
| 55 | import javax.swing.ListSelectionModel; | ||
| 56 | import javax.swing.WindowConstants; | ||
| 57 | import javax.swing.event.CaretEvent; | ||
| 58 | import javax.swing.event.CaretListener; | ||
| 59 | import javax.swing.text.BadLocationException; | ||
| 60 | import javax.swing.text.Highlighter; | ||
| 61 | import javax.swing.tree.DefaultTreeModel; | ||
| 62 | import javax.swing.tree.TreeNode; | ||
| 63 | import javax.swing.tree.TreePath; | ||
| 64 | |||
| 65 | import com.google.common.collect.Lists; | ||
| 66 | |||
| 67 | import cuchaz.enigma.Constants; | ||
| 68 | import cuchaz.enigma.ExceptionIgnorer; | ||
| 69 | import cuchaz.enigma.analysis.BehaviorReferenceTreeNode; | ||
| 70 | import cuchaz.enigma.analysis.ClassImplementationsTreeNode; | ||
| 71 | import cuchaz.enigma.analysis.ClassInheritanceTreeNode; | ||
| 72 | import cuchaz.enigma.analysis.EntryReference; | ||
| 73 | import cuchaz.enigma.analysis.FieldReferenceTreeNode; | ||
| 74 | import cuchaz.enigma.analysis.MethodImplementationsTreeNode; | ||
| 75 | import cuchaz.enigma.analysis.MethodInheritanceTreeNode; | ||
| 76 | import cuchaz.enigma.analysis.ReferenceTreeNode; | ||
| 77 | import cuchaz.enigma.analysis.Token; | ||
| 78 | import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; | ||
| 79 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 80 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 81 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 82 | import cuchaz.enigma.mapping.Entry; | ||
| 83 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 84 | import cuchaz.enigma.mapping.IllegalNameException; | ||
| 85 | import cuchaz.enigma.mapping.MappingParseException; | ||
| 86 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 87 | import cuchaz.enigma.mapping.Signature; | ||
| 88 | import de.sciss.syntaxpane.DefaultSyntaxKit; | ||
| 89 | |||
| 90 | public 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 0000000..6690622 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.io.File; | ||
| 14 | import java.io.FileReader; | ||
| 15 | import java.io.FileWriter; | ||
| 16 | import java.io.IOException; | ||
| 17 | import java.util.Collection; | ||
| 18 | import java.util.Deque; | ||
| 19 | import java.util.List; | ||
| 20 | import java.util.jar.JarFile; | ||
| 21 | |||
| 22 | import com.google.common.collect.Lists; | ||
| 23 | import com.google.common.collect.Queues; | ||
| 24 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | ||
| 25 | |||
| 26 | import cuchaz.enigma.Deobfuscator; | ||
| 27 | import cuchaz.enigma.Deobfuscator.ProgressListener; | ||
| 28 | import cuchaz.enigma.analysis.BehaviorReferenceTreeNode; | ||
| 29 | import cuchaz.enigma.analysis.ClassImplementationsTreeNode; | ||
| 30 | import cuchaz.enigma.analysis.ClassInheritanceTreeNode; | ||
| 31 | import cuchaz.enigma.analysis.EntryReference; | ||
| 32 | import cuchaz.enigma.analysis.FieldReferenceTreeNode; | ||
| 33 | import cuchaz.enigma.analysis.MethodImplementationsTreeNode; | ||
| 34 | import cuchaz.enigma.analysis.MethodInheritanceTreeNode; | ||
| 35 | import cuchaz.enigma.analysis.SourceIndex; | ||
| 36 | import cuchaz.enigma.analysis.Token; | ||
| 37 | import cuchaz.enigma.gui.ProgressDialog.ProgressRunnable; | ||
| 38 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 39 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 40 | import cuchaz.enigma.mapping.Entry; | ||
| 41 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 42 | import cuchaz.enigma.mapping.MappingParseException; | ||
| 43 | import cuchaz.enigma.mapping.MappingsReader; | ||
| 44 | import cuchaz.enigma.mapping.MappingsWriter; | ||
| 45 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 46 | import cuchaz.enigma.mapping.TranslationDirection; | ||
| 47 | |||
| 48 | public 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 0000000..5dc3ffb --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Font; | ||
| 14 | import java.awt.event.ActionListener; | ||
| 15 | import java.awt.event.MouseEvent; | ||
| 16 | import java.util.Arrays; | ||
| 17 | |||
| 18 | import javax.swing.JButton; | ||
| 19 | import javax.swing.JComponent; | ||
| 20 | import javax.swing.JLabel; | ||
| 21 | import javax.swing.ToolTipManager; | ||
| 22 | |||
| 23 | public 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 0000000..150eaad --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.BorderLayout; | ||
| 14 | import java.awt.Container; | ||
| 15 | import java.awt.Dimension; | ||
| 16 | import java.awt.FlowLayout; | ||
| 17 | import java.awt.event.ActionEvent; | ||
| 18 | import java.awt.event.ActionListener; | ||
| 19 | import java.awt.event.KeyAdapter; | ||
| 20 | import java.awt.event.KeyEvent; | ||
| 21 | import java.util.Collection; | ||
| 22 | import java.util.List; | ||
| 23 | import java.util.Map; | ||
| 24 | |||
| 25 | import javax.swing.BoxLayout; | ||
| 26 | import javax.swing.ButtonGroup; | ||
| 27 | import javax.swing.JButton; | ||
| 28 | import javax.swing.JFrame; | ||
| 29 | import javax.swing.JLabel; | ||
| 30 | import javax.swing.JPanel; | ||
| 31 | import javax.swing.JRadioButton; | ||
| 32 | import javax.swing.JScrollPane; | ||
| 33 | import javax.swing.JSplitPane; | ||
| 34 | import javax.swing.WindowConstants; | ||
| 35 | import javax.swing.text.Highlighter.HighlightPainter; | ||
| 36 | |||
| 37 | import com.google.common.collect.Lists; | ||
| 38 | import com.google.common.collect.Maps; | ||
| 39 | |||
| 40 | import cuchaz.enigma.Constants; | ||
| 41 | import cuchaz.enigma.Deobfuscator; | ||
| 42 | import cuchaz.enigma.analysis.EntryReference; | ||
| 43 | import cuchaz.enigma.analysis.SourceIndex; | ||
| 44 | import cuchaz.enigma.analysis.Token; | ||
| 45 | import cuchaz.enigma.convert.ClassMatches; | ||
| 46 | import cuchaz.enigma.convert.MemberMatches; | ||
| 47 | import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; | ||
| 48 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 49 | import cuchaz.enigma.mapping.Entry; | ||
| 50 | import de.sciss.syntaxpane.DefaultSyntaxKit; | ||
| 51 | |||
| 52 | |||
| 53 | public 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 0000000..4c3714a --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Color; | ||
| 14 | |||
| 15 | public class ObfuscatedHighlightPainter extends BoxHighlightPainter { | ||
| 16 | |||
| 17 | public ObfuscatedHighlightPainter() { | ||
| 18 | // red ish | ||
| 19 | super(new Color(255, 220, 220), new Color(160, 80, 80)); | ||
| 20 | } | ||
| 21 | } | ||
diff --git a/src/cuchaz/enigma/gui/OtherHighlightPainter.java b/src/cuchaz/enigma/gui/OtherHighlightPainter.java new file mode 100644 index 0000000..8d3fbe8 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Color; | ||
| 14 | |||
| 15 | public class OtherHighlightPainter extends BoxHighlightPainter { | ||
| 16 | |||
| 17 | public OtherHighlightPainter() { | ||
| 18 | // grey | ||
| 19 | super(null, new Color(180, 180, 180)); | ||
| 20 | } | ||
| 21 | } | ||
diff --git a/src/cuchaz/enigma/gui/ProgressDialog.java b/src/cuchaz/enigma/gui/ProgressDialog.java new file mode 100644 index 0000000..1c20f10 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.BorderLayout; | ||
| 14 | import java.awt.Container; | ||
| 15 | import java.awt.Dimension; | ||
| 16 | import java.awt.FlowLayout; | ||
| 17 | |||
| 18 | import javax.swing.BorderFactory; | ||
| 19 | import javax.swing.JFrame; | ||
| 20 | import javax.swing.JLabel; | ||
| 21 | import javax.swing.JPanel; | ||
| 22 | import javax.swing.JProgressBar; | ||
| 23 | import javax.swing.WindowConstants; | ||
| 24 | |||
| 25 | import cuchaz.enigma.Constants; | ||
| 26 | import cuchaz.enigma.Deobfuscator.ProgressListener; | ||
| 27 | |||
| 28 | public class ProgressDialog implements ProgressListener, AutoCloseable { | ||
| 29 | |||
| 30 | private JFrame m_frame; | ||
| 31 | private JLabel m_title; | ||
| 32 | private JLabel m_text; | ||
| 33 | private JProgressBar m_progress; | ||
| 34 | |||
| 35 | public ProgressDialog(JFrame parent) { | ||
| 36 | |||
| 37 | // init frame | ||
| 38 | m_frame = new JFrame(Constants.Name + " - Operation in progress"); | ||
| 39 | final Container pane = m_frame.getContentPane(); | ||
| 40 | FlowLayout layout = new FlowLayout(); | ||
| 41 | layout.setAlignment(FlowLayout.LEFT); | ||
| 42 | pane.setLayout(layout); | ||
| 43 | |||
| 44 | m_title = new JLabel(); | ||
| 45 | pane.add(m_title); | ||
| 46 | |||
| 47 | // set up the progress bar | ||
| 48 | JPanel panel = new JPanel(); | ||
| 49 | pane.add(panel); | ||
| 50 | panel.setLayout(new BorderLayout()); | ||
| 51 | m_text = GuiTricks.unboldLabel(new JLabel()); | ||
| 52 | m_progress = new JProgressBar(); | ||
| 53 | m_text.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); | ||
| 54 | panel.add(m_text, BorderLayout.NORTH); | ||
| 55 | panel.add(m_progress, BorderLayout.CENTER); | ||
| 56 | panel.setPreferredSize(new Dimension(360, 50)); | ||
| 57 | |||
| 58 | // show the frame | ||
| 59 | pane.doLayout(); | ||
| 60 | m_frame.setSize(400, 120); | ||
| 61 | m_frame.setResizable(false); | ||
| 62 | m_frame.setLocationRelativeTo(parent); | ||
| 63 | m_frame.setVisible(true); | ||
| 64 | m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); | ||
| 65 | } | ||
| 66 | |||
| 67 | public void close() { | ||
| 68 | m_frame.dispose(); | ||
| 69 | } | ||
| 70 | |||
| 71 | @Override | ||
| 72 | public void init(int totalWork, String title) { | ||
| 73 | m_title.setText(title); | ||
| 74 | m_progress.setMinimum(0); | ||
| 75 | m_progress.setMaximum(totalWork); | ||
| 76 | m_progress.setValue(0); | ||
| 77 | } | ||
| 78 | |||
| 79 | @Override | ||
| 80 | public void onProgress(int numDone, String message) { | ||
| 81 | m_text.setText(message); | ||
| 82 | m_progress.setValue(numDone); | ||
| 83 | |||
| 84 | // update the frame | ||
| 85 | m_frame.validate(); | ||
| 86 | m_frame.repaint(); | ||
| 87 | } | ||
| 88 | |||
| 89 | public static interface ProgressRunnable { | ||
| 90 | void run(ProgressListener listener) throws Exception; | ||
| 91 | } | ||
| 92 | |||
| 93 | public static void runInThread(final JFrame parent, final ProgressRunnable runnable) { | ||
| 94 | new Thread() { | ||
| 95 | @Override | ||
| 96 | public void run() { | ||
| 97 | try (ProgressDialog progress = new ProgressDialog(parent)) { | ||
| 98 | runnable.run(progress); | ||
| 99 | } catch (Exception ex) { | ||
| 100 | throw new Error(ex); | ||
| 101 | } | ||
| 102 | } | ||
| 103 | }.start(); | ||
| 104 | } | ||
| 105 | } | ||
diff --git a/src/cuchaz/enigma/gui/ReadableToken.java b/src/cuchaz/enigma/gui/ReadableToken.java new file mode 100644 index 0000000..0741af3 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | public class ReadableToken { | ||
| 14 | |||
| 15 | public int line; | ||
| 16 | public int startColumn; | ||
| 17 | public int endColumn; | ||
| 18 | |||
| 19 | public ReadableToken(int line, int startColumn, int endColumn) { | ||
| 20 | this.line = line; | ||
| 21 | this.startColumn = startColumn; | ||
| 22 | this.endColumn = endColumn; | ||
| 23 | } | ||
| 24 | |||
| 25 | @Override | ||
| 26 | public String toString() { | ||
| 27 | StringBuilder buf = new StringBuilder(); | ||
| 28 | buf.append("line "); | ||
| 29 | buf.append(line); | ||
| 30 | buf.append(" columns "); | ||
| 31 | buf.append(startColumn); | ||
| 32 | buf.append("-"); | ||
| 33 | buf.append(endColumn); | ||
| 34 | return buf.toString(); | ||
| 35 | } | ||
| 36 | } | ||
diff --git a/src/cuchaz/enigma/gui/RenameListener.java b/src/cuchaz/enigma/gui/RenameListener.java new file mode 100644 index 0000000..8b515bb --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import cuchaz.enigma.mapping.Entry; | ||
| 14 | |||
| 15 | public 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 0000000..6070452 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 14 | |||
| 15 | |||
| 16 | public 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 0000000..4165da4 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.BasicStroke; | ||
| 14 | import java.awt.Color; | ||
| 15 | import java.awt.Graphics; | ||
| 16 | import java.awt.Graphics2D; | ||
| 17 | import java.awt.Rectangle; | ||
| 18 | import java.awt.Shape; | ||
| 19 | |||
| 20 | import javax.swing.text.Highlighter; | ||
| 21 | import javax.swing.text.JTextComponent; | ||
| 22 | |||
| 23 | public class SelectionHighlightPainter implements Highlighter.HighlightPainter { | ||
| 24 | |||
| 25 | @Override | ||
| 26 | public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { | ||
| 27 | // draw a thick border | ||
| 28 | Graphics2D g2d = (Graphics2D)g; | ||
| 29 | Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end); | ||
| 30 | g2d.setColor(Color.black); | ||
| 31 | g2d.setStroke(new BasicStroke(2.0f)); | ||
| 32 | g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); | ||
| 33 | } | ||
| 34 | } | ||
diff --git a/src/cuchaz/enigma/gui/TokenListCellRenderer.java b/src/cuchaz/enigma/gui/TokenListCellRenderer.java new file mode 100644 index 0000000..e4f7c87 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Component; | ||
| 14 | |||
| 15 | import javax.swing.DefaultListCellRenderer; | ||
| 16 | import javax.swing.JLabel; | ||
| 17 | import javax.swing.JList; | ||
| 18 | import javax.swing.ListCellRenderer; | ||
| 19 | |||
| 20 | import cuchaz.enigma.analysis.Token; | ||
| 21 | |||
| 22 | public class TokenListCellRenderer implements ListCellRenderer<Token> { | ||
| 23 | |||
| 24 | private GuiController m_controller; | ||
| 25 | private DefaultListCellRenderer m_defaultRenderer; | ||
| 26 | |||
| 27 | public TokenListCellRenderer(GuiController controller) { | ||
| 28 | m_controller = controller; | ||
| 29 | m_defaultRenderer = new DefaultListCellRenderer(); | ||
| 30 | } | ||
| 31 | |||
| 32 | @Override | ||
| 33 | public Component getListCellRendererComponent(JList<? extends Token> list, Token token, int index, boolean isSelected, boolean hasFocus) { | ||
| 34 | JLabel label = (JLabel)m_defaultRenderer.getListCellRendererComponent(list, token, index, isSelected, hasFocus); | ||
| 35 | label.setText(m_controller.getReadableToken(token).toString()); | ||
| 36 | return label; | ||
| 37 | } | ||
| 38 | } | ||
diff --git a/src/cuchaz/enigma/mapping/ArgumentEntry.java b/src/cuchaz/enigma/mapping/ArgumentEntry.java new file mode 100644 index 0000000..9d99016 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | |||
| 15 | import cuchaz.enigma.Util; | ||
| 16 | |||
| 17 | public class ArgumentEntry implements Entry, Serializable { | ||
| 18 | |||
| 19 | private static final long serialVersionUID = 4472172468162696006L; | ||
| 20 | |||
| 21 | private BehaviorEntry m_behaviorEntry; | ||
| 22 | private int m_index; | ||
| 23 | private String m_name; | ||
| 24 | |||
| 25 | public ArgumentEntry(BehaviorEntry behaviorEntry, int index, String name) { | ||
| 26 | if (behaviorEntry == null) { | ||
| 27 | throw new IllegalArgumentException("Behavior cannot be null!"); | ||
| 28 | } | ||
| 29 | if (index < 0) { | ||
| 30 | throw new IllegalArgumentException("Index must be non-negative!"); | ||
| 31 | } | ||
| 32 | if (name == null) { | ||
| 33 | throw new IllegalArgumentException("Argument name cannot be null!"); | ||
| 34 | } | ||
| 35 | |||
| 36 | m_behaviorEntry = behaviorEntry; | ||
| 37 | m_index = index; | ||
| 38 | m_name = name; | ||
| 39 | } | ||
| 40 | |||
| 41 | public ArgumentEntry(ArgumentEntry other) { | ||
| 42 | m_behaviorEntry = (BehaviorEntry)m_behaviorEntry.cloneToNewClass(getClassEntry()); | ||
| 43 | m_index = other.m_index; | ||
| 44 | m_name = other.m_name; | ||
| 45 | } | ||
| 46 | |||
| 47 | public ArgumentEntry(ArgumentEntry other, String newClassName) { | ||
| 48 | m_behaviorEntry = (BehaviorEntry)other.m_behaviorEntry.cloneToNewClass(new ClassEntry(newClassName)); | ||
| 49 | m_index = other.m_index; | ||
| 50 | m_name = other.m_name; | ||
| 51 | } | ||
| 52 | |||
| 53 | public BehaviorEntry getBehaviorEntry() { | ||
| 54 | return m_behaviorEntry; | ||
| 55 | } | ||
| 56 | |||
| 57 | public int getIndex() { | ||
| 58 | return m_index; | ||
| 59 | } | ||
| 60 | |||
| 61 | @Override | ||
| 62 | public String getName() { | ||
| 63 | return m_name; | ||
| 64 | } | ||
| 65 | |||
| 66 | @Override | ||
| 67 | public ClassEntry getClassEntry() { | ||
| 68 | return m_behaviorEntry.getClassEntry(); | ||
| 69 | } | ||
| 70 | |||
| 71 | @Override | ||
| 72 | public String getClassName() { | ||
| 73 | return m_behaviorEntry.getClassName(); | ||
| 74 | } | ||
| 75 | |||
| 76 | @Override | ||
| 77 | public ArgumentEntry cloneToNewClass(ClassEntry classEntry) { | ||
| 78 | return new ArgumentEntry(this, classEntry.getName()); | ||
| 79 | } | ||
| 80 | |||
| 81 | public String getMethodName() { | ||
| 82 | return m_behaviorEntry.getName(); | ||
| 83 | } | ||
| 84 | |||
| 85 | public Signature getMethodSignature() { | ||
| 86 | return m_behaviorEntry.getSignature(); | ||
| 87 | } | ||
| 88 | |||
| 89 | @Override | ||
| 90 | public int hashCode() { | ||
| 91 | return Util.combineHashesOrdered( | ||
| 92 | m_behaviorEntry, | ||
| 93 | Integer.valueOf(m_index).hashCode(), | ||
| 94 | m_name.hashCode() | ||
| 95 | ); | ||
| 96 | } | ||
| 97 | |||
| 98 | @Override | ||
| 99 | public boolean equals(Object other) { | ||
| 100 | if (other instanceof ArgumentEntry) { | ||
| 101 | return equals((ArgumentEntry)other); | ||
| 102 | } | ||
| 103 | return false; | ||
| 104 | } | ||
| 105 | |||
| 106 | public boolean equals(ArgumentEntry other) { | ||
| 107 | return m_behaviorEntry.equals(other.m_behaviorEntry) | ||
| 108 | && m_index == other.m_index | ||
| 109 | && m_name.equals(other.m_name); | ||
| 110 | } | ||
| 111 | |||
| 112 | @Override | ||
| 113 | public String toString() { | ||
| 114 | return m_behaviorEntry.toString() + "(" + m_index + ":" + m_name + ")"; | ||
| 115 | } | ||
| 116 | } | ||
diff --git a/src/cuchaz/enigma/mapping/ArgumentMapping.java b/src/cuchaz/enigma/mapping/ArgumentMapping.java new file mode 100644 index 0000000..a0055a6 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | |||
| 15 | public 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 0000000..031d267 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | public 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 0000000..373203f --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | import java.util.List; | ||
| 15 | |||
| 16 | import com.google.common.collect.Lists; | ||
| 17 | |||
| 18 | public 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 0000000..0b0105e --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | import java.util.ArrayList; | ||
| 15 | import java.util.Map; | ||
| 16 | |||
| 17 | import com.google.common.collect.Maps; | ||
| 18 | |||
| 19 | public 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 0000000..f00d811 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | public 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 0000000..7cde8f6 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | |||
| 15 | import cuchaz.enigma.Util; | ||
| 16 | |||
| 17 | public class ConstructorEntry implements BehaviorEntry, Serializable { | ||
| 18 | |||
| 19 | private static final long serialVersionUID = -868346075317366758L; | ||
| 20 | |||
| 21 | private ClassEntry m_classEntry; | ||
| 22 | private Signature m_signature; | ||
| 23 | |||
| 24 | public ConstructorEntry(ClassEntry classEntry) { | ||
| 25 | this(classEntry, null); | ||
| 26 | } | ||
| 27 | |||
| 28 | public ConstructorEntry(ClassEntry classEntry, Signature signature) { | ||
| 29 | if (classEntry == null) { | ||
| 30 | throw new IllegalArgumentException("Class cannot be null!"); | ||
| 31 | } | ||
| 32 | |||
| 33 | m_classEntry = classEntry; | ||
| 34 | m_signature = signature; | ||
| 35 | } | ||
| 36 | |||
| 37 | public ConstructorEntry(ConstructorEntry other) { | ||
| 38 | m_classEntry = new ClassEntry(other.m_classEntry); | ||
| 39 | m_signature = other.m_signature; | ||
| 40 | } | ||
| 41 | |||
| 42 | public ConstructorEntry(ConstructorEntry other, String newClassName) { | ||
| 43 | m_classEntry = new ClassEntry(newClassName); | ||
| 44 | m_signature = other.m_signature; | ||
| 45 | } | ||
| 46 | |||
| 47 | @Override | ||
| 48 | public ClassEntry getClassEntry() { | ||
| 49 | return m_classEntry; | ||
| 50 | } | ||
| 51 | |||
| 52 | @Override | ||
| 53 | public String getName() { | ||
| 54 | if (isStatic()) { | ||
| 55 | return "<clinit>"; | ||
| 56 | } | ||
| 57 | return "<init>"; | ||
| 58 | } | ||
| 59 | |||
| 60 | public boolean isStatic() { | ||
| 61 | return m_signature == null; | ||
| 62 | } | ||
| 63 | |||
| 64 | @Override | ||
| 65 | public Signature getSignature() { | ||
| 66 | return m_signature; | ||
| 67 | } | ||
| 68 | |||
| 69 | @Override | ||
| 70 | public String getClassName() { | ||
| 71 | return m_classEntry.getName(); | ||
| 72 | } | ||
| 73 | |||
| 74 | @Override | ||
| 75 | public ConstructorEntry cloneToNewClass(ClassEntry classEntry) { | ||
| 76 | return new ConstructorEntry(this, classEntry.getName()); | ||
| 77 | } | ||
| 78 | |||
| 79 | @Override | ||
| 80 | public int hashCode() { | ||
| 81 | if (isStatic()) { | ||
| 82 | return Util.combineHashesOrdered(m_classEntry); | ||
| 83 | } else { | ||
| 84 | return Util.combineHashesOrdered(m_classEntry, m_signature); | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | @Override | ||
| 89 | public boolean equals(Object other) { | ||
| 90 | if (other instanceof ConstructorEntry) { | ||
| 91 | return equals((ConstructorEntry)other); | ||
| 92 | } | ||
| 93 | return false; | ||
| 94 | } | ||
| 95 | |||
| 96 | public boolean equals(ConstructorEntry other) { | ||
| 97 | if (isStatic() != other.isStatic()) { | ||
| 98 | return false; | ||
| 99 | } | ||
| 100 | |||
| 101 | if (isStatic()) { | ||
| 102 | return m_classEntry.equals(other.m_classEntry); | ||
| 103 | } else { | ||
| 104 | return m_classEntry.equals(other.m_classEntry) && m_signature.equals(other.m_signature); | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | @Override | ||
| 109 | public String toString() { | ||
| 110 | if (isStatic()) { | ||
| 111 | return m_classEntry.getName() + "." + getName(); | ||
| 112 | } else { | ||
| 113 | return m_classEntry.getName() + "." + getName() + m_signature; | ||
| 114 | } | ||
| 115 | } | ||
| 116 | } | ||
diff --git a/src/cuchaz/enigma/mapping/Entry.java b/src/cuchaz/enigma/mapping/Entry.java new file mode 100644 index 0000000..3c94a95 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | public 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 0000000..03d97ba --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import javassist.CtBehavior; | ||
| 14 | import javassist.CtClass; | ||
| 15 | import javassist.CtConstructor; | ||
| 16 | import javassist.CtField; | ||
| 17 | import javassist.CtMethod; | ||
| 18 | import javassist.bytecode.Descriptor; | ||
| 19 | import javassist.expr.ConstructorCall; | ||
| 20 | import javassist.expr.FieldAccess; | ||
| 21 | import javassist.expr.MethodCall; | ||
| 22 | import javassist.expr.NewExpr; | ||
| 23 | |||
| 24 | import cuchaz.enigma.analysis.JarIndex; | ||
| 25 | |||
| 26 | public 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 0000000..82b28cd --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | public class EntryPair<T extends Entry> { | ||
| 14 | |||
| 15 | public T obf; | ||
| 16 | public T deobf; | ||
| 17 | |||
| 18 | public EntryPair(T obf, T deobf) { | ||
| 19 | this.obf = obf; | ||
| 20 | this.deobf = deobf; | ||
| 21 | } | ||
| 22 | } | ||
diff --git a/src/cuchaz/enigma/mapping/FieldEntry.java b/src/cuchaz/enigma/mapping/FieldEntry.java new file mode 100644 index 0000000..e4a74f4 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | |||
| 15 | import cuchaz.enigma.Util; | ||
| 16 | |||
| 17 | public 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 0000000..2855740 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | |||
| 15 | public 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 0000000..f62df7c --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | public 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 0000000..73fca94 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | public class MappingParseException extends Exception { | ||
| 14 | |||
| 15 | private static final long serialVersionUID = -5487280332892507236L; | ||
| 16 | |||
| 17 | private int m_line; | ||
| 18 | private String m_message; | ||
| 19 | |||
| 20 | public MappingParseException(int line, String message) { | ||
| 21 | m_line = line; | ||
| 22 | m_message = message; | ||
| 23 | } | ||
| 24 | |||
| 25 | @Override | ||
| 26 | public String getMessage() { | ||
| 27 | return "Line " + m_line + ": " + m_message; | ||
| 28 | } | ||
| 29 | } | ||
diff --git a/src/cuchaz/enigma/mapping/Mappings.java b/src/cuchaz/enigma/mapping/Mappings.java new file mode 100644 index 0000000..11ed5d0 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | import java.util.ArrayList; | ||
| 15 | import java.util.Collection; | ||
| 16 | import java.util.List; | ||
| 17 | import java.util.Map; | ||
| 18 | import java.util.Set; | ||
| 19 | |||
| 20 | import com.google.common.collect.Lists; | ||
| 21 | import com.google.common.collect.Maps; | ||
| 22 | import com.google.common.collect.Sets; | ||
| 23 | |||
| 24 | import cuchaz.enigma.analysis.TranslationIndex; | ||
| 25 | |||
| 26 | public 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 0000000..b25ea3c --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.util.Map; | ||
| 14 | |||
| 15 | import com.google.common.collect.Lists; | ||
| 16 | import com.google.common.collect.Maps; | ||
| 17 | |||
| 18 | import cuchaz.enigma.analysis.JarIndex; | ||
| 19 | import cuchaz.enigma.analysis.RelatedMethodChecker; | ||
| 20 | |||
| 21 | |||
| 22 | public 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 0000000..0a4b117 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.BufferedReader; | ||
| 14 | import java.io.IOException; | ||
| 15 | import java.io.Reader; | ||
| 16 | import java.util.Deque; | ||
| 17 | |||
| 18 | import com.google.common.collect.Queues; | ||
| 19 | |||
| 20 | public 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 0000000..47e5738 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.IOException; | ||
| 14 | import java.io.ObjectOutputStream; | ||
| 15 | import java.io.OutputStream; | ||
| 16 | import java.util.List; | ||
| 17 | import java.util.Set; | ||
| 18 | import java.util.zip.GZIPOutputStream; | ||
| 19 | |||
| 20 | import cuchaz.enigma.analysis.JarIndex; | ||
| 21 | |||
| 22 | public 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 0000000..1ebefef --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.IOException; | ||
| 14 | import java.io.PrintWriter; | ||
| 15 | import java.io.Writer; | ||
| 16 | import java.util.ArrayList; | ||
| 17 | import java.util.Collections; | ||
| 18 | import java.util.List; | ||
| 19 | |||
| 20 | public 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 0000000..8378297 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | |||
| 14 | public 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 0000000..eb9e204 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | |||
| 15 | import cuchaz.enigma.Util; | ||
| 16 | |||
| 17 | public class MethodEntry implements BehaviorEntry, Serializable { | ||
| 18 | |||
| 19 | private static final long serialVersionUID = 4770915224467247458L; | ||
| 20 | |||
| 21 | private ClassEntry m_classEntry; | ||
| 22 | private String m_name; | ||
| 23 | private Signature m_signature; | ||
| 24 | |||
| 25 | public MethodEntry(ClassEntry classEntry, String name, Signature signature) { | ||
| 26 | if (classEntry == null) { | ||
| 27 | throw new IllegalArgumentException("Class cannot be null!"); | ||
| 28 | } | ||
| 29 | if (name == null) { | ||
| 30 | throw new IllegalArgumentException("Method name cannot be null!"); | ||
| 31 | } | ||
| 32 | if (signature == null) { | ||
| 33 | throw new IllegalArgumentException("Method signature cannot be null!"); | ||
| 34 | } | ||
| 35 | if (name.startsWith("<")) { | ||
| 36 | throw new IllegalArgumentException("Don't use MethodEntry for a constructor!"); | ||
| 37 | } | ||
| 38 | |||
| 39 | m_classEntry = classEntry; | ||
| 40 | m_name = name; | ||
| 41 | m_signature = signature; | ||
| 42 | } | ||
| 43 | |||
| 44 | public MethodEntry(MethodEntry other) { | ||
| 45 | m_classEntry = new ClassEntry(other.m_classEntry); | ||
| 46 | m_name = other.m_name; | ||
| 47 | m_signature = other.m_signature; | ||
| 48 | } | ||
| 49 | |||
| 50 | public MethodEntry(MethodEntry other, String newClassName) { | ||
| 51 | m_classEntry = new ClassEntry(newClassName); | ||
| 52 | m_name = other.m_name; | ||
| 53 | m_signature = other.m_signature; | ||
| 54 | } | ||
| 55 | |||
| 56 | @Override | ||
| 57 | public ClassEntry getClassEntry() { | ||
| 58 | return m_classEntry; | ||
| 59 | } | ||
| 60 | |||
| 61 | @Override | ||
| 62 | public String getName() { | ||
| 63 | return m_name; | ||
| 64 | } | ||
| 65 | |||
| 66 | @Override | ||
| 67 | public Signature getSignature() { | ||
| 68 | return m_signature; | ||
| 69 | } | ||
| 70 | |||
| 71 | @Override | ||
| 72 | public String getClassName() { | ||
| 73 | return m_classEntry.getName(); | ||
| 74 | } | ||
| 75 | |||
| 76 | @Override | ||
| 77 | public MethodEntry cloneToNewClass(ClassEntry classEntry) { | ||
| 78 | return new MethodEntry(this, classEntry.getName()); | ||
| 79 | } | ||
| 80 | |||
| 81 | @Override | ||
| 82 | public int hashCode() { | ||
| 83 | return Util.combineHashesOrdered(m_classEntry, m_name, m_signature); | ||
| 84 | } | ||
| 85 | |||
| 86 | @Override | ||
| 87 | public boolean equals(Object other) { | ||
| 88 | if (other instanceof MethodEntry) { | ||
| 89 | return equals((MethodEntry)other); | ||
| 90 | } | ||
| 91 | return false; | ||
| 92 | } | ||
| 93 | |||
| 94 | public boolean equals(MethodEntry other) { | ||
| 95 | return m_classEntry.equals(other.m_classEntry) | ||
| 96 | && m_name.equals(other.m_name) | ||
| 97 | && m_signature.equals(other.m_signature); | ||
| 98 | } | ||
| 99 | |||
| 100 | @Override | ||
| 101 | public String toString() { | ||
| 102 | return m_classEntry.getName() + "." + m_name + m_signature; | ||
| 103 | } | ||
| 104 | } | ||
diff --git a/src/cuchaz/enigma/mapping/MethodMapping.java b/src/cuchaz/enigma/mapping/MethodMapping.java new file mode 100644 index 0000000..055e1fe --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | import java.util.Map; | ||
| 15 | import java.util.Map.Entry; | ||
| 16 | |||
| 17 | import com.google.common.collect.Maps; | ||
| 18 | |||
| 19 | public 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 0000000..12520e1 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.util.Arrays; | ||
| 14 | import java.util.List; | ||
| 15 | import java.util.regex.Pattern; | ||
| 16 | |||
| 17 | import javassist.bytecode.Descriptor; | ||
| 18 | |||
| 19 | public 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 0000000..777a12e --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import com.strobel.assembler.metadata.FieldDefinition; | ||
| 14 | import com.strobel.assembler.metadata.MethodDefinition; | ||
| 15 | |||
| 16 | |||
| 17 | public 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 0000000..8f2b6b2 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | import java.util.List; | ||
| 15 | |||
| 16 | import com.google.common.collect.Lists; | ||
| 17 | |||
| 18 | import cuchaz.enigma.Util; | ||
| 19 | |||
| 20 | public 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 0000000..eb53233 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.IOException; | ||
| 14 | import java.io.StringReader; | ||
| 15 | import java.util.List; | ||
| 16 | |||
| 17 | import com.google.common.collect.Lists; | ||
| 18 | |||
| 19 | public class SignatureUpdater { | ||
| 20 | |||
| 21 | public interface ClassNameUpdater { | ||
| 22 | String update(String className); | ||
| 23 | } | ||
| 24 | |||
| 25 | public static String update(String signature, ClassNameUpdater updater) { | ||
| 26 | try { | ||
| 27 | StringBuilder buf = new StringBuilder(); | ||
| 28 | |||
| 29 | // read the signature character-by-character | ||
| 30 | StringReader reader = new StringReader(signature); | ||
| 31 | int i = -1; | ||
| 32 | while ( (i = reader.read()) != -1) { | ||
| 33 | char c = (char)i; | ||
| 34 | |||
| 35 | // does this character start a class name? | ||
| 36 | if (c == 'L') { | ||
| 37 | // update the class name and add it to the buffer | ||
| 38 | buf.append('L'); | ||
| 39 | String className = readClass(reader); | ||
| 40 | if (className == null) { | ||
| 41 | throw new IllegalArgumentException("Malformed signature: " + signature); | ||
| 42 | } | ||
| 43 | buf.append(updater.update(className)); | ||
| 44 | buf.append(';'); | ||
| 45 | } else { | ||
| 46 | // copy the character into the buffer | ||
| 47 | buf.append(c); | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | return buf.toString(); | ||
| 52 | } catch (IOException ex) { | ||
| 53 | // I'm pretty sure a StringReader will never throw one of these | ||
| 54 | throw new Error(ex); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | private static String readClass(StringReader reader) throws IOException { | ||
| 59 | // read all the characters in the buffer until we hit a ';' | ||
| 60 | // remember to treat generics correctly | ||
| 61 | StringBuilder buf = new StringBuilder(); | ||
| 62 | int depth = 0; | ||
| 63 | int i = -1; | ||
| 64 | while ( (i = reader.read()) != -1) { | ||
| 65 | char c = (char)i; | ||
| 66 | |||
| 67 | if (c == '<') { | ||
| 68 | depth++; | ||
| 69 | } else if (c == '>') { | ||
| 70 | depth--; | ||
| 71 | } else if (depth == 0) { | ||
| 72 | if (c == ';') { | ||
| 73 | return buf.toString(); | ||
| 74 | } else { | ||
| 75 | buf.append(c); | ||
| 76 | } | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | return null; | ||
| 81 | } | ||
| 82 | |||
| 83 | public static List<String> getClasses(String signature) { | ||
| 84 | final List<String> classNames = Lists.newArrayList(); | ||
| 85 | update(signature, new ClassNameUpdater() { | ||
| 86 | @Override | ||
| 87 | public String update(String className) { | ||
| 88 | classNames.add(className); | ||
| 89 | return className; | ||
| 90 | } | ||
| 91 | }); | ||
| 92 | return classNames; | ||
| 93 | } | ||
| 94 | } | ||
diff --git a/src/cuchaz/enigma/mapping/TranslationDirection.java b/src/cuchaz/enigma/mapping/TranslationDirection.java new file mode 100644 index 0000000..bc3aaa1 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | public enum TranslationDirection { | ||
| 14 | |||
| 15 | Deobfuscating { | ||
| 16 | @Override | ||
| 17 | public <T> T choose(T deobfChoice, T obfChoice) { | ||
| 18 | return deobfChoice; | ||
| 19 | } | ||
| 20 | }, | ||
| 21 | Obfuscating { | ||
| 22 | @Override | ||
| 23 | public <T> T choose(T deobfChoice, T obfChoice) { | ||
| 24 | return obfChoice; | ||
| 25 | } | ||
| 26 | }; | ||
| 27 | |||
| 28 | public abstract <T> T choose(T deobfChoice, T obfChoice); | ||
| 29 | } | ||
diff --git a/src/cuchaz/enigma/mapping/Translator.java b/src/cuchaz/enigma/mapping/Translator.java new file mode 100644 index 0000000..41c7d7c --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.util.List; | ||
| 14 | import java.util.Map; | ||
| 15 | |||
| 16 | import com.google.common.collect.Lists; | ||
| 17 | import com.google.common.collect.Maps; | ||
| 18 | |||
| 19 | import cuchaz.enigma.analysis.TranslationIndex; | ||
| 20 | |||
| 21 | public 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 0000000..f86a5cc --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | import java.util.Map; | ||
| 15 | |||
| 16 | import com.google.common.collect.Maps; | ||
| 17 | |||
| 18 | public 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 0000000..5f3ef8c --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | |||
| 14 | import static cuchaz.enigma.TestEntryFactory.*; | ||
| 15 | import static org.hamcrest.MatcherAssert.*; | ||
| 16 | import static org.hamcrest.Matchers.*; | ||
| 17 | |||
| 18 | import java.util.jar.JarFile; | ||
| 19 | |||
| 20 | import org.junit.BeforeClass; | ||
| 21 | import org.junit.Test; | ||
| 22 | |||
| 23 | import cuchaz.enigma.analysis.JarIndex; | ||
| 24 | |||
| 25 | |||
| 26 | public 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 0000000..1b0aa74 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import static org.junit.Assert.*; | ||
| 14 | |||
| 15 | import java.io.IOException; | ||
| 16 | import java.util.List; | ||
| 17 | import java.util.jar.JarFile; | ||
| 18 | |||
| 19 | import org.junit.Test; | ||
| 20 | |||
| 21 | import com.google.common.collect.Lists; | ||
| 22 | |||
| 23 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 24 | |||
| 25 | public 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 0000000..4aa773b --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import cuchaz.enigma.analysis.EntryReference; | ||
| 14 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 15 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 16 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 17 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 18 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 19 | import cuchaz.enigma.mapping.Signature; | ||
| 20 | import cuchaz.enigma.mapping.Type; | ||
| 21 | |||
| 22 | public 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 0000000..a4f9021 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import static org.hamcrest.MatcherAssert.*; | ||
| 14 | import static org.hamcrest.Matchers.*; | ||
| 15 | |||
| 16 | import java.util.jar.JarFile; | ||
| 17 | |||
| 18 | import org.junit.Test; | ||
| 19 | |||
| 20 | import static cuchaz.enigma.TestEntryFactory.*; | ||
| 21 | |||
| 22 | import cuchaz.enigma.analysis.JarIndex; | ||
| 23 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 24 | |||
| 25 | public 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 0000000..606801b --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import static cuchaz.enigma.TestEntryFactory.*; | ||
| 14 | import static org.hamcrest.MatcherAssert.*; | ||
| 15 | import static org.hamcrest.Matchers.*; | ||
| 16 | |||
| 17 | import java.io.File; | ||
| 18 | import java.util.Collection; | ||
| 19 | import java.util.jar.JarFile; | ||
| 20 | |||
| 21 | import org.junit.Test; | ||
| 22 | |||
| 23 | import cuchaz.enigma.analysis.EntryReference; | ||
| 24 | import cuchaz.enigma.analysis.JarIndex; | ||
| 25 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 26 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 27 | |||
| 28 | public 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 0000000..010f2dc --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import static cuchaz.enigma.TestEntryFactory.*; | ||
| 14 | import static org.hamcrest.MatcherAssert.*; | ||
| 15 | import static org.hamcrest.Matchers.*; | ||
| 16 | |||
| 17 | import java.util.Collection; | ||
| 18 | import java.util.Set; | ||
| 19 | import java.util.jar.JarFile; | ||
| 20 | |||
| 21 | import org.junit.Test; | ||
| 22 | |||
| 23 | import cuchaz.enigma.analysis.Access; | ||
| 24 | import cuchaz.enigma.analysis.EntryReference; | ||
| 25 | import cuchaz.enigma.analysis.JarIndex; | ||
| 26 | import cuchaz.enigma.analysis.TranslationIndex; | ||
| 27 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 28 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 29 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 30 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 31 | |||
| 32 | public 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 0000000..09479bb --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import static cuchaz.enigma.TestEntryFactory.*; | ||
| 14 | import static org.hamcrest.MatcherAssert.*; | ||
| 15 | import static org.hamcrest.Matchers.*; | ||
| 16 | |||
| 17 | import java.util.Collection; | ||
| 18 | import java.util.Set; | ||
| 19 | import java.util.jar.JarFile; | ||
| 20 | |||
| 21 | import org.junit.Test; | ||
| 22 | |||
| 23 | import cuchaz.enigma.analysis.Access; | ||
| 24 | import cuchaz.enigma.analysis.ClassImplementationsTreeNode; | ||
| 25 | import cuchaz.enigma.analysis.ClassInheritanceTreeNode; | ||
| 26 | import cuchaz.enigma.analysis.EntryReference; | ||
| 27 | import cuchaz.enigma.analysis.JarIndex; | ||
| 28 | import cuchaz.enigma.analysis.MethodInheritanceTreeNode; | ||
| 29 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 30 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 31 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 32 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 33 | import cuchaz.enigma.mapping.Translator; | ||
| 34 | |||
| 35 | public 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 0000000..8537adf --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import static org.hamcrest.MatcherAssert.*; | ||
| 14 | import static org.hamcrest.Matchers.*; | ||
| 15 | |||
| 16 | import org.junit.Test; | ||
| 17 | |||
| 18 | import cuchaz.enigma.mapping.ClassNameReplacer; | ||
| 19 | import cuchaz.enigma.mapping.Signature; | ||
| 20 | import cuchaz.enigma.mapping.Type; | ||
| 21 | |||
| 22 | |||
| 23 | public 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 0000000..58d9ca9 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import java.io.File; | ||
| 14 | import java.util.Set; | ||
| 15 | import java.util.jar.JarFile; | ||
| 16 | |||
| 17 | import org.junit.Test; | ||
| 18 | |||
| 19 | import com.google.common.collect.Sets; | ||
| 20 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | ||
| 21 | |||
| 22 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 23 | |||
| 24 | public 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 0000000..66c6fd1 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import static cuchaz.enigma.TestEntryFactory.*; | ||
| 14 | import static org.hamcrest.MatcherAssert.*; | ||
| 15 | import static org.hamcrest.Matchers.*; | ||
| 16 | |||
| 17 | import java.util.jar.JarFile; | ||
| 18 | |||
| 19 | import org.junit.Test; | ||
| 20 | |||
| 21 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 22 | |||
| 23 | public 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 0000000..9e58d25 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import static cuchaz.enigma.TestEntryFactory.*; | ||
| 14 | import static org.hamcrest.MatcherAssert.*; | ||
| 15 | import static org.hamcrest.Matchers.*; | ||
| 16 | |||
| 17 | import java.io.InputStream; | ||
| 18 | import java.io.InputStreamReader; | ||
| 19 | import java.util.jar.JarFile; | ||
| 20 | |||
| 21 | import org.junit.BeforeClass; | ||
| 22 | import org.junit.Test; | ||
| 23 | |||
| 24 | import cuchaz.enigma.mapping.Entry; | ||
| 25 | import cuchaz.enigma.mapping.Mappings; | ||
| 26 | import cuchaz.enigma.mapping.MappingsReader; | ||
| 27 | import cuchaz.enigma.mapping.TranslationDirection; | ||
| 28 | import cuchaz.enigma.mapping.Translator; | ||
| 29 | |||
| 30 | |||
| 31 | public 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 0000000..01c235b --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import static cuchaz.enigma.TestEntryFactory.*; | ||
| 14 | import static org.hamcrest.MatcherAssert.*; | ||
| 15 | import static org.hamcrest.Matchers.*; | ||
| 16 | |||
| 17 | import org.junit.Test; | ||
| 18 | |||
| 19 | import cuchaz.enigma.mapping.Type; | ||
| 20 | |||
| 21 | |||
| 22 | public 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 0000000..7afb4cf --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import java.io.IOException; | ||
| 14 | import java.util.Collection; | ||
| 15 | import java.util.List; | ||
| 16 | import java.util.jar.JarFile; | ||
| 17 | |||
| 18 | import com.google.common.collect.Lists; | ||
| 19 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | ||
| 20 | |||
| 21 | import cuchaz.enigma.analysis.EntryReference; | ||
| 22 | import cuchaz.enigma.analysis.SourceIndex; | ||
| 23 | import cuchaz.enigma.analysis.Token; | ||
| 24 | import cuchaz.enigma.mapping.Entry; | ||
| 25 | |||
| 26 | public class TokenChecker { | ||
| 27 | |||
| 28 | private Deobfuscator m_deobfuscator; | ||
| 29 | |||
| 30 | protected TokenChecker(JarFile jarFile) | ||
| 31 | throws IOException { | ||
| 32 | m_deobfuscator = new Deobfuscator(jarFile); | ||
| 33 | } | ||
| 34 | |||
| 35 | protected String getDeclarationToken(Entry entry) { | ||
| 36 | // decompile the class | ||
| 37 | CompilationUnit tree = m_deobfuscator.getSourceTree(entry.getClassName()); | ||
| 38 | // DEBUG | ||
| 39 | // tree.acceptVisitor( new TreeDumpVisitor( new File( "tree." + entry.getClassName().replace( '/', '.' ) + ".txt" ) ), null ); | ||
| 40 | String source = m_deobfuscator.getSource(tree); | ||
| 41 | SourceIndex index = m_deobfuscator.getSourceIndex(tree, source); | ||
| 42 | |||
| 43 | // get the token value | ||
| 44 | Token token = index.getDeclarationToken(entry); | ||
| 45 | if (token == null) { | ||
| 46 | return null; | ||
| 47 | } | ||
| 48 | return source.substring(token.start, token.end); | ||
| 49 | } | ||
| 50 | |||
| 51 | @SuppressWarnings("unchecked") | ||
| 52 | protected Collection<String> getReferenceTokens(EntryReference<? extends Entry,? extends Entry> reference) { | ||
| 53 | // decompile the class | ||
| 54 | CompilationUnit tree = m_deobfuscator.getSourceTree(reference.context.getClassName()); | ||
| 55 | String source = m_deobfuscator.getSource(tree); | ||
| 56 | SourceIndex index = m_deobfuscator.getSourceIndex(tree, source); | ||
| 57 | |||
| 58 | // get the token values | ||
| 59 | List<String> values = Lists.newArrayList(); | ||
| 60 | for (Token token : index.getReferenceTokens((EntryReference<Entry,Entry>)reference)) { | ||
| 61 | values.add(source.substring(token.start, token.end)); | ||
| 62 | } | ||
| 63 | return values; | ||
| 64 | } | ||
| 65 | } | ||
diff --git a/test/cuchaz/enigma/inputs/Keep.java b/test/cuchaz/enigma/inputs/Keep.java new file mode 100644 index 0000000..f04875f --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs; | ||
| 12 | |||
| 13 | public 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 0000000..65e782a --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.constructors; | ||
| 12 | |||
| 13 | // none/a | ||
| 14 | public 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 0000000..75096ec --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.constructors; | ||
| 12 | |||
| 13 | // none/b | ||
| 14 | public 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 0000000..655f4da --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.constructors; | ||
| 12 | |||
| 13 | public 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 0000000..b0fb3e9 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.constructors; | ||
| 12 | |||
| 13 | // none/d extends none/a | ||
| 14 | public 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 0000000..5031405 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.constructors; | ||
| 12 | |||
| 13 | // none/e extends none/d | ||
| 14 | public 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 0000000..4f9c5b0 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.inheritanceTree; | ||
| 12 | |||
| 13 | // none/a | ||
| 14 | public 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 0000000..140d2a8 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.inheritanceTree; | ||
| 12 | |||
| 13 | // none/b extends none/a | ||
| 14 | public 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 0000000..99d149b --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.inheritanceTree; | ||
| 12 | |||
| 13 | // none/c extends none/a | ||
| 14 | public 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 0000000..2e414b7 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.inheritanceTree; | ||
| 12 | |||
| 13 | // none/d extends none/b | ||
| 14 | public 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 0000000..f644439 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.innerClasses; | ||
| 12 | |||
| 13 | public 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 0000000..d78be84 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.innerClasses; | ||
| 12 | |||
| 13 | public 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 0000000..eb03489 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.innerClasses; | ||
| 12 | |||
| 13 | @SuppressWarnings("unused") | ||
| 14 | public 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 0000000..0e9bf82 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.innerClasses; | ||
| 12 | |||
| 13 | public 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 0000000..255434d --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.innerClasses; | ||
| 12 | |||
| 13 | public 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 0000000..7d1dab4 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.innerClasses; | ||
| 12 | |||
| 13 | |||
| 14 | public 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 0000000..bf264fa --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.loneClass; | ||
| 12 | |||
| 13 | public 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 0000000..26acac8 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.translation; | ||
| 12 | |||
| 13 | public 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 0000000..035e329 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.translation; | ||
| 12 | |||
| 13 | public 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 0000000..6026a8d --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.translation; | ||
| 12 | |||
| 13 | public 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 0000000..a1827f9 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.translation; | ||
| 12 | |||
| 13 | import java.util.ArrayList; | ||
| 14 | import java.util.List; | ||
| 15 | |||
| 16 | public 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 0000000..769eb70 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.translation; | ||
| 12 | |||
| 13 | import java.util.Iterator; | ||
| 14 | |||
| 15 | |||
| 16 | public 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 0000000..32c246c --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.translation; | ||
| 12 | |||
| 13 | public 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 0000000..a2e0daf --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.translation; | ||
| 12 | |||
| 13 | |||
| 14 | public 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 0000000..1b718a5 --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.translation; | ||
| 12 | |||
| 13 | |||
| 14 | public 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 0000000..3490f9d --- /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 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.inputs.translation; | ||
| 12 | |||
| 13 | import java.util.List; | ||
| 14 | import java.util.Map; | ||
| 15 | |||
| 16 | |||
| 17 | public 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 0000000..db78c19 --- /dev/null +++ b/test/cuchaz/enigma/resources/translation.mappings | |||
| @@ -0,0 +1,41 @@ | |||
| 1 | CLASS 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 | ||
| 9 | CLASS 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 | ||
| 14 | CLASS 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 | ||
| 20 | CLASS 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 | ||
| 31 | CLASS none/h | ||
| 32 | CLASS 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; | ||