diff options
| author | 2015-02-08 21:29:25 -0500 | |
|---|---|---|
| committer | 2015-02-08 21:29:25 -0500 | |
| commit | ed9b5cdfc648e86fd463bfa8d86b94c41671e14c (patch) | |
| tree | 2619bbc7e04dfa3b82f8dfd3b1d31f529766cd4b | |
| download | enigma-fork-ed9b5cdfc648e86fd463bfa8d86b94c41671e14c.tar.gz enigma-fork-ed9b5cdfc648e86fd463bfa8d86b94c41671e14c.tar.xz enigma-fork-ed9b5cdfc648e86fd463bfa8d86b94c41671e14c.zip | |
switch all classes to new signature/type system
132 files changed, 15543 insertions, 0 deletions
diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..16e0b31 --- /dev/null +++ b/.classpath | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | <classpath> | ||
| 3 | <classpathentry kind="src" path="src"/> | ||
| 4 | <classpathentry kind="src" path="conf"/> | ||
| 5 | <classpathentry kind="src" path="test"/> | ||
| 6 | <classpathentry exported="true" kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> | ||
| 7 | <classpathentry combineaccessrules="false" kind="src" path="/procyon"/> | ||
| 8 | <classpathentry kind="lib" path="lib/deps.jar"/> | ||
| 9 | <classpathentry kind="lib" path="lib/test-deps.jar"/> | ||
| 10 | <classpathentry kind="output" path="bin"/> | ||
| 11 | </classpath> | ||
diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..659df81 --- /dev/null +++ b/.hgignore | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | |||
| 2 | syntax: glob | ||
| 3 | bin | ||
| 4 | lib | ||
| 5 | build | ||
| 6 | data | ||
| 7 | input | ||
| 8 | ivy | ||
| 9 | *.pyc \ No newline at end of file | ||
diff --git a/.project b/.project new file mode 100644 index 0000000..08dff6c --- /dev/null +++ b/.project | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | <projectDescription> | ||
| 3 | <name>Enigma</name> | ||
| 4 | <comment></comment> | ||
| 5 | <projects> | ||
| 6 | </projects> | ||
| 7 | <buildSpec> | ||
| 8 | <buildCommand> | ||
| 9 | <name>org.eclipse.jdt.core.javabuilder</name> | ||
| 10 | <arguments> | ||
| 11 | </arguments> | ||
| 12 | </buildCommand> | ||
| 13 | </buildSpec> | ||
| 14 | <natures> | ||
| 15 | <nature>org.eclipse.jdt.core.javanature</nature> | ||
| 16 | </natures> | ||
| 17 | </projectDescription> | ||
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..b5d234f --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | # | ||
| 2 | #Mon Sep 22 22:51:23 EDT 2014 | ||
| 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.py b/build.py new file mode 100644 index 0000000..4729498 --- /dev/null +++ b/build.py | |||
| @@ -0,0 +1,120 @@ | |||
| 1 | |||
| 2 | import os | ||
| 3 | import sys | ||
| 4 | |||
| 5 | # settings | ||
| 6 | PathSsjb = "../ssjb" | ||
| 7 | Author = "Cuchaz" | ||
| 8 | |||
| 9 | DirBin = "bin" | ||
| 10 | DirLib = "lib" | ||
| 11 | DirBuild = "build" | ||
| 12 | PathLocalMavenRepo = "../maven" | ||
| 13 | |||
| 14 | |||
| 15 | # import ssjb | ||
| 16 | sys.path.insert(0, PathSsjb) | ||
| 17 | import ssjb | ||
| 18 | import ssjb.ivy | ||
| 19 | |||
| 20 | |||
| 21 | ArtifactStandalone = ssjb.ivy.Dep("cuchaz:enigma:0.6b") | ||
| 22 | ArtifactLib = ssjb.ivy.Dep("cuchaz:enigma-lib:0.6b") | ||
| 23 | |||
| 24 | # dependencies | ||
| 25 | ExtraRepos = [ | ||
| 26 | "http://maven.cuchazinteractive.com" | ||
| 27 | ] | ||
| 28 | LibDeps = [ | ||
| 29 | ssjb.ivy.Dep("com.google.guava:guava:17.0"), | ||
| 30 | ssjb.ivy.Dep("org.javassist:javassist:3.18.1-GA"), | ||
| 31 | ssjb.ivy.Dep("org.bitbucket.mstrobel:procyon-decompiler:0.5.28-enigma") | ||
| 32 | ] | ||
| 33 | StandaloneDeps = LibDeps + [ | ||
| 34 | ssjb.ivy.Dep("de.sciss:jsyntaxpane:1.0.0") | ||
| 35 | ] | ||
| 36 | ProguardDep = ssjb.ivy.Dep("net.sf.proguard:proguard-base:5.1") | ||
| 37 | TestDeps = [ | ||
| 38 | ssjb.ivy.Dep("junit:junit:4.12"), | ||
| 39 | ssjb.ivy.Dep("org.hamcrest:hamcrest-all:1.3") | ||
| 40 | ] | ||
| 41 | |||
| 42 | # functions | ||
| 43 | |||
| 44 | def buildTestJar(name, glob): | ||
| 45 | |||
| 46 | pathJar = os.path.join(DirBuild, "%s.jar" % name) | ||
| 47 | pathObfJar = os.path.join(DirBuild, "%s.obf.jar" % name) | ||
| 48 | |||
| 49 | # build the deobf jar | ||
| 50 | with ssjb.file.TempDir("tmp") as dirTemp: | ||
| 51 | ssjb.file.copyTree(dirTemp, DirBin, ssjb.file.find(DirBin, "cuchaz/enigma/inputs/Keep.class")) | ||
| 52 | ssjb.file.copyTree(dirTemp, DirBin, ssjb.file.find(DirBin, glob)) | ||
| 53 | ssjb.jar.makeJar(pathJar, dirTemp) | ||
| 54 | |||
| 55 | # build the obf jar | ||
| 56 | ssjb.callJavaJar( | ||
| 57 | os.path.join(DirLib, "proguard.jar"), | ||
| 58 | ["@proguard.conf", "-injars", pathJar, "-outjars", pathObfJar] | ||
| 59 | ) | ||
| 60 | |||
| 61 | |||
| 62 | def applyReadme(dirTemp): | ||
| 63 | ssjb.file.copy(dirTemp, "license.APL2.txt") | ||
| 64 | ssjb.file.copy(dirTemp, "license.GPL3.txt") | ||
| 65 | ssjb.file.copy(dirTemp, "readme.txt") | ||
| 66 | |||
| 67 | |||
| 68 | def buildStandaloneJar(dirOut): | ||
| 69 | with ssjb.file.TempDir(os.path.join(dirOut, "tmp")) as dirTemp: | ||
| 70 | ssjb.file.copyTree(dirTemp, DirBin, ssjb.file.find(DirBin)) | ||
| 71 | for path in ssjb.ivy.getJarPaths(StandaloneDeps, ExtraRepos): | ||
| 72 | ssjb.jar.unpackJar(dirTemp, path) | ||
| 73 | ssjb.file.delete(os.path.join(dirTemp, "LICENSE.txt")) | ||
| 74 | ssjb.file.delete(os.path.join(dirTemp, "META-INF/maven")) | ||
| 75 | applyReadme(dirTemp) | ||
| 76 | manifest = ssjb.jar.buildManifest( | ||
| 77 | ArtifactStandalone.artifactId, | ||
| 78 | ArtifactStandalone.version, | ||
| 79 | Author, | ||
| 80 | "cuchaz.enigma.Main" | ||
| 81 | ) | ||
| 82 | pathJar = os.path.join(DirBuild, "%s.jar" % ArtifactStandalone.getName()) | ||
| 83 | ssjb.jar.makeJar(pathJar, dirTemp, manifest=manifest) | ||
| 84 | ssjb.ivy.deployJarToLocalMavenRepo(PathLocalMavenRepo, pathJar, ArtifactStandalone) | ||
| 85 | |||
| 86 | def buildLibJar(dirOut): | ||
| 87 | with ssjb.file.TempDir(os.path.join(dirOut, "tmp")) as dirTemp: | ||
| 88 | ssjb.file.copyTree(dirTemp, DirBin, ssjb.file.find(DirBin)) | ||
| 89 | applyReadme(dirTemp) | ||
| 90 | pathJar = os.path.join(DirBuild, "%s.jar" % ArtifactLib.getName()) | ||
| 91 | ssjb.jar.makeJar(pathJar, dirTemp) | ||
| 92 | ssjb.ivy.deployJarToLocalMavenRepo(PathLocalMavenRepo, pathJar, ArtifactLib, deps=LibDeps) | ||
| 93 | |||
| 94 | |||
| 95 | # tasks | ||
| 96 | |||
| 97 | def taskGetDeps(): | ||
| 98 | ssjb.file.mkdir(DirLib) | ||
| 99 | ssjb.ivy.makeLibsJar(os.path.join(DirLib, "deps.jar"), StandaloneDeps, extraRepos=ExtraRepos) | ||
| 100 | ssjb.ivy.makeLibsJar(os.path.join(DirLib, "test-deps.jar"), TestDeps) | ||
| 101 | ssjb.ivy.makeJar(os.path.join(DirLib, "proguard.jar"), ProguardDep) | ||
| 102 | |||
| 103 | def taskBuildTestJars(): | ||
| 104 | buildTestJar("testLoneClass", "cuchaz/enigma/inputs/loneClass/*.class") | ||
| 105 | buildTestJar("testConstructors", "cuchaz/enigma/inputs/constructors/*.class") | ||
| 106 | buildTestJar("testInheritanceTree", "cuchaz/enigma/inputs/inheritanceTree/*.class") | ||
| 107 | buildTestJar("testInnerClasses", "cuchaz/enigma/inputs/innerClasses/*.class") | ||
| 108 | buildTestJar("testTranslation", "cuchaz/enigma/inputs/translation/*.class") | ||
| 109 | |||
| 110 | def taskBuild(): | ||
| 111 | ssjb.file.delete(DirBuild) | ||
| 112 | ssjb.file.mkdir(DirBuild) | ||
| 113 | buildStandaloneJar(DirBuild) | ||
| 114 | buildLibJar(DirBuild) | ||
| 115 | |||
| 116 | ssjb.registerTask("getDeps", taskGetDeps) | ||
| 117 | ssjb.registerTask("buildTestJars", taskBuildTestJars) | ||
| 118 | ssjb.registerTask("build", taskBuild) | ||
| 119 | ssjb.run() | ||
| 120 | |||
diff --git a/conf/about.html b/conf/about.html new file mode 100644 index 0000000..b75c1bf --- /dev/null +++ b/conf/about.html | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | <html> | ||
| 2 | <h1>%s</h1> | ||
| 3 | <p>A tool for debofuscation of Java code</p> | ||
| 4 | <p> | ||
| 5 | <p>Version: %s</p> | ||
| 6 | </html> \ No newline at end of file | ||
diff --git a/license.APL2.txt b/license.APL2.txt new file mode 100644 index 0000000..a453e43 --- /dev/null +++ b/license.APL2.txt | |||
| @@ -0,0 +1,55 @@ | |||
| 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.GPL3.txt b/license.GPL3.txt new file mode 100644 index 0000000..20d40b6 --- /dev/null +++ b/license.GPL3.txt | |||
| @@ -0,0 +1,674 @@ | |||
| 1 | GNU GENERAL PUBLIC LICENSE | ||
| 2 | Version 3, 29 June 2007 | ||
| 3 | |||
| 4 | Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> | ||
| 5 | Everyone is permitted to copy and distribute verbatim copies | ||
| 6 | of this license document, but changing it is not allowed. | ||
| 7 | |||
| 8 | Preamble | ||
| 9 | |||
| 10 | The GNU General Public License is a free, copyleft license for | ||
| 11 | software and other kinds of works. | ||
| 12 | |||
| 13 | The licenses for most software and other practical works are designed | ||
| 14 | to take away your freedom to share and change the works. By contrast, | ||
| 15 | the GNU General Public License is intended to guarantee your freedom to | ||
| 16 | share and change all versions of a program--to make sure it remains free | ||
| 17 | software for all its users. We, the Free Software Foundation, use the | ||
| 18 | GNU General Public License for most of our software; it applies also to | ||
| 19 | any other work released this way by its authors. You can apply it to | ||
| 20 | your programs, too. | ||
| 21 | |||
| 22 | When we speak of free software, we are referring to freedom, not | ||
| 23 | price. Our General Public Licenses are designed to make sure that you | ||
| 24 | have the freedom to distribute copies of free software (and charge for | ||
| 25 | them if you wish), that you receive source code or can get it if you | ||
| 26 | want it, that you can change the software or use pieces of it in new | ||
| 27 | free programs, and that you know you can do these things. | ||
| 28 | |||
| 29 | To protect your rights, we need to prevent others from denying you | ||
| 30 | these rights or asking you to surrender the rights. Therefore, you have | ||
| 31 | certain responsibilities if you distribute copies of the software, or if | ||
| 32 | you modify it: responsibilities to respect the freedom of others. | ||
| 33 | |||
| 34 | For example, if you distribute copies of such a program, whether | ||
| 35 | gratis or for a fee, you must pass on to the recipients the same | ||
| 36 | freedoms that you received. You must make sure that they, too, receive | ||
| 37 | or can get the source code. And you must show them these terms so they | ||
| 38 | know their rights. | ||
| 39 | |||
| 40 | Developers that use the GNU GPL protect your rights with two steps: | ||
| 41 | (1) assert copyright on the software, and (2) offer you this License | ||
| 42 | giving you legal permission to copy, distribute and/or modify it. | ||
| 43 | |||
| 44 | For the developers' and authors' protection, the GPL clearly explains | ||
| 45 | that there is no warranty for this free software. For both users' and | ||
| 46 | authors' sake, the GPL requires that modified versions be marked as | ||
| 47 | changed, so that their problems will not be attributed erroneously to | ||
| 48 | authors of previous versions. | ||
| 49 | |||
| 50 | Some devices are designed to deny users access to install or run | ||
| 51 | modified versions of the software inside them, although the manufacturer | ||
| 52 | can do so. This is fundamentally incompatible with the aim of | ||
| 53 | protecting users' freedom to change the software. The systematic | ||
| 54 | pattern of such abuse occurs in the area of products for individuals to | ||
| 55 | use, which is precisely where it is most unacceptable. Therefore, we | ||
| 56 | have designed this version of the GPL to prohibit the practice for those | ||
| 57 | products. If such problems arise substantially in other domains, we | ||
| 58 | stand ready to extend this provision to those domains in future versions | ||
| 59 | of the GPL, as needed to protect the freedom of users. | ||
| 60 | |||
| 61 | Finally, every program is threatened constantly by software patents. | ||
| 62 | States should not allow patents to restrict development and use of | ||
| 63 | software on general-purpose computers, but in those that do, we wish to | ||
| 64 | avoid the special danger that patents applied to a free program could | ||
| 65 | make it effectively proprietary. To prevent this, the GPL assures that | ||
| 66 | patents cannot be used to render the program non-free. | ||
| 67 | |||
| 68 | The precise terms and conditions for copying, distribution and | ||
| 69 | modification follow. | ||
| 70 | |||
| 71 | TERMS AND CONDITIONS | ||
| 72 | |||
| 73 | 0. Definitions. | ||
| 74 | |||
| 75 | "This License" refers to version 3 of the GNU General Public License. | ||
| 76 | |||
| 77 | "Copyright" also means copyright-like laws that apply to other kinds of | ||
| 78 | works, such as semiconductor masks. | ||
| 79 | |||
| 80 | "The Program" refers to any copyrightable work licensed under this | ||
| 81 | License. Each licensee is addressed as "you". "Licensees" and | ||
| 82 | "recipients" may be individuals or organizations. | ||
| 83 | |||
| 84 | To "modify" a work means to copy from or adapt all or part of the work | ||
| 85 | in a fashion requiring copyright permission, other than the making of an | ||
| 86 | exact copy. The resulting work is called a "modified version" of the | ||
| 87 | earlier work or a work "based on" the earlier work. | ||
| 88 | |||
| 89 | A "covered work" means either the unmodified Program or a work based | ||
| 90 | on the Program. | ||
| 91 | |||
| 92 | To "propagate" a work means to do anything with it that, without | ||
| 93 | permission, would make you directly or secondarily liable for | ||
| 94 | infringement under applicable copyright law, except executing it on a | ||
| 95 | computer or modifying a private copy. Propagation includes copying, | ||
| 96 | distribution (with or without modification), making available to the | ||
| 97 | public, and in some countries other activities as well. | ||
| 98 | |||
| 99 | To "convey" a work means any kind of propagation that enables other | ||
| 100 | parties to make or receive copies. Mere interaction with a user through | ||
| 101 | a computer network, with no transfer of a copy, is not conveying. | ||
| 102 | |||
| 103 | An interactive user interface displays "Appropriate Legal Notices" | ||
| 104 | to the extent that it includes a convenient and prominently visible | ||
| 105 | feature that (1) displays an appropriate copyright notice, and (2) | ||
| 106 | tells the user that there is no warranty for the work (except to the | ||
| 107 | extent that warranties are provided), that licensees may convey the | ||
| 108 | work under this License, and how to view a copy of this License. If | ||
| 109 | the interface presents a list of user commands or options, such as a | ||
| 110 | menu, a prominent item in the list meets this criterion. | ||
| 111 | |||
| 112 | 1. Source Code. | ||
| 113 | |||
| 114 | The "source code" for a work means the preferred form of the work | ||
| 115 | for making modifications to it. "Object code" means any non-source | ||
| 116 | form of a work. | ||
| 117 | |||
| 118 | A "Standard Interface" means an interface that either is an official | ||
| 119 | standard defined by a recognized standards body, or, in the case of | ||
| 120 | interfaces specified for a particular programming language, one that | ||
| 121 | is widely used among developers working in that language. | ||
| 122 | |||
| 123 | The "System Libraries" of an executable work include anything, other | ||
| 124 | than the work as a whole, that (a) is included in the normal form of | ||
| 125 | packaging a Major Component, but which is not part of that Major | ||
| 126 | Component, and (b) serves only to enable use of the work with that | ||
| 127 | Major Component, or to implement a Standard Interface for which an | ||
| 128 | implementation is available to the public in source code form. A | ||
| 129 | "Major Component", in this context, means a major essential component | ||
| 130 | (kernel, window system, and so on) of the specific operating system | ||
| 131 | (if any) on which the executable work runs, or a compiler used to | ||
| 132 | produce the work, or an object code interpreter used to run it. | ||
| 133 | |||
| 134 | The "Corresponding Source" for a work in object code form means all | ||
| 135 | the source code needed to generate, install, and (for an executable | ||
| 136 | work) run the object code and to modify the work, including scripts to | ||
| 137 | control those activities. However, it does not include the work's | ||
| 138 | System Libraries, or general-purpose tools or generally available free | ||
| 139 | programs which are used unmodified in performing those activities but | ||
| 140 | which are not part of the work. For example, Corresponding Source | ||
| 141 | includes interface definition files associated with source files for | ||
| 142 | the work, and the source code for shared libraries and dynamically | ||
| 143 | linked subprograms that the work is specifically designed to require, | ||
| 144 | such as by intimate data communication or control flow between those | ||
| 145 | subprograms and other parts of the work. | ||
| 146 | |||
| 147 | The Corresponding Source need not include anything that users | ||
| 148 | can regenerate automatically from other parts of the Corresponding | ||
| 149 | Source. | ||
| 150 | |||
| 151 | The Corresponding Source for a work in source code form is that | ||
| 152 | same work. | ||
| 153 | |||
| 154 | 2. Basic Permissions. | ||
| 155 | |||
| 156 | All rights granted under this License are granted for the term of | ||
| 157 | copyright on the Program, and are irrevocable provided the stated | ||
| 158 | conditions are met. This License explicitly affirms your unlimited | ||
| 159 | permission to run the unmodified Program. The output from running a | ||
| 160 | covered work is covered by this License only if the output, given its | ||
| 161 | content, constitutes a covered work. This License acknowledges your | ||
| 162 | rights of fair use or other equivalent, as provided by copyright law. | ||
| 163 | |||
| 164 | You may make, run and propagate covered works that you do not | ||
| 165 | convey, without conditions so long as your license otherwise remains | ||
| 166 | in force. You may convey covered works to others for the sole purpose | ||
| 167 | of having them make modifications exclusively for you, or provide you | ||
| 168 | with facilities for running those works, provided that you comply with | ||
| 169 | the terms of this License in conveying all material for which you do | ||
| 170 | not control copyright. Those thus making or running the covered works | ||
| 171 | for you must do so exclusively on your behalf, under your direction | ||
| 172 | and control, on terms that prohibit them from making any copies of | ||
| 173 | your copyrighted material outside their relationship with you. | ||
| 174 | |||
| 175 | Conveying under any other circumstances is permitted solely under | ||
| 176 | the conditions stated below. Sublicensing is not allowed; section 10 | ||
| 177 | makes it unnecessary. | ||
| 178 | |||
| 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. | ||
| 180 | |||
| 181 | No covered work shall be deemed part of an effective technological | ||
| 182 | measure under any applicable law fulfilling obligations under article | ||
| 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or | ||
| 184 | similar laws prohibiting or restricting circumvention of such | ||
| 185 | measures. | ||
| 186 | |||
| 187 | When you convey a covered work, you waive any legal power to forbid | ||
| 188 | circumvention of technological measures to the extent such circumvention | ||
| 189 | is effected by exercising rights under this License with respect to | ||
| 190 | the covered work, and you disclaim any intention to limit operation or | ||
| 191 | modification of the work as a means of enforcing, against the work's | ||
| 192 | users, your or third parties' legal rights to forbid circumvention of | ||
| 193 | technological measures. | ||
| 194 | |||
| 195 | 4. Conveying Verbatim Copies. | ||
| 196 | |||
| 197 | You may convey verbatim copies of the Program's source code as you | ||
| 198 | receive it, in any medium, provided that you conspicuously and | ||
| 199 | appropriately publish on each copy an appropriate copyright notice; | ||
| 200 | keep intact all notices stating that this License and any | ||
| 201 | non-permissive terms added in accord with section 7 apply to the code; | ||
| 202 | keep intact all notices of the absence of any warranty; and give all | ||
| 203 | recipients a copy of this License along with the Program. | ||
| 204 | |||
| 205 | You may charge any price or no price for each copy that you convey, | ||
| 206 | and you may offer support or warranty protection for a fee. | ||
| 207 | |||
| 208 | 5. Conveying Modified Source Versions. | ||
| 209 | |||
| 210 | You may convey a work based on the Program, or the modifications to | ||
| 211 | produce it from the Program, in the form of source code under the | ||
| 212 | terms of section 4, provided that you also meet all of these conditions: | ||
| 213 | |||
| 214 | a) The work must carry prominent notices stating that you modified | ||
| 215 | it, and giving a relevant date. | ||
| 216 | |||
| 217 | b) The work must carry prominent notices stating that it is | ||
| 218 | released under this License and any conditions added under section | ||
| 219 | 7. This requirement modifies the requirement in section 4 to | ||
| 220 | "keep intact all notices". | ||
| 221 | |||
| 222 | c) You must license the entire work, as a whole, under this | ||
| 223 | License to anyone who comes into possession of a copy. This | ||
| 224 | License will therefore apply, along with any applicable section 7 | ||
| 225 | additional terms, to the whole of the work, and all its parts, | ||
| 226 | regardless of how they are packaged. This License gives no | ||
| 227 | permission to license the work in any other way, but it does not | ||
| 228 | invalidate such permission if you have separately received it. | ||
| 229 | |||
| 230 | d) If the work has interactive user interfaces, each must display | ||
| 231 | Appropriate Legal Notices; however, if the Program has interactive | ||
| 232 | interfaces that do not display Appropriate Legal Notices, your | ||
| 233 | work need not make them do so. | ||
| 234 | |||
| 235 | A compilation of a covered work with other separate and independent | ||
| 236 | works, which are not by their nature extensions of the covered work, | ||
| 237 | and which are not combined with it such as to form a larger program, | ||
| 238 | in or on a volume of a storage or distribution medium, is called an | ||
| 239 | "aggregate" if the compilation and its resulting copyright are not | ||
| 240 | used to limit the access or legal rights of the compilation's users | ||
| 241 | beyond what the individual works permit. Inclusion of a covered work | ||
| 242 | in an aggregate does not cause this License to apply to the other | ||
| 243 | parts of the aggregate. | ||
| 244 | |||
| 245 | 6. Conveying Non-Source Forms. | ||
| 246 | |||
| 247 | You may convey a covered work in object code form under the terms | ||
| 248 | of sections 4 and 5, provided that you also convey the | ||
| 249 | machine-readable Corresponding Source under the terms of this License, | ||
| 250 | in one of these ways: | ||
| 251 | |||
| 252 | a) Convey the object code in, or embodied in, a physical product | ||
| 253 | (including a physical distribution medium), accompanied by the | ||
| 254 | Corresponding Source fixed on a durable physical medium | ||
| 255 | customarily used for software interchange. | ||
| 256 | |||
| 257 | b) Convey the object code in, or embodied in, a physical product | ||
| 258 | (including a physical distribution medium), accompanied by a | ||
| 259 | written offer, valid for at least three years and valid for as | ||
| 260 | long as you offer spare parts or customer support for that product | ||
| 261 | model, to give anyone who possesses the object code either (1) a | ||
| 262 | copy of the Corresponding Source for all the software in the | ||
| 263 | product that is covered by this License, on a durable physical | ||
| 264 | medium customarily used for software interchange, for a price no | ||
| 265 | more than your reasonable cost of physically performing this | ||
| 266 | conveying of source, or (2) access to copy the | ||
| 267 | Corresponding Source from a network server at no charge. | ||
| 268 | |||
| 269 | c) Convey individual copies of the object code with a copy of the | ||
| 270 | written offer to provide the Corresponding Source. This | ||
| 271 | alternative is allowed only occasionally and noncommercially, and | ||
| 272 | only if you received the object code with such an offer, in accord | ||
| 273 | with subsection 6b. | ||
| 274 | |||
| 275 | d) Convey the object code by offering access from a designated | ||
| 276 | place (gratis or for a charge), and offer equivalent access to the | ||
| 277 | Corresponding Source in the same way through the same place at no | ||
| 278 | further charge. You need not require recipients to copy the | ||
| 279 | Corresponding Source along with the object code. If the place to | ||
| 280 | copy the object code is a network server, the Corresponding Source | ||
| 281 | may be on a different server (operated by you or a third party) | ||
| 282 | that supports equivalent copying facilities, provided you maintain | ||
| 283 | clear directions next to the object code saying where to find the | ||
| 284 | Corresponding Source. Regardless of what server hosts the | ||
| 285 | Corresponding Source, you remain obligated to ensure that it is | ||
| 286 | available for as long as needed to satisfy these requirements. | ||
| 287 | |||
| 288 | e) Convey the object code using peer-to-peer transmission, provided | ||
| 289 | you inform other peers where the object code and Corresponding | ||
| 290 | Source of the work are being offered to the general public at no | ||
| 291 | charge under subsection 6d. | ||
| 292 | |||
| 293 | A separable portion of the object code, whose source code is excluded | ||
| 294 | from the Corresponding Source as a System Library, need not be | ||
| 295 | included in conveying the object code work. | ||
| 296 | |||
| 297 | A "User Product" is either (1) a "consumer product", which means any | ||
| 298 | tangible personal property which is normally used for personal, family, | ||
| 299 | or household purposes, or (2) anything designed or sold for incorporation | ||
| 300 | into a dwelling. In determining whether a product is a consumer product, | ||
| 301 | doubtful cases shall be resolved in favor of coverage. For a particular | ||
| 302 | product received by a particular user, "normally used" refers to a | ||
| 303 | typical or common use of that class of product, regardless of the status | ||
| 304 | of the particular user or of the way in which the particular user | ||
| 305 | actually uses, or expects or is expected to use, the product. A product | ||
| 306 | is a consumer product regardless of whether the product has substantial | ||
| 307 | commercial, industrial or non-consumer uses, unless such uses represent | ||
| 308 | the only significant mode of use of the product. | ||
| 309 | |||
| 310 | "Installation Information" for a User Product means any methods, | ||
| 311 | procedures, authorization keys, or other information required to install | ||
| 312 | and execute modified versions of a covered work in that User Product from | ||
| 313 | a modified version of its Corresponding Source. The information must | ||
| 314 | suffice to ensure that the continued functioning of the modified object | ||
| 315 | code is in no case prevented or interfered with solely because | ||
| 316 | modification has been made. | ||
| 317 | |||
| 318 | If you convey an object code work under this section in, or with, or | ||
| 319 | specifically for use in, a User Product, and the conveying occurs as | ||
| 320 | part of a transaction in which the right of possession and use of the | ||
| 321 | User Product is transferred to the recipient in perpetuity or for a | ||
| 322 | fixed term (regardless of how the transaction is characterized), the | ||
| 323 | Corresponding Source conveyed under this section must be accompanied | ||
| 324 | by the Installation Information. But this requirement does not apply | ||
| 325 | if neither you nor any third party retains the ability to install | ||
| 326 | modified object code on the User Product (for example, the work has | ||
| 327 | been installed in ROM). | ||
| 328 | |||
| 329 | The requirement to provide Installation Information does not include a | ||
| 330 | requirement to continue to provide support service, warranty, or updates | ||
| 331 | for a work that has been modified or installed by the recipient, or for | ||
| 332 | the User Product in which it has been modified or installed. Access to a | ||
| 333 | network may be denied when the modification itself materially and | ||
| 334 | adversely affects the operation of the network or violates the rules and | ||
| 335 | protocols for communication across the network. | ||
| 336 | |||
| 337 | Corresponding Source conveyed, and Installation Information provided, | ||
| 338 | in accord with this section must be in a format that is publicly | ||
| 339 | documented (and with an implementation available to the public in | ||
| 340 | source code form), and must require no special password or key for | ||
| 341 | unpacking, reading or copying. | ||
| 342 | |||
| 343 | 7. Additional Terms. | ||
| 344 | |||
| 345 | "Additional permissions" are terms that supplement the terms of this | ||
| 346 | License by making exceptions from one or more of its conditions. | ||
| 347 | Additional permissions that are applicable to the entire Program shall | ||
| 348 | be treated as though they were included in this License, to the extent | ||
| 349 | that they are valid under applicable law. If additional permissions | ||
| 350 | apply only to part of the Program, that part may be used separately | ||
| 351 | under those permissions, but the entire Program remains governed by | ||
| 352 | this License without regard to the additional permissions. | ||
| 353 | |||
| 354 | When you convey a copy of a covered work, you may at your option | ||
| 355 | remove any additional permissions from that copy, or from any part of | ||
| 356 | it. (Additional permissions may be written to require their own | ||
| 357 | removal in certain cases when you modify the work.) You may place | ||
| 358 | additional permissions on material, added by you to a covered work, | ||
| 359 | for which you have or can give appropriate copyright permission. | ||
| 360 | |||
| 361 | Notwithstanding any other provision of this License, for material you | ||
| 362 | add to a covered work, you may (if authorized by the copyright holders of | ||
| 363 | that material) supplement the terms of this License with terms: | ||
| 364 | |||
| 365 | a) Disclaiming warranty or limiting liability differently from the | ||
| 366 | terms of sections 15 and 16 of this License; or | ||
| 367 | |||
| 368 | b) Requiring preservation of specified reasonable legal notices or | ||
| 369 | author attributions in that material or in the Appropriate Legal | ||
| 370 | Notices displayed by works containing it; or | ||
| 371 | |||
| 372 | c) Prohibiting misrepresentation of the origin of that material, or | ||
| 373 | requiring that modified versions of such material be marked in | ||
| 374 | reasonable ways as different from the original version; or | ||
| 375 | |||
| 376 | d) Limiting the use for publicity purposes of names of licensors or | ||
| 377 | authors of the material; or | ||
| 378 | |||
| 379 | e) Declining to grant rights under trademark law for use of some | ||
| 380 | trade names, trademarks, or service marks; or | ||
| 381 | |||
| 382 | f) Requiring indemnification of licensors and authors of that | ||
| 383 | material by anyone who conveys the material (or modified versions of | ||
| 384 | it) with contractual assumptions of liability to the recipient, for | ||
| 385 | any liability that these contractual assumptions directly impose on | ||
| 386 | those licensors and authors. | ||
| 387 | |||
| 388 | All other non-permissive additional terms are considered "further | ||
| 389 | restrictions" within the meaning of section 10. If the Program as you | ||
| 390 | received it, or any part of it, contains a notice stating that it is | ||
| 391 | governed by this License along with a term that is a further | ||
| 392 | restriction, you may remove that term. If a license document contains | ||
| 393 | a further restriction but permits relicensing or conveying under this | ||
| 394 | License, you may add to a covered work material governed by the terms | ||
| 395 | of that license document, provided that the further restriction does | ||
| 396 | not survive such relicensing or conveying. | ||
| 397 | |||
| 398 | If you add terms to a covered work in accord with this section, you | ||
| 399 | must place, in the relevant source files, a statement of the | ||
| 400 | additional terms that apply to those files, or a notice indicating | ||
| 401 | where to find the applicable terms. | ||
| 402 | |||
| 403 | Additional terms, permissive or non-permissive, may be stated in the | ||
| 404 | form of a separately written license, or stated as exceptions; | ||
| 405 | the above requirements apply either way. | ||
| 406 | |||
| 407 | 8. Termination. | ||
| 408 | |||
| 409 | You may not propagate or modify a covered work except as expressly | ||
| 410 | provided under this License. Any attempt otherwise to propagate or | ||
| 411 | modify it is void, and will automatically terminate your rights under | ||
| 412 | this License (including any patent licenses granted under the third | ||
| 413 | paragraph of section 11). | ||
| 414 | |||
| 415 | However, if you cease all violation of this License, then your | ||
| 416 | license from a particular copyright holder is reinstated (a) | ||
| 417 | provisionally, unless and until the copyright holder explicitly and | ||
| 418 | finally terminates your license, and (b) permanently, if the copyright | ||
| 419 | holder fails to notify you of the violation by some reasonable means | ||
| 420 | prior to 60 days after the cessation. | ||
| 421 | |||
| 422 | Moreover, your license from a particular copyright holder is | ||
| 423 | reinstated permanently if the copyright holder notifies you of the | ||
| 424 | violation by some reasonable means, this is the first time you have | ||
| 425 | received notice of violation of this License (for any work) from that | ||
| 426 | copyright holder, and you cure the violation prior to 30 days after | ||
| 427 | your receipt of the notice. | ||
| 428 | |||
| 429 | Termination of your rights under this section does not terminate the | ||
| 430 | licenses of parties who have received copies or rights from you under | ||
| 431 | this License. If your rights have been terminated and not permanently | ||
| 432 | reinstated, you do not qualify to receive new licenses for the same | ||
| 433 | material under section 10. | ||
| 434 | |||
| 435 | 9. Acceptance Not Required for Having Copies. | ||
| 436 | |||
| 437 | You are not required to accept this License in order to receive or | ||
| 438 | run a copy of the Program. Ancillary propagation of a covered work | ||
| 439 | occurring solely as a consequence of using peer-to-peer transmission | ||
| 440 | to receive a copy likewise does not require acceptance. However, | ||
| 441 | nothing other than this License grants you permission to propagate or | ||
| 442 | modify any covered work. These actions infringe copyright if you do | ||
| 443 | not accept this License. Therefore, by modifying or propagating a | ||
| 444 | covered work, you indicate your acceptance of this License to do so. | ||
| 445 | |||
| 446 | 10. Automatic Licensing of Downstream Recipients. | ||
| 447 | |||
| 448 | Each time you convey a covered work, the recipient automatically | ||
| 449 | receives a license from the original licensors, to run, modify and | ||
| 450 | propagate that work, subject to this License. You are not responsible | ||
| 451 | for enforcing compliance by third parties with this License. | ||
| 452 | |||
| 453 | An "entity transaction" is a transaction transferring control of an | ||
| 454 | organization, or substantially all assets of one, or subdividing an | ||
| 455 | organization, or merging organizations. If propagation of a covered | ||
| 456 | work results from an entity transaction, each party to that | ||
| 457 | transaction who receives a copy of the work also receives whatever | ||
| 458 | licenses to the work the party's predecessor in interest had or could | ||
| 459 | give under the previous paragraph, plus a right to possession of the | ||
| 460 | Corresponding Source of the work from the predecessor in interest, if | ||
| 461 | the predecessor has it or can get it with reasonable efforts. | ||
| 462 | |||
| 463 | You may not impose any further restrictions on the exercise of the | ||
| 464 | rights granted or affirmed under this License. For example, you may | ||
| 465 | not impose a license fee, royalty, or other charge for exercise of | ||
| 466 | rights granted under this License, and you may not initiate litigation | ||
| 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that | ||
| 468 | any patent claim is infringed by making, using, selling, offering for | ||
| 469 | sale, or importing the Program or any portion of it. | ||
| 470 | |||
| 471 | 11. Patents. | ||
| 472 | |||
| 473 | A "contributor" is a copyright holder who authorizes use under this | ||
| 474 | License of the Program or a work on which the Program is based. The | ||
| 475 | work thus licensed is called the contributor's "contributor version". | ||
| 476 | |||
| 477 | A contributor's "essential patent claims" are all patent claims | ||
| 478 | owned or controlled by the contributor, whether already acquired or | ||
| 479 | hereafter acquired, that would be infringed by some manner, permitted | ||
| 480 | by this License, of making, using, or selling its contributor version, | ||
| 481 | but do not include claims that would be infringed only as a | ||
| 482 | consequence of further modification of the contributor version. For | ||
| 483 | purposes of this definition, "control" includes the right to grant | ||
| 484 | patent sublicenses in a manner consistent with the requirements of | ||
| 485 | this License. | ||
| 486 | |||
| 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free | ||
| 488 | patent license under the contributor's essential patent claims, to | ||
| 489 | make, use, sell, offer for sale, import and otherwise run, modify and | ||
| 490 | propagate the contents of its contributor version. | ||
| 491 | |||
| 492 | In the following three paragraphs, a "patent license" is any express | ||
| 493 | agreement or commitment, however denominated, not to enforce a patent | ||
| 494 | (such as an express permission to practice a patent or covenant not to | ||
| 495 | sue for patent infringement). To "grant" such a patent license to a | ||
| 496 | party means to make such an agreement or commitment not to enforce a | ||
| 497 | patent against the party. | ||
| 498 | |||
| 499 | If you convey a covered work, knowingly relying on a patent license, | ||
| 500 | and the Corresponding Source of the work is not available for anyone | ||
| 501 | to copy, free of charge and under the terms of this License, through a | ||
| 502 | publicly available network server or other readily accessible means, | ||
| 503 | then you must either (1) cause the Corresponding Source to be so | ||
| 504 | available, or (2) arrange to deprive yourself of the benefit of the | ||
| 505 | patent license for this particular work, or (3) arrange, in a manner | ||
| 506 | consistent with the requirements of this License, to extend the patent | ||
| 507 | license to downstream recipients. "Knowingly relying" means you have | ||
| 508 | actual knowledge that, but for the patent license, your conveying the | ||
| 509 | covered work in a country, or your recipient's use of the covered work | ||
| 510 | in a country, would infringe one or more identifiable patents in that | ||
| 511 | country that you have reason to believe are valid. | ||
| 512 | |||
| 513 | If, pursuant to or in connection with a single transaction or | ||
| 514 | arrangement, you convey, or propagate by procuring conveyance of, a | ||
| 515 | covered work, and grant a patent license to some of the parties | ||
| 516 | receiving the covered work authorizing them to use, propagate, modify | ||
| 517 | or convey a specific copy of the covered work, then the patent license | ||
| 518 | you grant is automatically extended to all recipients of the covered | ||
| 519 | work and works based on it. | ||
| 520 | |||
| 521 | A patent license is "discriminatory" if it does not include within | ||
| 522 | the scope of its coverage, prohibits the exercise of, or is | ||
| 523 | conditioned on the non-exercise of one or more of the rights that are | ||
| 524 | specifically granted under this License. You may not convey a covered | ||
| 525 | work if you are a party to an arrangement with a third party that is | ||
| 526 | in the business of distributing software, under which you make payment | ||
| 527 | to the third party based on the extent of your activity of conveying | ||
| 528 | the work, and under which the third party grants, to any of the | ||
| 529 | parties who would receive the covered work from you, a discriminatory | ||
| 530 | patent license (a) in connection with copies of the covered work | ||
| 531 | conveyed by you (or copies made from those copies), or (b) primarily | ||
| 532 | for and in connection with specific products or compilations that | ||
| 533 | contain the covered work, unless you entered into that arrangement, | ||
| 534 | or that patent license was granted, prior to 28 March 2007. | ||
| 535 | |||
| 536 | Nothing in this License shall be construed as excluding or limiting | ||
| 537 | any implied license or other defenses to infringement that may | ||
| 538 | otherwise be available to you under applicable patent law. | ||
| 539 | |||
| 540 | 12. No Surrender of Others' Freedom. | ||
| 541 | |||
| 542 | If conditions are imposed on you (whether by court order, agreement or | ||
| 543 | otherwise) that contradict the conditions of this License, they do not | ||
| 544 | excuse you from the conditions of this License. If you cannot convey a | ||
| 545 | covered work so as to satisfy simultaneously your obligations under this | ||
| 546 | License and any other pertinent obligations, then as a consequence you may | ||
| 547 | not convey it at all. For example, if you agree to terms that obligate you | ||
| 548 | to collect a royalty for further conveying from those to whom you convey | ||
| 549 | the Program, the only way you could satisfy both those terms and this | ||
| 550 | License would be to refrain entirely from conveying the Program. | ||
| 551 | |||
| 552 | 13. Use with the GNU Affero General Public License. | ||
| 553 | |||
| 554 | Notwithstanding any other provision of this License, you have | ||
| 555 | permission to link or combine any covered work with a work licensed | ||
| 556 | under version 3 of the GNU Affero General Public License into a single | ||
| 557 | combined work, and to convey the resulting work. The terms of this | ||
| 558 | License will continue to apply to the part which is the covered work, | ||
| 559 | but the special requirements of the GNU Affero General Public License, | ||
| 560 | section 13, concerning interaction through a network will apply to the | ||
| 561 | combination as such. | ||
| 562 | |||
| 563 | 14. Revised Versions of this License. | ||
| 564 | |||
| 565 | The Free Software Foundation may publish revised and/or new versions of | ||
| 566 | the GNU General Public License from time to time. Such new versions will | ||
| 567 | be similar in spirit to the present version, but may differ in detail to | ||
| 568 | address new problems or concerns. | ||
| 569 | |||
| 570 | Each version is given a distinguishing version number. If the | ||
| 571 | Program specifies that a certain numbered version of the GNU General | ||
| 572 | Public License "or any later version" applies to it, you have the | ||
| 573 | option of following the terms and conditions either of that numbered | ||
| 574 | version or of any later version published by the Free Software | ||
| 575 | Foundation. If the Program does not specify a version number of the | ||
| 576 | GNU General Public License, you may choose any version ever published | ||
| 577 | by the Free Software Foundation. | ||
| 578 | |||
| 579 | If the Program specifies that a proxy can decide which future | ||
| 580 | versions of the GNU General Public License can be used, that proxy's | ||
| 581 | public statement of acceptance of a version permanently authorizes you | ||
| 582 | to choose that version for the Program. | ||
| 583 | |||
| 584 | Later license versions may give you additional or different | ||
| 585 | permissions. However, no additional obligations are imposed on any | ||
| 586 | author or copyright holder as a result of your choosing to follow a | ||
| 587 | later version. | ||
| 588 | |||
| 589 | 15. Disclaimer of Warranty. | ||
| 590 | |||
| 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY | ||
| 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT | ||
| 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY | ||
| 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, | ||
| 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | ||
| 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM | ||
| 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF | ||
| 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. | ||
| 599 | |||
| 600 | 16. Limitation of Liability. | ||
| 601 | |||
| 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | ||
| 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS | ||
| 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY | ||
| 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE | ||
| 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF | ||
| 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD | ||
| 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), | ||
| 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF | ||
| 610 | SUCH DAMAGES. | ||
| 611 | |||
| 612 | 17. Interpretation of Sections 15 and 16. | ||
| 613 | |||
| 614 | If the disclaimer of warranty and limitation of liability provided | ||
| 615 | above cannot be given local legal effect according to their terms, | ||
| 616 | reviewing courts shall apply local law that most closely approximates | ||
| 617 | an absolute waiver of all civil liability in connection with the | ||
| 618 | Program, unless a warranty or assumption of liability accompanies a | ||
| 619 | copy of the Program in return for a fee. | ||
| 620 | |||
| 621 | END OF TERMS AND CONDITIONS | ||
| 622 | |||
| 623 | How to Apply These Terms to Your New Programs | ||
| 624 | |||
| 625 | If you develop a new program, and you want it to be of the greatest | ||
| 626 | possible use to the public, the best way to achieve this is to make it | ||
| 627 | free software which everyone can redistribute and change under these terms. | ||
| 628 | |||
| 629 | To do so, attach the following notices to the program. It is safest | ||
| 630 | to attach them to the start of each source file to most effectively | ||
| 631 | state the exclusion of warranty; and each file should have at least | ||
| 632 | the "copyright" line and a pointer to where the full notice is found. | ||
| 633 | |||
| 634 | <one line to give the program's name and a brief idea of what it does.> | ||
| 635 | Copyright (C) <year> <name of author> | ||
| 636 | |||
| 637 | This program is free software: you can redistribute it and/or modify | ||
| 638 | it under the terms of the GNU General Public License as published by | ||
| 639 | the Free Software Foundation, either version 3 of the License, or | ||
| 640 | (at your option) any later version. | ||
| 641 | |||
| 642 | This program is distributed in the hope that it will be useful, | ||
| 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 645 | GNU General Public License for more details. | ||
| 646 | |||
| 647 | You should have received a copy of the GNU General Public License | ||
| 648 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| 649 | |||
| 650 | Also add information on how to contact you by electronic and paper mail. | ||
| 651 | |||
| 652 | If the program does terminal interaction, make it output a short | ||
| 653 | notice like this when it starts in an interactive mode: | ||
| 654 | |||
| 655 | <program> Copyright (C) <year> <name of author> | ||
| 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | ||
| 657 | This is free software, and you are welcome to redistribute it | ||
| 658 | under certain conditions; type `show c' for details. | ||
| 659 | |||
| 660 | The hypothetical commands `show w' and `show c' should show the appropriate | ||
| 661 | parts of the General Public License. Of course, your program's commands | ||
| 662 | might be different; for a GUI interface, you would use an "about box". | ||
| 663 | |||
| 664 | You should also get your employer (if you work as a programmer) or school, | ||
| 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. | ||
| 666 | For more information on this, and how to apply and follow the GNU GPL, see | ||
| 667 | <http://www.gnu.org/licenses/>. | ||
| 668 | |||
| 669 | The GNU General Public License does not permit incorporating your program | ||
| 670 | into proprietary programs. If your program is a subroutine library, you | ||
| 671 | may consider it more useful to permit linking proprietary applications with | ||
| 672 | the library. If this is what you want to do, use the GNU Lesser General | ||
| 673 | Public License instead of this License. But first, please read | ||
| 674 | <http://www.gnu.org/philosophy/why-not-lgpl.html>. \ No newline at end of file | ||
diff --git a/proguard.conf b/proguard.conf new file mode 100644 index 0000000..e1f04ae --- /dev/null +++ b/proguard.conf | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | -libraryjars <java.home>/lib/rt.jar | ||
| 2 | -overloadaggressively | ||
| 3 | -repackageclasses | ||
| 4 | -allowaccessmodification | ||
| 5 | -dontoptimize | ||
| 6 | -dontshrink | ||
| 7 | -keep class cuchaz.enigma.inputs.Keep \ No newline at end of file | ||
diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..3844f54 --- /dev/null +++ b/readme.txt | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | |||
| 2 | Enigma v0.6 beta | ||
| 3 | A tool for deobfuscation of Java bytecode | ||
| 4 | |||
| 5 | Copyright Jeff Martin, 2014 | ||
| 6 | |||
| 7 | |||
| 8 | LICENSE | ||
| 9 | |||
| 10 | Enigma is distributed under the GNU 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 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 | ||
diff --git a/src/cuchaz/enigma/CommandMain.java b/src/cuchaz/enigma/CommandMain.java new file mode 100644 index 0000000..1ec2ad2 --- /dev/null +++ b/src/cuchaz/enigma/CommandMain.java | |||
| @@ -0,0 +1,136 @@ | |||
| 1 | package cuchaz.enigma; | ||
| 2 | |||
| 3 | import java.io.File; | ||
| 4 | import java.io.FileReader; | ||
| 5 | import java.util.jar.JarFile; | ||
| 6 | |||
| 7 | import cuchaz.enigma.Deobfuscator.ProgressListener; | ||
| 8 | import cuchaz.enigma.mapping.Mappings; | ||
| 9 | import cuchaz.enigma.mapping.MappingsReader; | ||
| 10 | |||
| 11 | public class CommandMain { | ||
| 12 | |||
| 13 | public static class ConsoleProgressListener implements ProgressListener { | ||
| 14 | |||
| 15 | private static final int ReportTime = 5000; // 5s | ||
| 16 | |||
| 17 | private int m_totalWork; | ||
| 18 | private long m_startTime; | ||
| 19 | private long m_lastReportTime; | ||
| 20 | |||
| 21 | @Override | ||
| 22 | public void init(int totalWork, String title) { | ||
| 23 | m_totalWork = totalWork; | ||
| 24 | m_startTime = System.currentTimeMillis(); | ||
| 25 | m_lastReportTime = m_startTime; | ||
| 26 | System.out.println(title); | ||
| 27 | } | ||
| 28 | |||
| 29 | @Override | ||
| 30 | public void onProgress(int numDone, String message) { | ||
| 31 | |||
| 32 | long now = System.currentTimeMillis(); | ||
| 33 | boolean isLastUpdate = numDone == m_totalWork; | ||
| 34 | boolean shouldReport = isLastUpdate || now - m_lastReportTime > ReportTime; | ||
| 35 | |||
| 36 | if (shouldReport) { | ||
| 37 | int percent = numDone*100/m_totalWork; | ||
| 38 | System.out.println(String.format("\tProgress: %3d%%", percent)); | ||
| 39 | m_lastReportTime = now; | ||
| 40 | } | ||
| 41 | if (isLastUpdate) { | ||
| 42 | double elapsedSeconds = (now - m_startTime)/1000; | ||
| 43 | System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds)); | ||
| 44 | } | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | public static void main(String[] args) | ||
| 49 | throws Exception { | ||
| 50 | |||
| 51 | try { | ||
| 52 | |||
| 53 | // process the command | ||
| 54 | String command = getArg(args, 0, "command"); | ||
| 55 | if (command.equalsIgnoreCase("deobfuscate")) { | ||
| 56 | deobfuscate(args); | ||
| 57 | } else if(command.equalsIgnoreCase("decompile")) { | ||
| 58 | decompile(args); | ||
| 59 | } else { | ||
| 60 | throw new IllegalArgumentException("Command not recognized: " + command); | ||
| 61 | } | ||
| 62 | } catch (IllegalArgumentException ex) { | ||
| 63 | System.out.println(ex.getMessage()); | ||
| 64 | printHelp(); | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | private static void printHelp() { | ||
| 69 | System.out.println(String.format("%s - %s", Constants.Name, Constants.Version)); | ||
| 70 | System.out.println("Usage:"); | ||
| 71 | System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain <command>"); | ||
| 72 | System.out.println("\twhere <command> is one of:"); | ||
| 73 | System.out.println("\t\tdeobfuscate <mappings file> <in jar> <out jar>"); | ||
| 74 | System.out.println("\t\tdecompile <mappings file> <in jar> <out folder>"); | ||
| 75 | } | ||
| 76 | |||
| 77 | private static void decompile(String[] args) | ||
| 78 | throws Exception { | ||
| 79 | File fileMappings = getReadableFile(getArg(args, 1, "mappings file")); | ||
| 80 | File fileJarIn = getReadableFile(getArg(args, 2, "in jar")); | ||
| 81 | File fileJarOut = getWritableFolder(getArg(args, 3, "out folder")); | ||
| 82 | Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); | ||
| 83 | deobfuscator.writeSources(fileJarOut, new ConsoleProgressListener()); | ||
| 84 | } | ||
| 85 | |||
| 86 | private static void deobfuscate(String[] args) | ||
| 87 | throws Exception { | ||
| 88 | File fileMappings = getReadableFile(getArg(args, 1, "mappings file")); | ||
| 89 | File fileJarIn = getReadableFile(getArg(args, 2, "in jar")); | ||
| 90 | File fileJarOut = getWritableFile(getArg(args, 3, "out jar")); | ||
| 91 | Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); | ||
| 92 | deobfuscator.writeJar(fileJarOut, new ConsoleProgressListener()); | ||
| 93 | } | ||
| 94 | |||
| 95 | private static Deobfuscator getDeobfuscator(File fileMappings, JarFile jar) | ||
| 96 | throws Exception { | ||
| 97 | System.out.println("Reading mappings..."); | ||
| 98 | Mappings mappings = new MappingsReader().read(new FileReader(fileMappings)); | ||
| 99 | System.out.println("Reading jar..."); | ||
| 100 | Deobfuscator deobfuscator = new Deobfuscator(jar); | ||
| 101 | deobfuscator.setMappings(mappings); | ||
| 102 | return deobfuscator; | ||
| 103 | } | ||
| 104 | |||
| 105 | private static String getArg(String[] args, int i, String name) { | ||
| 106 | if (i >= args.length) { | ||
| 107 | throw new IllegalArgumentException(name + " is required"); | ||
| 108 | } | ||
| 109 | return args[i]; | ||
| 110 | } | ||
| 111 | |||
| 112 | private static File getWritableFile(String path) { | ||
| 113 | File file = new File(path).getAbsoluteFile(); | ||
| 114 | File dir = file.getParentFile(); | ||
| 115 | if (dir == null || !dir.exists()) { | ||
| 116 | throw new IllegalArgumentException("Cannot write to folder: " + file); | ||
| 117 | } | ||
| 118 | return file; | ||
| 119 | } | ||
| 120 | |||
| 121 | private static File getWritableFolder(String path) { | ||
| 122 | File dir = new File(path).getAbsoluteFile(); | ||
| 123 | if (!dir.exists()) { | ||
| 124 | throw new IllegalArgumentException("Cannot write to folder: " + dir); | ||
| 125 | } | ||
| 126 | return dir; | ||
| 127 | } | ||
| 128 | |||
| 129 | private static File getReadableFile(String path) { | ||
| 130 | File file = new File(path).getAbsoluteFile(); | ||
| 131 | if (!file.exists()) { | ||
| 132 | throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath()); | ||
| 133 | } | ||
| 134 | return file; | ||
| 135 | } | ||
| 136 | } | ||
diff --git a/src/cuchaz/enigma/Constants.java b/src/cuchaz/enigma/Constants.java new file mode 100644 index 0000000..a1ba2e9 --- /dev/null +++ b/src/cuchaz/enigma/Constants.java | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | public class Constants { | ||
| 14 | public static final String Name = "Enigma"; | ||
| 15 | public static final String Version = "0.6 beta"; | ||
| 16 | public static final String Url = "http://www.cuchazinteractive.com/enigma"; | ||
| 17 | public static final int MiB = 1024 * 1024; // 1 mebibyte | ||
| 18 | public static final int KiB = 1024; // 1 kebibyte | ||
| 19 | public static final String NonePackage = "none"; | ||
| 20 | } | ||
diff --git a/src/cuchaz/enigma/Deobfuscator.java b/src/cuchaz/enigma/Deobfuscator.java new file mode 100644 index 0000000..5f61686 --- /dev/null +++ b/src/cuchaz/enigma/Deobfuscator.java | |||
| @@ -0,0 +1,539 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin.\ | ||
| 3 | * | ||
| 4 | * All rights reserved. This program and the accompanying materials | ||
| 5 | * are made available under the terms of the GNU Public License v3.0 | ||
| 6 | * which accompanies this distribution, and is available at | ||
| 7 | * http://www.gnu.org/licenses/gpl.html | ||
| 8 | * | ||
| 9 | * Contributors: | ||
| 10 | * Jeff Martin - initial API and implementation | ||
| 11 | ******************************************************************************/ | ||
| 12 | package cuchaz.enigma; | ||
| 13 | |||
| 14 | import java.io.File; | ||
| 15 | import java.io.FileOutputStream; | ||
| 16 | import java.io.FileWriter; | ||
| 17 | import java.io.IOException; | ||
| 18 | import java.io.StringWriter; | ||
| 19 | import java.util.List; | ||
| 20 | import java.util.Map; | ||
| 21 | import java.util.Set; | ||
| 22 | import java.util.jar.JarEntry; | ||
| 23 | import java.util.jar.JarFile; | ||
| 24 | import java.util.jar.JarOutputStream; | ||
| 25 | |||
| 26 | import javassist.CtClass; | ||
| 27 | import javassist.bytecode.Descriptor; | ||
| 28 | |||
| 29 | import com.google.common.collect.Lists; | ||
| 30 | import com.google.common.collect.Maps; | ||
| 31 | import com.google.common.collect.Sets; | ||
| 32 | import com.strobel.assembler.metadata.MetadataSystem; | ||
| 33 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 34 | import com.strobel.decompiler.DecompilerContext; | ||
| 35 | import com.strobel.decompiler.DecompilerSettings; | ||
| 36 | import com.strobel.decompiler.PlainTextOutput; | ||
| 37 | import com.strobel.decompiler.languages.java.JavaOutputVisitor; | ||
| 38 | import com.strobel.decompiler.languages.java.ast.AstBuilder; | ||
| 39 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | ||
| 40 | import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; | ||
| 41 | |||
| 42 | import cuchaz.enigma.analysis.EntryReference; | ||
| 43 | import cuchaz.enigma.analysis.JarClassIterator; | ||
| 44 | import cuchaz.enigma.analysis.JarIndex; | ||
| 45 | import cuchaz.enigma.analysis.SourceIndex; | ||
| 46 | import cuchaz.enigma.analysis.SourceIndexVisitor; | ||
| 47 | import cuchaz.enigma.analysis.Token; | ||
| 48 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 49 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 50 | import cuchaz.enigma.mapping.BehaviorEntryFactory; | ||
| 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.MappingsRenamer; | ||
| 59 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 60 | import cuchaz.enigma.mapping.MethodMapping; | ||
| 61 | import cuchaz.enigma.mapping.TranslationDirection; | ||
| 62 | import cuchaz.enigma.mapping.Translator; | ||
| 63 | |||
| 64 | public class Deobfuscator { | ||
| 65 | |||
| 66 | public interface ProgressListener { | ||
| 67 | void init(int totalWork, String title); | ||
| 68 | void onProgress(int numDone, String message); | ||
| 69 | } | ||
| 70 | |||
| 71 | private JarFile m_jar; | ||
| 72 | private DecompilerSettings m_settings; | ||
| 73 | private JarIndex m_jarIndex; | ||
| 74 | private Mappings m_mappings; | ||
| 75 | private MappingsRenamer m_renamer; | ||
| 76 | private Map<TranslationDirection,Translator> m_translatorCache; | ||
| 77 | |||
| 78 | public Deobfuscator(JarFile jar) throws IOException { | ||
| 79 | m_jar = jar; | ||
| 80 | |||
| 81 | // build the jar index | ||
| 82 | m_jarIndex = new JarIndex(); | ||
| 83 | m_jarIndex.indexJar(m_jar, true); | ||
| 84 | |||
| 85 | // config the decompiler | ||
| 86 | m_settings = DecompilerSettings.javaDefaults(); | ||
| 87 | m_settings.setMergeVariables(true); | ||
| 88 | m_settings.setForceExplicitImports(true); | ||
| 89 | m_settings.setForceExplicitTypeArguments(true); | ||
| 90 | // DEBUG | ||
| 91 | //m_settings.setShowSyntheticMembers(true); | ||
| 92 | |||
| 93 | // init defaults | ||
| 94 | m_translatorCache = Maps.newTreeMap(); | ||
| 95 | |||
| 96 | // init mappings | ||
| 97 | setMappings(new Mappings()); | ||
| 98 | } | ||
| 99 | |||
| 100 | public String getJarName() { | ||
| 101 | return m_jar.getName(); | ||
| 102 | } | ||
| 103 | |||
| 104 | public JarIndex getJarIndex() { | ||
| 105 | return m_jarIndex; | ||
| 106 | } | ||
| 107 | |||
| 108 | public Mappings getMappings() { | ||
| 109 | return m_mappings; | ||
| 110 | } | ||
| 111 | |||
| 112 | public void setMappings(Mappings val) { | ||
| 113 | if (val == null) { | ||
| 114 | val = new Mappings(); | ||
| 115 | } | ||
| 116 | |||
| 117 | // pass 1: look for any classes that got moved to inner classes | ||
| 118 | Map<String,String> renames = Maps.newHashMap(); | ||
| 119 | for (ClassMapping classMapping : val.classes()) { | ||
| 120 | // make sure we strip the packages off of obfuscated inner classes | ||
| 121 | String innerClassName = new ClassEntry(classMapping.getObfName()).getSimpleName(); | ||
| 122 | String outerClassName = m_jarIndex.getOuterClass(innerClassName); | ||
| 123 | if (outerClassName != null) { | ||
| 124 | // build the composite class name | ||
| 125 | String newName = outerClassName + "$" + innerClassName; | ||
| 126 | |||
| 127 | // add a rename | ||
| 128 | renames.put(classMapping.getObfName(), newName); | ||
| 129 | |||
| 130 | System.out.println(String.format("Converted class mapping %s to %s", classMapping.getObfName(), newName)); | ||
| 131 | } | ||
| 132 | } | ||
| 133 | for (Map.Entry<String,String> entry : renames.entrySet()) { | ||
| 134 | val.renameObfClass(entry.getKey(), entry.getValue()); | ||
| 135 | } | ||
| 136 | |||
| 137 | // pass 2: look for fields/methods that are actually declared in superclasses | ||
| 138 | MappingsRenamer renamer = new MappingsRenamer(m_jarIndex, val); | ||
| 139 | for (ClassMapping classMapping : Lists.newArrayList(val.classes())) { | ||
| 140 | ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfName()); | ||
| 141 | |||
| 142 | // fields | ||
| 143 | for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) { | ||
| 144 | FieldEntry fieldEntry = new FieldEntry(obfClassEntry, fieldMapping.getObfName()); | ||
| 145 | ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(fieldEntry); | ||
| 146 | if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(fieldEntry.getClassEntry())) { | ||
| 147 | boolean wasMoved = renamer.moveFieldToObfClass(classMapping, fieldMapping, resolvedObfClassEntry); | ||
| 148 | if (wasMoved) { | ||
| 149 | System.out.println(String.format("Moved field %s to class %s", fieldEntry, resolvedObfClassEntry)); | ||
| 150 | } else { | ||
| 151 | System.err.println(String.format("WARNING: Would move field %s to class %s but the field was already there. Dropping instead.", fieldEntry, resolvedObfClassEntry)); | ||
| 152 | } | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | // methods | ||
| 157 | for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { | ||
| 158 | // skip constructors | ||
| 159 | if (methodMapping.isConstructor()) { | ||
| 160 | continue; | ||
| 161 | } | ||
| 162 | |||
| 163 | MethodEntry methodEntry = new MethodEntry( | ||
| 164 | obfClassEntry, | ||
| 165 | methodMapping.getObfName(), | ||
| 166 | methodMapping.getObfSignature() | ||
| 167 | ); | ||
| 168 | ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(methodEntry); | ||
| 169 | if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(methodEntry.getClassEntry())) { | ||
| 170 | boolean wasMoved = renamer.moveMethodToObfClass(classMapping, methodMapping, resolvedObfClassEntry); | ||
| 171 | if (wasMoved) { | ||
| 172 | System.out.println(String.format("Moved method %s to class %s", methodEntry, resolvedObfClassEntry)); | ||
| 173 | } else { | ||
| 174 | System.err.println(String.format("WARNING: Would move method %s to class %s but the method was already there. Dropping instead.", methodEntry, resolvedObfClassEntry)); | ||
| 175 | } | ||
| 176 | } | ||
| 177 | } | ||
| 178 | |||
| 179 | // TODO: recurse to inner classes? | ||
| 180 | } | ||
| 181 | |||
| 182 | // drop mappings that don't match the jar | ||
| 183 | List<ClassEntry> unknownClasses = Lists.newArrayList(); | ||
| 184 | for (ClassMapping classMapping : val.classes()) { | ||
| 185 | checkClassMapping(unknownClasses, classMapping); | ||
| 186 | } | ||
| 187 | if (!unknownClasses.isEmpty()) { | ||
| 188 | throw new Error("Unable to find classes in jar: " + unknownClasses); | ||
| 189 | } | ||
| 190 | |||
| 191 | m_mappings = val; | ||
| 192 | m_renamer = renamer; | ||
| 193 | m_translatorCache.clear(); | ||
| 194 | } | ||
| 195 | |||
| 196 | private void checkClassMapping(List<ClassEntry> unknownClasses, ClassMapping classMapping) { | ||
| 197 | // check the class | ||
| 198 | ClassEntry classEntry = new ClassEntry(classMapping.getObfName()); | ||
| 199 | String outerClassName = m_jarIndex.getOuterClass(classEntry.getSimpleName()); | ||
| 200 | if (outerClassName != null) { | ||
| 201 | classEntry = new ClassEntry(outerClassName + "$" + classMapping.getObfName()); | ||
| 202 | } | ||
| 203 | if (!m_jarIndex.getObfClassEntries().contains(classEntry)) { | ||
| 204 | unknownClasses.add(classEntry); | ||
| 205 | } | ||
| 206 | |||
| 207 | // check the fields | ||
| 208 | for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) { | ||
| 209 | FieldEntry fieldEntry = new FieldEntry(classEntry, fieldMapping.getObfName()); | ||
| 210 | if (!m_jarIndex.containsObfField(fieldEntry)) { | ||
| 211 | System.err.println("WARNING: unable to find field " + fieldEntry + ". dropping mapping."); | ||
| 212 | classMapping.removeFieldMapping(fieldMapping); | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | // check methods | ||
| 217 | for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { | ||
| 218 | BehaviorEntry obfBehaviorEntry = BehaviorEntryFactory.createObf(classEntry, methodMapping); | ||
| 219 | if (!m_jarIndex.containsObfBehavior(obfBehaviorEntry)) { | ||
| 220 | System.err.println("WARNING: unable to find behavior " + obfBehaviorEntry + ". dropping mapping."); | ||
| 221 | classMapping.removeMethodMapping(methodMapping); | ||
| 222 | } | ||
| 223 | } | ||
| 224 | |||
| 225 | // check inner classes | ||
| 226 | for (ClassMapping innerClassMapping : classMapping.innerClasses()) { | ||
| 227 | checkClassMapping(unknownClasses, innerClassMapping); | ||
| 228 | } | ||
| 229 | } | ||
| 230 | |||
| 231 | public Translator getTranslator(TranslationDirection direction) { | ||
| 232 | Translator translator = m_translatorCache.get(direction); | ||
| 233 | if (translator == null) { | ||
| 234 | translator = m_mappings.getTranslator(direction, m_jarIndex.getTranslationIndex()); | ||
| 235 | m_translatorCache.put(direction, translator); | ||
| 236 | } | ||
| 237 | return translator; | ||
| 238 | } | ||
| 239 | |||
| 240 | public void getSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) { | ||
| 241 | for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) { | ||
| 242 | // skip inner classes | ||
| 243 | if (obfClassEntry.isInnerClass()) { | ||
| 244 | continue; | ||
| 245 | } | ||
| 246 | |||
| 247 | // separate the classes | ||
| 248 | ClassEntry deobfClassEntry = deobfuscateEntry(obfClassEntry); | ||
| 249 | if (!deobfClassEntry.equals(obfClassEntry)) { | ||
| 250 | // if the class has a mapping, clearly it's deobfuscated | ||
| 251 | deobfClasses.add(deobfClassEntry); | ||
| 252 | } else if (!obfClassEntry.getPackageName().equals(Constants.NonePackage)) { | ||
| 253 | // also call it deobufscated if it's not in the none package | ||
| 254 | deobfClasses.add(obfClassEntry); | ||
| 255 | } else { | ||
| 256 | // otherwise, assume it's still obfuscated | ||
| 257 | obfClasses.add(obfClassEntry); | ||
| 258 | } | ||
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 262 | public CompilationUnit getSourceTree(String obfClassName) { | ||
| 263 | // is this class deobfuscated? | ||
| 264 | // we need to tell the decompiler the deobfuscated name so it doesn't get freaked out | ||
| 265 | // the decompiler only sees the deobfuscated class, so we need to load it by the deobfuscated name | ||
| 266 | String lookupClassName = obfClassName; | ||
| 267 | ClassMapping classMapping = m_mappings.getClassByObf(obfClassName); | ||
| 268 | if (classMapping != null && classMapping.getDeobfName() != null) { | ||
| 269 | lookupClassName = classMapping.getDeobfName(); | ||
| 270 | } | ||
| 271 | |||
| 272 | // is this class even in the jar? | ||
| 273 | if (!m_jarIndex.containsObfClass(new ClassEntry(obfClassName))) { | ||
| 274 | return null; | ||
| 275 | } | ||
| 276 | |||
| 277 | // set the type loader | ||
| 278 | m_settings.setTypeLoader(new TranslatingTypeLoader( | ||
| 279 | m_jar, | ||
| 280 | m_jarIndex, | ||
| 281 | getTranslator(TranslationDirection.Obfuscating), | ||
| 282 | getTranslator(TranslationDirection.Deobfuscating) | ||
| 283 | )); | ||
| 284 | |||
| 285 | // decompile it! | ||
| 286 | TypeDefinition resolvedType = new MetadataSystem(m_settings.getTypeLoader()).lookupType(lookupClassName).resolve(); | ||
| 287 | DecompilerContext context = new DecompilerContext(); | ||
| 288 | context.setCurrentType(resolvedType); | ||
| 289 | context.setSettings(m_settings); | ||
| 290 | AstBuilder builder = new AstBuilder(context); | ||
| 291 | builder.addType(resolvedType); | ||
| 292 | builder.runTransformations(null); | ||
| 293 | return builder.getCompilationUnit(); | ||
| 294 | } | ||
| 295 | |||
| 296 | public SourceIndex getSourceIndex(CompilationUnit sourceTree, String source) { | ||
| 297 | // build the source index | ||
| 298 | SourceIndex index = new SourceIndex(source); | ||
| 299 | sourceTree.acceptVisitor(new SourceIndexVisitor(), index); | ||
| 300 | |||
| 301 | // DEBUG | ||
| 302 | // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null ); | ||
| 303 | |||
| 304 | // resolve all the classes in the source references | ||
| 305 | for (Token token : index.referenceTokens()) { | ||
| 306 | EntryReference<Entry,Entry> deobfReference = index.getDeobfReference(token); | ||
| 307 | |||
| 308 | // get the obfuscated entry | ||
| 309 | Entry obfEntry = obfuscateEntry(deobfReference.entry); | ||
| 310 | |||
| 311 | // try to resolve the class | ||
| 312 | ClassEntry resolvedObfClassEntry = m_jarIndex.getTranslationIndex().resolveEntryClass(obfEntry); | ||
| 313 | if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getClassEntry())) { | ||
| 314 | // change the class of the entry | ||
| 315 | obfEntry = obfEntry.cloneToNewClass(resolvedObfClassEntry); | ||
| 316 | |||
| 317 | // save the new deobfuscated reference | ||
| 318 | deobfReference.entry = deobfuscateEntry(obfEntry); | ||
| 319 | index.replaceDeobfReference(token, deobfReference); | ||
| 320 | } | ||
| 321 | |||
| 322 | // DEBUG | ||
| 323 | // System.out.println( token + " -> " + reference + " -> " + index.getReferenceToken( reference ) ); | ||
| 324 | } | ||
| 325 | |||
| 326 | return index; | ||
| 327 | } | ||
| 328 | |||
| 329 | public String getSource(CompilationUnit sourceTree) { | ||
| 330 | // render the AST into source | ||
| 331 | StringWriter buf = new StringWriter(); | ||
| 332 | sourceTree.acceptVisitor(new InsertParenthesesVisitor(), null); | ||
| 333 | sourceTree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(buf), m_settings), null); | ||
| 334 | return buf.toString(); | ||
| 335 | } | ||
| 336 | |||
| 337 | public void writeSources(File dirOut, ProgressListener progress) throws IOException { | ||
| 338 | // get the classes to decompile | ||
| 339 | Set<ClassEntry> classEntries = Sets.newHashSet(); | ||
| 340 | for (ClassEntry obfClassEntry : m_jarIndex.getObfClassEntries()) { | ||
| 341 | // skip inner classes | ||
| 342 | if (obfClassEntry.isInnerClass()) { | ||
| 343 | continue; | ||
| 344 | } | ||
| 345 | |||
| 346 | classEntries.add(obfClassEntry); | ||
| 347 | } | ||
| 348 | |||
| 349 | if (progress != null) { | ||
| 350 | progress.init(classEntries.size(), "Decompiling classes..."); | ||
| 351 | } | ||
| 352 | |||
| 353 | // DEOBFUSCATE ALL THE THINGS!! @_@ | ||
| 354 | int i = 0; | ||
| 355 | for (ClassEntry obfClassEntry : classEntries) { | ||
| 356 | ClassEntry deobfClassEntry = deobfuscateEntry(new ClassEntry(obfClassEntry)); | ||
| 357 | if (progress != null) { | ||
| 358 | progress.onProgress(i++, deobfClassEntry.toString()); | ||
| 359 | } | ||
| 360 | |||
| 361 | try { | ||
| 362 | // get the source | ||
| 363 | String source = getSource(getSourceTree(obfClassEntry.getName())); | ||
| 364 | |||
| 365 | // write the file | ||
| 366 | File file = new File(dirOut, deobfClassEntry.getName().replace('.', '/') + ".java"); | ||
| 367 | file.getParentFile().mkdirs(); | ||
| 368 | try (FileWriter out = new FileWriter(file)) { | ||
| 369 | out.write(source); | ||
| 370 | } | ||
| 371 | } catch (Throwable t) { | ||
| 372 | throw new Error("Unable to deobfuscate class " + deobfClassEntry.toString() + " (" + obfClassEntry.toString() + ")", t); | ||
| 373 | } | ||
| 374 | } | ||
| 375 | if (progress != null) { | ||
| 376 | progress.onProgress(i, "Done!"); | ||
| 377 | } | ||
| 378 | } | ||
| 379 | |||
| 380 | public void writeJar(File out, ProgressListener progress) { | ||
| 381 | try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) { | ||
| 382 | if (progress != null) { | ||
| 383 | progress.init(JarClassIterator.getClassEntries(m_jar).size(), "Translating classes..."); | ||
| 384 | } | ||
| 385 | |||
| 386 | // prep the loader | ||
| 387 | TranslatingTypeLoader loader = new TranslatingTypeLoader( | ||
| 388 | m_jar, | ||
| 389 | m_jarIndex, | ||
| 390 | getTranslator(TranslationDirection.Obfuscating), | ||
| 391 | getTranslator(TranslationDirection.Deobfuscating) | ||
| 392 | ); | ||
| 393 | |||
| 394 | int i = 0; | ||
| 395 | for (CtClass c : JarClassIterator.classes(m_jar)) { | ||
| 396 | if (progress != null) { | ||
| 397 | progress.onProgress(i++, c.getName()); | ||
| 398 | } | ||
| 399 | |||
| 400 | try { | ||
| 401 | c = loader.transformClass(c); | ||
| 402 | outJar.putNextEntry(new JarEntry(c.getName().replace('.', '/') + ".class")); | ||
| 403 | outJar.write(c.toBytecode()); | ||
| 404 | outJar.closeEntry(); | ||
| 405 | } catch (Throwable t) { | ||
| 406 | throw new Error("Unable to deobfuscate class " + c.getName(), t); | ||
| 407 | } | ||
| 408 | } | ||
| 409 | if (progress != null) { | ||
| 410 | progress.onProgress(i, "Done!"); | ||
| 411 | } | ||
| 412 | |||
| 413 | outJar.close(); | ||
| 414 | } catch (IOException ex) { | ||
| 415 | throw new Error("Unable to write to Jar file!"); | ||
| 416 | } | ||
| 417 | } | ||
| 418 | |||
| 419 | public <T extends Entry> T obfuscateEntry(T deobfEntry) { | ||
| 420 | if (deobfEntry == null) { | ||
| 421 | return null; | ||
| 422 | } | ||
| 423 | return getTranslator(TranslationDirection.Obfuscating).translateEntry(deobfEntry); | ||
| 424 | } | ||
| 425 | |||
| 426 | public <T extends Entry> T deobfuscateEntry(T obfEntry) { | ||
| 427 | if (obfEntry == null) { | ||
| 428 | return null; | ||
| 429 | } | ||
| 430 | return getTranslator(TranslationDirection.Deobfuscating).translateEntry(obfEntry); | ||
| 431 | } | ||
| 432 | |||
| 433 | public <E extends Entry,C extends Entry> EntryReference<E,C> obfuscateReference(EntryReference<E,C> deobfReference) { | ||
| 434 | if (deobfReference == null) { | ||
| 435 | return null; | ||
| 436 | } | ||
| 437 | return new EntryReference<E,C>( | ||
| 438 | obfuscateEntry(deobfReference.entry), | ||
| 439 | obfuscateEntry(deobfReference.context), | ||
| 440 | deobfReference | ||
| 441 | ); | ||
| 442 | } | ||
| 443 | |||
| 444 | public <E extends Entry,C extends Entry> EntryReference<E,C> deobfuscateReference(EntryReference<E,C> obfReference) { | ||
| 445 | if (obfReference == null) { | ||
| 446 | return null; | ||
| 447 | } | ||
| 448 | return new EntryReference<E,C>( | ||
| 449 | deobfuscateEntry(obfReference.entry), | ||
| 450 | deobfuscateEntry(obfReference.context), | ||
| 451 | obfReference | ||
| 452 | ); | ||
| 453 | } | ||
| 454 | |||
| 455 | public boolean isObfuscatedIdentifier(Entry obfEntry) { | ||
| 456 | return m_jarIndex.containsObfEntry(obfEntry); | ||
| 457 | } | ||
| 458 | |||
| 459 | public boolean isRenameable(EntryReference<Entry,Entry> obfReference) { | ||
| 460 | return obfReference.isNamed() && isObfuscatedIdentifier(obfReference.getNameableEntry()); | ||
| 461 | } | ||
| 462 | |||
| 463 | // NOTE: these methods are a bit messy... oh well | ||
| 464 | |||
| 465 | public boolean hasDeobfuscatedName(Entry obfEntry) { | ||
| 466 | Translator translator = getTranslator(TranslationDirection.Deobfuscating); | ||
| 467 | if (obfEntry instanceof ClassEntry) { | ||
| 468 | return translator.translate((ClassEntry)obfEntry) != null; | ||
| 469 | } else if (obfEntry instanceof FieldEntry) { | ||
| 470 | return translator.translate((FieldEntry)obfEntry) != null; | ||
| 471 | } else if (obfEntry instanceof MethodEntry) { | ||
| 472 | return translator.translate((MethodEntry)obfEntry) != null; | ||
| 473 | } else if (obfEntry instanceof ConstructorEntry) { | ||
| 474 | // constructors have no names | ||
| 475 | return false; | ||
| 476 | } else if (obfEntry instanceof ArgumentEntry) { | ||
| 477 | return translator.translate((ArgumentEntry)obfEntry) != null; | ||
| 478 | } else { | ||
| 479 | throw new Error("Unknown entry type: " + obfEntry.getClass().getName()); | ||
| 480 | } | ||
| 481 | } | ||
| 482 | |||
| 483 | public void rename(Entry obfEntry, String newName) { | ||
| 484 | if (obfEntry instanceof ClassEntry) { | ||
| 485 | m_renamer.setClassName((ClassEntry)obfEntry, Descriptor.toJvmName(newName)); | ||
| 486 | } else if (obfEntry instanceof FieldEntry) { | ||
| 487 | m_renamer.setFieldName((FieldEntry)obfEntry, newName); | ||
| 488 | } else if (obfEntry instanceof MethodEntry) { | ||
| 489 | m_renamer.setMethodTreeName((MethodEntry)obfEntry, newName); | ||
| 490 | } else if (obfEntry instanceof ConstructorEntry) { | ||
| 491 | throw new IllegalArgumentException("Cannot rename constructors"); | ||
| 492 | } else if (obfEntry instanceof ArgumentEntry) { | ||
| 493 | m_renamer.setArgumentName((ArgumentEntry)obfEntry, newName); | ||
| 494 | } else { | ||
| 495 | throw new Error("Unknown entry type: " + obfEntry.getClass().getName()); | ||
| 496 | } | ||
| 497 | |||
| 498 | // clear caches | ||
| 499 | m_translatorCache.clear(); | ||
| 500 | } | ||
| 501 | |||
| 502 | public void removeMapping(Entry obfEntry) { | ||
| 503 | if (obfEntry instanceof ClassEntry) { | ||
| 504 | m_renamer.removeClassMapping((ClassEntry)obfEntry); | ||
| 505 | } else if (obfEntry instanceof FieldEntry) { | ||
| 506 | m_renamer.removeFieldMapping((FieldEntry)obfEntry); | ||
| 507 | } else if (obfEntry instanceof MethodEntry) { | ||
| 508 | m_renamer.removeMethodTreeMapping((MethodEntry)obfEntry); | ||
| 509 | } else if (obfEntry instanceof ConstructorEntry) { | ||
| 510 | throw new IllegalArgumentException("Cannot rename constructors"); | ||
| 511 | } else if (obfEntry instanceof ArgumentEntry) { | ||
| 512 | m_renamer.removeArgumentMapping((ArgumentEntry)obfEntry); | ||
| 513 | } else { | ||
| 514 | throw new Error("Unknown entry type: " + obfEntry); | ||
| 515 | } | ||
| 516 | |||
| 517 | // clear caches | ||
| 518 | m_translatorCache.clear(); | ||
| 519 | } | ||
| 520 | |||
| 521 | public void markAsDeobfuscated(Entry obfEntry) { | ||
| 522 | if (obfEntry instanceof ClassEntry) { | ||
| 523 | m_renamer.markClassAsDeobfuscated((ClassEntry)obfEntry); | ||
| 524 | } else if (obfEntry instanceof FieldEntry) { | ||
| 525 | m_renamer.markFieldAsDeobfuscated((FieldEntry)obfEntry); | ||
| 526 | } else if (obfEntry instanceof MethodEntry) { | ||
| 527 | m_renamer.markMethodTreeAsDeobfuscated((MethodEntry)obfEntry); | ||
| 528 | } else if (obfEntry instanceof ConstructorEntry) { | ||
| 529 | throw new IllegalArgumentException("Cannot rename constructors"); | ||
| 530 | } else if (obfEntry instanceof ArgumentEntry) { | ||
| 531 | m_renamer.markArgumentAsDeobfuscated((ArgumentEntry)obfEntry); | ||
| 532 | } else { | ||
| 533 | throw new Error("Unknown entry type: " + obfEntry); | ||
| 534 | } | ||
| 535 | |||
| 536 | // clear caches | ||
| 537 | m_translatorCache.clear(); | ||
| 538 | } | ||
| 539 | } | ||
diff --git a/src/cuchaz/enigma/Main.java b/src/cuchaz/enigma/Main.java new file mode 100644 index 0000000..acae94b --- /dev/null +++ b/src/cuchaz/enigma/Main.java | |||
| @@ -0,0 +1,51 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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/TranslatingTypeLoader.java b/src/cuchaz/enigma/TranslatingTypeLoader.java new file mode 100644 index 0000000..cfa03a1 --- /dev/null +++ b/src/cuchaz/enigma/TranslatingTypeLoader.java | |||
| @@ -0,0 +1,211 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import java.io.ByteArrayOutputStream; | ||
| 14 | import java.io.IOException; | ||
| 15 | import java.io.InputStream; | ||
| 16 | import java.util.Map; | ||
| 17 | import java.util.jar.JarEntry; | ||
| 18 | import java.util.jar.JarFile; | ||
| 19 | |||
| 20 | import javassist.ByteArrayClassPath; | ||
| 21 | import javassist.CannotCompileException; | ||
| 22 | import javassist.ClassPool; | ||
| 23 | import javassist.CtClass; | ||
| 24 | import javassist.NotFoundException; | ||
| 25 | import javassist.bytecode.Descriptor; | ||
| 26 | |||
| 27 | import com.google.common.collect.Maps; | ||
| 28 | import com.strobel.assembler.metadata.Buffer; | ||
| 29 | import com.strobel.assembler.metadata.ClasspathTypeLoader; | ||
| 30 | import com.strobel.assembler.metadata.ITypeLoader; | ||
| 31 | |||
| 32 | import cuchaz.enigma.analysis.JarIndex; | ||
| 33 | import cuchaz.enigma.bytecode.ClassRenamer; | ||
| 34 | import cuchaz.enigma.bytecode.ClassTranslator; | ||
| 35 | import cuchaz.enigma.bytecode.InnerClassWriter; | ||
| 36 | import cuchaz.enigma.bytecode.MethodParameterWriter; | ||
| 37 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 38 | import cuchaz.enigma.mapping.Translator; | ||
| 39 | |||
| 40 | public class TranslatingTypeLoader implements ITypeLoader { | ||
| 41 | |||
| 42 | private JarFile m_jar; | ||
| 43 | private JarIndex m_jarIndex; | ||
| 44 | private Translator m_obfuscatingTranslator; | ||
| 45 | private Translator m_deobfuscatingTranslator; | ||
| 46 | private Map<String,byte[]> m_cache; | ||
| 47 | private ClasspathTypeLoader m_defaultTypeLoader; | ||
| 48 | |||
| 49 | public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex) { | ||
| 50 | this(jar, jarIndex, new Translator(), new Translator()); | ||
| 51 | } | ||
| 52 | |||
| 53 | public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator) { | ||
| 54 | m_jar = jar; | ||
| 55 | m_jarIndex = jarIndex; | ||
| 56 | m_obfuscatingTranslator = obfuscatingTranslator; | ||
| 57 | m_deobfuscatingTranslator = deobfuscatingTranslator; | ||
| 58 | m_cache = Maps.newHashMap(); | ||
| 59 | m_defaultTypeLoader = new ClasspathTypeLoader(); | ||
| 60 | } | ||
| 61 | |||
| 62 | public void clearCache() { | ||
| 63 | m_cache.clear(); | ||
| 64 | } | ||
| 65 | |||
| 66 | @Override | ||
| 67 | public boolean tryLoadType(String deobfClassName, Buffer out) { | ||
| 68 | // check the cache | ||
| 69 | byte[] data; | ||
| 70 | if (m_cache.containsKey(deobfClassName)) { | ||
| 71 | data = m_cache.get(deobfClassName); | ||
| 72 | } else { | ||
| 73 | data = loadType(deobfClassName); | ||
| 74 | m_cache.put(deobfClassName, data); | ||
| 75 | } | ||
| 76 | |||
| 77 | if (data == null) { | ||
| 78 | // chain to default type loader | ||
| 79 | return m_defaultTypeLoader.tryLoadType(deobfClassName, out); | ||
| 80 | } | ||
| 81 | |||
| 82 | // send the class to the decompiler | ||
| 83 | out.reset(data.length); | ||
| 84 | System.arraycopy(data, 0, out.array(), out.position(), data.length); | ||
| 85 | out.position(0); | ||
| 86 | return true; | ||
| 87 | } | ||
| 88 | |||
| 89 | public CtClass loadClass(String deobfClassName) { | ||
| 90 | byte[] data = loadType(deobfClassName); | ||
| 91 | if (data == null) { | ||
| 92 | return null; | ||
| 93 | } | ||
| 94 | |||
| 95 | // return a javassist handle for the class | ||
| 96 | String javaClassFileName = Descriptor.toJavaName(deobfClassName); | ||
| 97 | ClassPool classPool = new ClassPool(); | ||
| 98 | classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, data)); | ||
| 99 | try { | ||
| 100 | return classPool.get(javaClassFileName); | ||
| 101 | } catch (NotFoundException ex) { | ||
| 102 | throw new Error(ex); | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | private byte[] loadType(String deobfClassName) { | ||
| 107 | ClassEntry deobfClassEntry = new ClassEntry(deobfClassName); | ||
| 108 | ClassEntry obfClassEntry = m_obfuscatingTranslator.translateEntry(deobfClassEntry); | ||
| 109 | |||
| 110 | // is this an inner class referenced directly? | ||
| 111 | String obfOuterClassName = m_jarIndex.getOuterClass(obfClassEntry.getSimpleName()); | ||
| 112 | if (obfOuterClassName != null) { | ||
| 113 | // this class doesn't really exist. Reference it by outer$inner instead | ||
| 114 | System.err.println(String.format("WARNING: class %s referenced by bare inner name instead of via outer class %s", deobfClassName, obfOuterClassName)); | ||
| 115 | return null; | ||
| 116 | } | ||
| 117 | |||
| 118 | /* DEBUG | ||
| 119 | if( !Arrays.asList( "java", "org", "io" ).contains( deobfClassName.split( "/" )[0] ) ) { | ||
| 120 | System.out.println( String.format( "Looking for %s (%s)", deobfClassEntry.getName(), obfClassEntry.getName() ) ); | ||
| 121 | } | ||
| 122 | */ | ||
| 123 | |||
| 124 | // get the jar entry | ||
| 125 | String classFileName; | ||
| 126 | if (obfClassEntry.isInnerClass()) { | ||
| 127 | // use just the inner class name for inner classes | ||
| 128 | classFileName = obfClassEntry.getInnerClassName(); | ||
| 129 | } else if (obfClassEntry.getPackageName().equals(Constants.NonePackage)) { | ||
| 130 | // use the outer class simple name for classes in the none package | ||
| 131 | classFileName = obfClassEntry.getSimpleName(); | ||
| 132 | } else { | ||
| 133 | // otherwise, just use the class name (ie for classes in packages) | ||
| 134 | classFileName = obfClassEntry.getName(); | ||
| 135 | } | ||
| 136 | |||
| 137 | JarEntry entry = m_jar.getJarEntry(classFileName + ".class"); | ||
| 138 | if (entry == null) { | ||
| 139 | return null; | ||
| 140 | } | ||
| 141 | |||
| 142 | try { | ||
| 143 | // read the class file into a buffer | ||
| 144 | ByteArrayOutputStream data = new ByteArrayOutputStream(); | ||
| 145 | byte[] buf = new byte[1024 * 1024]; // 1 KiB | ||
| 146 | InputStream in = m_jar.getInputStream(entry); | ||
| 147 | while (true) { | ||
| 148 | int bytesRead = in.read(buf); | ||
| 149 | if (bytesRead <= 0) { | ||
| 150 | break; | ||
| 151 | } | ||
| 152 | data.write(buf, 0, bytesRead); | ||
| 153 | } | ||
| 154 | data.close(); | ||
| 155 | in.close(); | ||
| 156 | buf = data.toByteArray(); | ||
| 157 | |||
| 158 | // load the javassist handle to the raw class | ||
| 159 | String javaClassFileName = Descriptor.toJavaName(classFileName); | ||
| 160 | ClassPool classPool = new ClassPool(); | ||
| 161 | classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, buf)); | ||
| 162 | CtClass c = classPool.get(javaClassFileName); | ||
| 163 | |||
| 164 | c = transformClass(c); | ||
| 165 | |||
| 166 | // sanity checking | ||
| 167 | assertClassName(c, deobfClassEntry); | ||
| 168 | |||
| 169 | // DEBUG | ||
| 170 | //Util.writeClass( c ); | ||
| 171 | |||
| 172 | // we have a transformed class! | ||
| 173 | return c.toBytecode(); | ||
| 174 | } catch (IOException | NotFoundException | CannotCompileException ex) { | ||
| 175 | throw new Error(ex); | ||
| 176 | } | ||
| 177 | } | ||
| 178 | |||
| 179 | public CtClass transformClass(CtClass c) throws IOException, NotFoundException, CannotCompileException { | ||
| 180 | // we moved a lot of classes out of the default package into the none package | ||
| 181 | // make sure all the class references are consistent | ||
| 182 | ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage); | ||
| 183 | |||
| 184 | // reconstruct inner classes | ||
| 185 | new InnerClassWriter(m_jarIndex).write(c); | ||
| 186 | |||
| 187 | // re-get the javassist handle since we changed class names | ||
| 188 | ClassEntry obfClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); | ||
| 189 | String javaClassReconstructedName = Descriptor.toJavaName(obfClassEntry.getName()); | ||
| 190 | ClassPool classPool = new ClassPool(); | ||
| 191 | classPool.insertClassPath(new ByteArrayClassPath(javaClassReconstructedName, c.toBytecode())); | ||
| 192 | c = classPool.get(javaClassReconstructedName); | ||
| 193 | |||
| 194 | // check that the file is correct after inner class reconstruction (ie cause Javassist to fail fast if something is wrong) | ||
| 195 | assertClassName(c, obfClassEntry); | ||
| 196 | |||
| 197 | // do all kinds of deobfuscating transformations on the class | ||
| 198 | new MethodParameterWriter(m_deobfuscatingTranslator).writeMethodArguments(c); | ||
| 199 | new ClassTranslator(m_deobfuscatingTranslator).translate(c); | ||
| 200 | |||
| 201 | return c; | ||
| 202 | } | ||
| 203 | |||
| 204 | private void assertClassName(CtClass c, ClassEntry obfClassEntry) { | ||
| 205 | String name1 = Descriptor.toJvmName(c.getName()); | ||
| 206 | assert (name1.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name1); | ||
| 207 | |||
| 208 | String name2 = Descriptor.toJvmName(c.getClassFile().getName()); | ||
| 209 | assert (name2.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name2); | ||
| 210 | } | ||
| 211 | } | ||
diff --git a/src/cuchaz/enigma/Util.java b/src/cuchaz/enigma/Util.java new file mode 100644 index 0000000..7f04bda --- /dev/null +++ b/src/cuchaz/enigma/Util.java | |||
| @@ -0,0 +1,104 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..8d3409a --- /dev/null +++ b/src/cuchaz/enigma/analysis/Access.java | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..9adac5e --- /dev/null +++ b/src/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java | |||
| @@ -0,0 +1,93 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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/ClassImplementationsTreeNode.java b/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java new file mode 100644 index 0000000..49aac5f --- /dev/null +++ b/src/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java | |||
| @@ -0,0 +1,80 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..3eaa391 --- /dev/null +++ b/src/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java | |||
| @@ -0,0 +1,85 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..bb611df --- /dev/null +++ b/src/cuchaz/enigma/analysis/EntryReference.java | |||
| @@ -0,0 +1,126 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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.getInnerClassName(); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | return getNameableEntry().getName(); | ||
| 82 | } | ||
| 83 | |||
| 84 | @Override | ||
| 85 | public int hashCode() { | ||
| 86 | if (context != null) { | ||
| 87 | return Util.combineHashesOrdered(entry.hashCode(), context.hashCode()); | ||
| 88 | } | ||
| 89 | return entry.hashCode(); | ||
| 90 | } | ||
| 91 | |||
| 92 | @Override | ||
| 93 | public boolean equals(Object other) { | ||
| 94 | if (other instanceof EntryReference) { | ||
| 95 | return equals((EntryReference<?,?>)other); | ||
| 96 | } | ||
| 97 | return false; | ||
| 98 | } | ||
| 99 | |||
| 100 | public boolean equals(EntryReference<?,?> other) { | ||
| 101 | // check entry first | ||
| 102 | boolean isEntrySame = entry.equals(other.entry); | ||
| 103 | if (!isEntrySame) { | ||
| 104 | return false; | ||
| 105 | } | ||
| 106 | |||
| 107 | // check caller | ||
| 108 | if (context == null && other.context == null) { | ||
| 109 | return true; | ||
| 110 | } else if (context != null && other.context != null) { | ||
| 111 | return context.equals(other.context); | ||
| 112 | } | ||
| 113 | return false; | ||
| 114 | } | ||
| 115 | |||
| 116 | @Override | ||
| 117 | public String toString() { | ||
| 118 | StringBuilder buf = new StringBuilder(); | ||
| 119 | buf.append(entry); | ||
| 120 | if (context != null) { | ||
| 121 | buf.append(" called from "); | ||
| 122 | buf.append(context); | ||
| 123 | } | ||
| 124 | return buf.toString(); | ||
| 125 | } | ||
| 126 | } | ||
diff --git a/src/cuchaz/enigma/analysis/EntryRenamer.java b/src/cuchaz/enigma/analysis/EntryRenamer.java new file mode 100644 index 0000000..b54489c --- /dev/null +++ b/src/cuchaz/enigma/analysis/EntryRenamer.java | |||
| @@ -0,0 +1,171 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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.ConstructorEntry; | ||
| 25 | import cuchaz.enigma.mapping.Entry; | ||
| 26 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 27 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 28 | |||
| 29 | public class EntryRenamer { | ||
| 30 | |||
| 31 | public static <T> void renameClassesInSet(Map<String,String> renames, Set<T> set) { | ||
| 32 | List<T> entries = Lists.newArrayList(); | ||
| 33 | for (T val : set) { | ||
| 34 | entries.add(renameClassesInThing(renames, val)); | ||
| 35 | } | ||
| 36 | set.clear(); | ||
| 37 | set.addAll(entries); | ||
| 38 | } | ||
| 39 | |||
| 40 | public static <Key,Val> void renameClassesInMap(Map<String,String> renames, Map<Key,Val> map) { | ||
| 41 | // for each key/value pair... | ||
| 42 | Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet(); | ||
| 43 | for (Map.Entry<Key,Val> entry : map.entrySet()) { | ||
| 44 | entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>( | ||
| 45 | renameClassesInThing(renames, entry.getKey()), | ||
| 46 | renameClassesInThing(renames, entry.getValue()) | ||
| 47 | )); | ||
| 48 | } | ||
| 49 | map.clear(); | ||
| 50 | for (Map.Entry<Key,Val> entry : entriesToAdd) { | ||
| 51 | map.put(entry.getKey(), entry.getValue()); | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | public static <Key,Val> void renameClassesInMultimap(Map<String,String> renames, Multimap<Key,Val> map) { | ||
| 56 | // for each key/value pair... | ||
| 57 | Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet(); | ||
| 58 | for (Map.Entry<Key,Val> entry : map.entries()) { | ||
| 59 | entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>( | ||
| 60 | renameClassesInThing(renames, entry.getKey()), | ||
| 61 | renameClassesInThing(renames, entry.getValue()) | ||
| 62 | )); | ||
| 63 | } | ||
| 64 | map.clear(); | ||
| 65 | for (Map.Entry<Key,Val> entry : entriesToAdd) { | ||
| 66 | map.put(entry.getKey(), entry.getValue()); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | public static <Key,Val> void renameMethodsInMultimap(Map<MethodEntry,MethodEntry> renames, Multimap<Key,Val> map) { | ||
| 71 | // for each key/value pair... | ||
| 72 | Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet(); | ||
| 73 | for (Map.Entry<Key,Val> entry : map.entries()) { | ||
| 74 | entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>( | ||
| 75 | renameMethodsInThing(renames, entry.getKey()), | ||
| 76 | renameMethodsInThing(renames, entry.getValue()) | ||
| 77 | )); | ||
| 78 | } | ||
| 79 | map.clear(); | ||
| 80 | for (Map.Entry<Key,Val> entry : entriesToAdd) { | ||
| 81 | map.put(entry.getKey(), entry.getValue()); | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | public static <Key,Val> void renameMethodsInMap(Map<MethodEntry,MethodEntry> renames, Map<Key,Val> map) { | ||
| 86 | // for each key/value pair... | ||
| 87 | Set<Map.Entry<Key,Val>> entriesToAdd = Sets.newHashSet(); | ||
| 88 | for (Map.Entry<Key,Val> entry : map.entrySet()) { | ||
| 89 | entriesToAdd.add(new AbstractMap.SimpleEntry<Key,Val>( | ||
| 90 | renameMethodsInThing(renames, entry.getKey()), | ||
| 91 | renameMethodsInThing(renames, entry.getValue()) | ||
| 92 | )); | ||
| 93 | } | ||
| 94 | map.clear(); | ||
| 95 | for (Map.Entry<Key,Val> entry : entriesToAdd) { | ||
| 96 | map.put(entry.getKey(), entry.getValue()); | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | @SuppressWarnings("unchecked") | ||
| 101 | public static <T> T renameMethodsInThing(Map<MethodEntry,MethodEntry> renames, T thing) { | ||
| 102 | if (thing instanceof MethodEntry) { | ||
| 103 | MethodEntry methodEntry = (MethodEntry)thing; | ||
| 104 | MethodEntry newMethodEntry = renames.get(methodEntry); | ||
| 105 | if (newMethodEntry != null) { | ||
| 106 | return (T)new MethodEntry( | ||
| 107 | methodEntry.getClassEntry(), | ||
| 108 | newMethodEntry.getName(), | ||
| 109 | methodEntry.getSignature() | ||
| 110 | ); | ||
| 111 | } | ||
| 112 | return thing; | ||
| 113 | } else if (thing instanceof ArgumentEntry) { | ||
| 114 | ArgumentEntry argumentEntry = (ArgumentEntry)thing; | ||
| 115 | return (T)new ArgumentEntry( | ||
| 116 | renameMethodsInThing(renames, argumentEntry.getBehaviorEntry()), | ||
| 117 | argumentEntry.getIndex(), | ||
| 118 | argumentEntry.getName() | ||
| 119 | ); | ||
| 120 | } else if (thing instanceof EntryReference) { | ||
| 121 | EntryReference<Entry,Entry> reference = (EntryReference<Entry,Entry>)thing; | ||
| 122 | reference.entry = renameMethodsInThing(renames, reference.entry); | ||
| 123 | reference.context = renameMethodsInThing(renames, reference.context); | ||
| 124 | return thing; | ||
| 125 | } | ||
| 126 | return thing; | ||
| 127 | } | ||
| 128 | |||
| 129 | @SuppressWarnings("unchecked") | ||
| 130 | public static <T> T renameClassesInThing(Map<String,String> renames, T thing) { | ||
| 131 | if (thing instanceof String) { | ||
| 132 | String stringEntry = (String)thing; | ||
| 133 | if (renames.containsKey(stringEntry)) { | ||
| 134 | return (T)renames.get(stringEntry); | ||
| 135 | } | ||
| 136 | } else if (thing instanceof ClassEntry) { | ||
| 137 | ClassEntry classEntry = (ClassEntry)thing; | ||
| 138 | return (T)new ClassEntry(renameClassesInThing(renames, classEntry.getClassName())); | ||
| 139 | } else if (thing instanceof FieldEntry) { | ||
| 140 | FieldEntry fieldEntry = (FieldEntry)thing; | ||
| 141 | return (T)new FieldEntry(renameClassesInThing(renames, fieldEntry.getClassEntry()), fieldEntry.getName()); | ||
| 142 | } else if (thing instanceof ConstructorEntry) { | ||
| 143 | ConstructorEntry constructorEntry = (ConstructorEntry)thing; | ||
| 144 | return (T)new ConstructorEntry( | ||
| 145 | renameClassesInThing(renames, constructorEntry.getClassEntry()), | ||
| 146 | constructorEntry.getSignature() | ||
| 147 | ); | ||
| 148 | } else if (thing instanceof MethodEntry) { | ||
| 149 | MethodEntry methodEntry = (MethodEntry)thing; | ||
| 150 | return (T)new MethodEntry( | ||
| 151 | renameClassesInThing(renames, methodEntry.getClassEntry()), | ||
| 152 | methodEntry.getName(), | ||
| 153 | methodEntry.getSignature() | ||
| 154 | ); | ||
| 155 | } else if (thing instanceof ArgumentEntry) { | ||
| 156 | ArgumentEntry argumentEntry = (ArgumentEntry)thing; | ||
| 157 | return (T)new ArgumentEntry( | ||
| 158 | renameClassesInThing(renames, argumentEntry.getBehaviorEntry()), | ||
| 159 | argumentEntry.getIndex(), | ||
| 160 | argumentEntry.getName() | ||
| 161 | ); | ||
| 162 | } else if (thing instanceof EntryReference) { | ||
| 163 | EntryReference<Entry,Entry> reference = (EntryReference<Entry,Entry>)thing; | ||
| 164 | reference.entry = renameClassesInThing(renames, reference.entry); | ||
| 165 | reference.context = renameClassesInThing(renames, reference.context); | ||
| 166 | return thing; | ||
| 167 | } | ||
| 168 | |||
| 169 | return thing; | ||
| 170 | } | ||
| 171 | } | ||
diff --git a/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java new file mode 100644 index 0000000..2173eea --- /dev/null +++ b/src/cuchaz/enigma/analysis/FieldReferenceTreeNode.java | |||
| @@ -0,0 +1,81 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..72a9912 --- /dev/null +++ b/src/cuchaz/enigma/analysis/JarClassIterator.java | |||
| @@ -0,0 +1,137 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..3aac8bd --- /dev/null +++ b/src/cuchaz/enigma/analysis/JarIndex.java | |||
| @@ -0,0 +1,734 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.analysis; | ||
| 12 | |||
| 13 | import java.lang.reflect.Modifier; | ||
| 14 | import java.util.Collection; | ||
| 15 | import java.util.HashSet; | ||
| 16 | import java.util.List; | ||
| 17 | import java.util.Map; | ||
| 18 | import java.util.Set; | ||
| 19 | import java.util.jar.JarFile; | ||
| 20 | |||
| 21 | import javassist.CannotCompileException; | ||
| 22 | import javassist.CtBehavior; | ||
| 23 | import javassist.CtClass; | ||
| 24 | import javassist.CtConstructor; | ||
| 25 | import javassist.CtField; | ||
| 26 | import javassist.bytecode.AccessFlag; | ||
| 27 | import javassist.bytecode.Descriptor; | ||
| 28 | import javassist.bytecode.FieldInfo; | ||
| 29 | import javassist.expr.ConstructorCall; | ||
| 30 | import javassist.expr.ExprEditor; | ||
| 31 | import javassist.expr.FieldAccess; | ||
| 32 | import javassist.expr.MethodCall; | ||
| 33 | import javassist.expr.NewExpr; | ||
| 34 | |||
| 35 | import com.google.common.collect.HashMultimap; | ||
| 36 | import com.google.common.collect.Lists; | ||
| 37 | import com.google.common.collect.Maps; | ||
| 38 | import com.google.common.collect.Multimap; | ||
| 39 | import com.google.common.collect.Sets; | ||
| 40 | |||
| 41 | import cuchaz.enigma.Constants; | ||
| 42 | import cuchaz.enigma.bytecode.ClassRenamer; | ||
| 43 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 44 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 45 | import cuchaz.enigma.mapping.BehaviorEntryFactory; | ||
| 46 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 47 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 48 | import cuchaz.enigma.mapping.Entry; | ||
| 49 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 50 | import cuchaz.enigma.mapping.JavassistUtil; | ||
| 51 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 52 | import cuchaz.enigma.mapping.Translator; | ||
| 53 | |||
| 54 | public class JarIndex { | ||
| 55 | |||
| 56 | private Set<ClassEntry> m_obfClassEntries; | ||
| 57 | private TranslationIndex m_translationIndex; | ||
| 58 | private Multimap<String,String> m_interfaces; | ||
| 59 | private Map<Entry,Access> m_access; | ||
| 60 | private Map<FieldEntry,ClassEntry> m_fieldClasses; // TODO: this will become obsolete! | ||
| 61 | private Multimap<String,MethodEntry> m_methodImplementations; | ||
| 62 | private Multimap<BehaviorEntry,EntryReference<BehaviorEntry,BehaviorEntry>> m_behaviorReferences; | ||
| 63 | private Multimap<FieldEntry,EntryReference<FieldEntry,BehaviorEntry>> m_fieldReferences; | ||
| 64 | private Multimap<String,String> m_innerClasses; | ||
| 65 | private Map<String,String> m_outerClasses; | ||
| 66 | private Map<String,BehaviorEntry> m_anonymousClasses; | ||
| 67 | |||
| 68 | public JarIndex() { | ||
| 69 | m_obfClassEntries = Sets.newHashSet(); | ||
| 70 | m_translationIndex = new TranslationIndex(); | ||
| 71 | m_interfaces = HashMultimap.create(); | ||
| 72 | m_access = Maps.newHashMap(); | ||
| 73 | m_fieldClasses = Maps.newHashMap(); | ||
| 74 | m_methodImplementations = HashMultimap.create(); | ||
| 75 | m_behaviorReferences = HashMultimap.create(); | ||
| 76 | m_fieldReferences = HashMultimap.create(); | ||
| 77 | m_innerClasses = HashMultimap.create(); | ||
| 78 | m_outerClasses = Maps.newHashMap(); | ||
| 79 | m_anonymousClasses = Maps.newHashMap(); | ||
| 80 | } | ||
| 81 | |||
| 82 | public void indexJar(JarFile jar, boolean buildInnerClasses) { | ||
| 83 | |||
| 84 | // step 1: read the class names | ||
| 85 | for (ClassEntry classEntry : JarClassIterator.getClassEntries(jar)) { | ||
| 86 | if (classEntry.isInDefaultPackage()) { | ||
| 87 | // move out of default package | ||
| 88 | classEntry = new ClassEntry(Constants.NonePackage + "/" + classEntry.getName()); | ||
| 89 | } | ||
| 90 | m_obfClassEntries.add(classEntry); | ||
| 91 | } | ||
| 92 | |||
| 93 | // step 2: index field/method/constructor access | ||
| 94 | for (CtClass c : JarClassIterator.classes(jar)) { | ||
| 95 | ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage); | ||
| 96 | for (CtField field : c.getDeclaredFields()) { | ||
| 97 | m_access.put(JavassistUtil.getFieldEntry(field), Access.get(field)); | ||
| 98 | } | ||
| 99 | for (CtBehavior behavior : c.getDeclaredBehaviors()) { | ||
| 100 | m_access.put(JavassistUtil.getBehaviorEntry(behavior), Access.get(behavior)); | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | // step 3: index extends, implements, fields, and methods | ||
| 105 | for (CtClass c : JarClassIterator.classes(jar)) { | ||
| 106 | ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage); | ||
| 107 | m_translationIndex.indexClass(c); | ||
| 108 | String className = Descriptor.toJvmName(c.getName()); | ||
| 109 | for (String interfaceName : c.getClassFile().getInterfaces()) { | ||
| 110 | className = Descriptor.toJvmName(className); | ||
| 111 | interfaceName = Descriptor.toJvmName(interfaceName); | ||
| 112 | if (className.equals(interfaceName)) { | ||
| 113 | throw new IllegalArgumentException("Class cannot be its own interface! " + className); | ||
| 114 | } | ||
| 115 | m_interfaces.put(className, interfaceName); | ||
| 116 | } | ||
| 117 | for (CtField field : c.getDeclaredFields()) { | ||
| 118 | indexField(field); | ||
| 119 | } | ||
| 120 | for (CtBehavior behavior : c.getDeclaredBehaviors()) { | ||
| 121 | indexBehavior(behavior); | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | // step 4: index field, method, constructor references | ||
| 126 | for (CtClass c : JarClassIterator.classes(jar)) { | ||
| 127 | ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage); | ||
| 128 | for (CtBehavior behavior : c.getDeclaredBehaviors()) { | ||
| 129 | indexBehaviorReferences(behavior); | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | if (buildInnerClasses) { | ||
| 134 | // step 5: index inner classes and anonymous classes | ||
| 135 | for (CtClass c : JarClassIterator.classes(jar)) { | ||
| 136 | ClassRenamer.moveAllClassesOutOfDefaultPackage(c, Constants.NonePackage); | ||
| 137 | String outerClassName = findOuterClass(c); | ||
| 138 | if (outerClassName != null) { | ||
| 139 | String innerClassName = c.getSimpleName(); | ||
| 140 | m_innerClasses.put(outerClassName, innerClassName); | ||
| 141 | boolean innerWasAdded = m_outerClasses.put(innerClassName, outerClassName) == null; | ||
| 142 | assert (innerWasAdded); | ||
| 143 | |||
| 144 | BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassName); | ||
| 145 | if (enclosingBehavior != null) { | ||
| 146 | m_anonymousClasses.put(innerClassName, enclosingBehavior); | ||
| 147 | |||
| 148 | // DEBUG | ||
| 149 | // System.out.println( "ANONYMOUS: " + outerClassName + "$" + innerClassName ); | ||
| 150 | } else { | ||
| 151 | // DEBUG | ||
| 152 | // System.out.println( "INNER: " + outerClassName + "$" + innerClassName ); | ||
| 153 | } | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | // step 6: update other indices with inner class info | ||
| 158 | Map<String,String> renames = Maps.newHashMap(); | ||
| 159 | for (Map.Entry<String,String> entry : m_outerClasses.entrySet()) { | ||
| 160 | renames.put(Constants.NonePackage + "/" + entry.getKey(), entry.getValue() + "$" + entry.getKey()); | ||
| 161 | } | ||
| 162 | EntryRenamer.renameClassesInSet(renames, m_obfClassEntries); | ||
| 163 | m_translationIndex.renameClasses(renames); | ||
| 164 | EntryRenamer.renameClassesInMultimap(renames, m_interfaces); | ||
| 165 | EntryRenamer.renameClassesInMultimap(renames, m_methodImplementations); | ||
| 166 | EntryRenamer.renameClassesInMultimap(renames, m_behaviorReferences); | ||
| 167 | EntryRenamer.renameClassesInMultimap(renames, m_fieldReferences); | ||
| 168 | EntryRenamer.renameClassesInMap(renames, m_access); | ||
| 169 | } | ||
| 170 | } | ||
| 171 | |||
| 172 | private void indexField(CtField field) { | ||
| 173 | // get the field entry | ||
| 174 | String className = Descriptor.toJvmName(field.getDeclaringClass().getName()); | ||
| 175 | FieldEntry fieldEntry = new FieldEntry(new ClassEntry(className), field.getName()); | ||
| 176 | |||
| 177 | // is the field a class type? | ||
| 178 | if (field.getSignature().startsWith("L")) { | ||
| 179 | ClassEntry fieldTypeEntry = new ClassEntry(field.getSignature().substring(1, field.getSignature().length() - 1)); | ||
| 180 | m_fieldClasses.put(fieldEntry, fieldTypeEntry); | ||
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | private void indexBehavior(CtBehavior behavior) { | ||
| 185 | // get the behavior entry | ||
| 186 | final BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(behavior); | ||
| 187 | if (behaviorEntry instanceof MethodEntry) { | ||
| 188 | MethodEntry methodEntry = (MethodEntry)behaviorEntry; | ||
| 189 | |||
| 190 | // index implementation | ||
| 191 | m_methodImplementations.put(behaviorEntry.getClassName(), methodEntry); | ||
| 192 | } | ||
| 193 | // looks like we don't care about constructors here | ||
| 194 | } | ||
| 195 | |||
| 196 | private void indexBehaviorReferences(CtBehavior behavior) { | ||
| 197 | // index method calls | ||
| 198 | final BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(behavior); | ||
| 199 | try { | ||
| 200 | behavior.instrument(new ExprEditor() { | ||
| 201 | @Override | ||
| 202 | public void edit(MethodCall call) { | ||
| 203 | MethodEntry calledMethodEntry = JavassistUtil.getMethodEntry(call); | ||
| 204 | ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledMethodEntry); | ||
| 205 | if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) { | ||
| 206 | calledMethodEntry = new MethodEntry( | ||
| 207 | resolvedClassEntry, | ||
| 208 | calledMethodEntry.getName(), | ||
| 209 | calledMethodEntry.getSignature() | ||
| 210 | ); | ||
| 211 | } | ||
| 212 | EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>( | ||
| 213 | calledMethodEntry, | ||
| 214 | call.getMethodName(), | ||
| 215 | behaviorEntry | ||
| 216 | ); | ||
| 217 | m_behaviorReferences.put(calledMethodEntry, reference); | ||
| 218 | } | ||
| 219 | |||
| 220 | @Override | ||
| 221 | public void edit(FieldAccess call) { | ||
| 222 | FieldEntry calledFieldEntry = JavassistUtil.getFieldEntry(call); | ||
| 223 | ClassEntry resolvedClassEntry = m_translationIndex.resolveEntryClass(calledFieldEntry); | ||
| 224 | if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) { | ||
| 225 | calledFieldEntry = new FieldEntry(resolvedClassEntry, call.getFieldName()); | ||
| 226 | } | ||
| 227 | EntryReference<FieldEntry,BehaviorEntry> reference = new EntryReference<FieldEntry,BehaviorEntry>( | ||
| 228 | calledFieldEntry, | ||
| 229 | call.getFieldName(), | ||
| 230 | behaviorEntry | ||
| 231 | ); | ||
| 232 | m_fieldReferences.put(calledFieldEntry, reference); | ||
| 233 | } | ||
| 234 | |||
| 235 | @Override | ||
| 236 | public void edit(ConstructorCall call) { | ||
| 237 | ConstructorEntry calledConstructorEntry = JavassistUtil.getConstructorEntry(call); | ||
| 238 | EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>( | ||
| 239 | calledConstructorEntry, | ||
| 240 | call.getMethodName(), | ||
| 241 | behaviorEntry | ||
| 242 | ); | ||
| 243 | m_behaviorReferences.put(calledConstructorEntry, reference); | ||
| 244 | } | ||
| 245 | |||
| 246 | @Override | ||
| 247 | public void edit(NewExpr call) { | ||
| 248 | ConstructorEntry calledConstructorEntry = JavassistUtil.getConstructorEntry(call); | ||
| 249 | EntryReference<BehaviorEntry,BehaviorEntry> reference = new EntryReference<BehaviorEntry,BehaviorEntry>( | ||
| 250 | calledConstructorEntry, | ||
| 251 | call.getClassName(), | ||
| 252 | behaviorEntry | ||
| 253 | ); | ||
| 254 | m_behaviorReferences.put(calledConstructorEntry, reference); | ||
| 255 | } | ||
| 256 | }); | ||
| 257 | } catch (CannotCompileException ex) { | ||
| 258 | throw new Error(ex); | ||
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 262 | private String findOuterClass(CtClass c) { | ||
| 263 | |||
| 264 | // inner classes: | ||
| 265 | // have constructors that can (illegally) set synthetic fields | ||
| 266 | // the outer class is the only class that calls constructors | ||
| 267 | |||
| 268 | // use the synthetic fields to find the synthetic constructors | ||
| 269 | for (CtConstructor constructor : c.getDeclaredConstructors()) { | ||
| 270 | Set<String> syntheticFieldTypes = Sets.newHashSet(); | ||
| 271 | if (!isIllegalConstructor(syntheticFieldTypes, constructor)) { | ||
| 272 | continue; | ||
| 273 | } | ||
| 274 | |||
| 275 | ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); | ||
| 276 | ConstructorEntry constructorEntry = JavassistUtil.getConstructorEntry(constructor); | ||
| 277 | |||
| 278 | // gather the classes from the illegally-set synthetic fields | ||
| 279 | Set<ClassEntry> illegallySetClasses = Sets.newHashSet(); | ||
| 280 | for (String type : syntheticFieldTypes) { | ||
| 281 | if (type.startsWith("L")) { | ||
| 282 | ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1)); | ||
| 283 | if (isSaneOuterClass(outerClassEntry, classEntry)) { | ||
| 284 | illegallySetClasses.add(outerClassEntry); | ||
| 285 | } | ||
| 286 | } | ||
| 287 | } | ||
| 288 | |||
| 289 | // who calls this constructor? | ||
| 290 | Set<ClassEntry> callerClasses = Sets.newHashSet(); | ||
| 291 | for (EntryReference<BehaviorEntry,BehaviorEntry> reference : getBehaviorReferences(constructorEntry)) { | ||
| 292 | |||
| 293 | // make sure it's not a call to super | ||
| 294 | if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) { | ||
| 295 | |||
| 296 | // is the entry a superclass of the context? | ||
| 297 | ClassEntry calledClassEntry = reference.entry.getClassEntry(); | ||
| 298 | ClassEntry superclassEntry = m_translationIndex.getSuperclass(reference.context.getClassEntry()); | ||
| 299 | if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) { | ||
| 300 | // it's a super call, skip | ||
| 301 | continue; | ||
| 302 | } | ||
| 303 | } | ||
| 304 | |||
| 305 | if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) { | ||
| 306 | callerClasses.add(reference.context.getClassEntry()); | ||
| 307 | } | ||
| 308 | } | ||
| 309 | |||
| 310 | // do we have an answer yet? | ||
| 311 | if (callerClasses.isEmpty()) { | ||
| 312 | if (illegallySetClasses.size() == 1) { | ||
| 313 | return illegallySetClasses.iterator().next().getName(); | ||
| 314 | } else { | ||
| 315 | System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry)); | ||
| 316 | } | ||
| 317 | } else { | ||
| 318 | if (callerClasses.size() == 1) { | ||
| 319 | return callerClasses.iterator().next().getName(); | ||
| 320 | } else { | ||
| 321 | // multiple callers, do the illegally set classes narrow it down? | ||
| 322 | Set<ClassEntry> intersection = Sets.newHashSet(callerClasses); | ||
| 323 | intersection.retainAll(illegallySetClasses); | ||
| 324 | if (intersection.size() == 1) { | ||
| 325 | return intersection.iterator().next().getName(); | ||
| 326 | } else { | ||
| 327 | System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses)); | ||
| 328 | } | ||
| 329 | } | ||
| 330 | } | ||
| 331 | } | ||
| 332 | |||
| 333 | return null; | ||
| 334 | } | ||
| 335 | |||
| 336 | private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) { | ||
| 337 | |||
| 338 | // clearly this would be silly | ||
| 339 | if (outerClassEntry.equals(innerClassEntry)) { | ||
| 340 | return false; | ||
| 341 | } | ||
| 342 | |||
| 343 | // is the outer class in the jar? | ||
| 344 | if (!m_obfClassEntries.contains(outerClassEntry)) { | ||
| 345 | return false; | ||
| 346 | } | ||
| 347 | |||
| 348 | return true; | ||
| 349 | } | ||
| 350 | |||
| 351 | @SuppressWarnings("unchecked") | ||
| 352 | private boolean isIllegalConstructor(Set<String> syntheticFieldTypes, CtConstructor constructor) { | ||
| 353 | |||
| 354 | // illegal constructors only set synthetic member fields, then call super() | ||
| 355 | String className = constructor.getDeclaringClass().getName(); | ||
| 356 | |||
| 357 | // collect all the field accesses, constructor calls, and method calls | ||
| 358 | final List<FieldAccess> illegalFieldWrites = Lists.newArrayList(); | ||
| 359 | final List<ConstructorCall> constructorCalls = Lists.newArrayList(); | ||
| 360 | try { | ||
| 361 | constructor.instrument(new ExprEditor() { | ||
| 362 | @Override | ||
| 363 | public void edit(FieldAccess fieldAccess) { | ||
| 364 | if (fieldAccess.isWriter() && constructorCalls.isEmpty()) { | ||
| 365 | illegalFieldWrites.add(fieldAccess); | ||
| 366 | } | ||
| 367 | } | ||
| 368 | |||
| 369 | @Override | ||
| 370 | public void edit(ConstructorCall constructorCall) { | ||
| 371 | constructorCalls.add(constructorCall); | ||
| 372 | } | ||
| 373 | }); | ||
| 374 | } catch (CannotCompileException ex) { | ||
| 375 | // we're not compiling anything... this is stupid | ||
| 376 | throw new Error(ex); | ||
| 377 | } | ||
| 378 | |||
| 379 | // are there any illegal field writes? | ||
| 380 | if (illegalFieldWrites.isEmpty()) { | ||
| 381 | return false; | ||
| 382 | } | ||
| 383 | |||
| 384 | // are all the writes to synthetic fields? | ||
| 385 | for (FieldAccess fieldWrite : illegalFieldWrites) { | ||
| 386 | |||
| 387 | // all illegal writes have to be to the local class | ||
| 388 | if (!fieldWrite.getClassName().equals(className)) { | ||
| 389 | System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName())); | ||
| 390 | return false; | ||
| 391 | } | ||
| 392 | |||
| 393 | // find the field | ||
| 394 | FieldInfo fieldInfo = null; | ||
| 395 | for (FieldInfo info : (List<FieldInfo>)constructor.getDeclaringClass().getClassFile().getFields()) { | ||
| 396 | if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) { | ||
| 397 | fieldInfo = info; | ||
| 398 | break; | ||
| 399 | } | ||
| 400 | } | ||
| 401 | if (fieldInfo == null) { | ||
| 402 | // field is in a superclass or something, can't be a local synthetic member | ||
| 403 | return false; | ||
| 404 | } | ||
| 405 | |||
| 406 | // is this field synthetic? | ||
| 407 | boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0; | ||
| 408 | if (isSynthetic) { | ||
| 409 | syntheticFieldTypes.add(fieldInfo.getDescriptor()); | ||
| 410 | } else { | ||
| 411 | System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName())); | ||
| 412 | return false; | ||
| 413 | } | ||
| 414 | } | ||
| 415 | |||
| 416 | // we passed all the tests! | ||
| 417 | return true; | ||
| 418 | } | ||
| 419 | |||
| 420 | private BehaviorEntry isAnonymousClass(CtClass c, String outerClassName) { | ||
| 421 | |||
| 422 | ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); | ||
| 423 | |||
| 424 | // anonymous classes: | ||
| 425 | // can't be abstract | ||
| 426 | // have only one constructor | ||
| 427 | // it's called exactly once by the outer class | ||
| 428 | // the type the instance is assigned to can't be this type | ||
| 429 | |||
| 430 | // is abstract? | ||
| 431 | if (Modifier.isAbstract(c.getModifiers())) { | ||
| 432 | return null; | ||
| 433 | } | ||
| 434 | |||
| 435 | // is there exactly one constructor? | ||
| 436 | if (c.getDeclaredConstructors().length != 1) { | ||
| 437 | return null; | ||
| 438 | } | ||
| 439 | CtConstructor constructor = c.getDeclaredConstructors()[0]; | ||
| 440 | |||
| 441 | // is this constructor called exactly once? | ||
| 442 | ConstructorEntry constructorEntry = JavassistUtil.getConstructorEntry(constructor); | ||
| 443 | Collection<EntryReference<BehaviorEntry,BehaviorEntry>> references = getBehaviorReferences(constructorEntry); | ||
| 444 | if (references.size() != 1) { | ||
| 445 | return null; | ||
| 446 | } | ||
| 447 | |||
| 448 | // does the caller use this type? | ||
| 449 | BehaviorEntry caller = references.iterator().next().context; | ||
| 450 | for (FieldEntry fieldEntry : getReferencedFields(caller)) { | ||
| 451 | ClassEntry fieldClass = getFieldClass(fieldEntry); | ||
| 452 | if (fieldClass != null && fieldClass.equals(innerClassEntry)) { | ||
| 453 | // caller references this type, so it can't be anonymous | ||
| 454 | return null; | ||
| 455 | } | ||
| 456 | } | ||
| 457 | for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) { | ||
| 458 | if (behaviorEntry.getSignature().hasClass(innerClassEntry)) { | ||
| 459 | return null; | ||
| 460 | } | ||
| 461 | } | ||
| 462 | |||
| 463 | return caller; | ||
| 464 | } | ||
| 465 | |||
| 466 | public Set<ClassEntry> getObfClassEntries() { | ||
| 467 | return m_obfClassEntries; | ||
| 468 | } | ||
| 469 | |||
| 470 | public TranslationIndex getTranslationIndex() { | ||
| 471 | return m_translationIndex; | ||
| 472 | } | ||
| 473 | |||
| 474 | public Access getAccess(Entry entry) { | ||
| 475 | return m_access.get(entry); | ||
| 476 | } | ||
| 477 | |||
| 478 | public ClassEntry getFieldClass(FieldEntry fieldEntry) { | ||
| 479 | return m_fieldClasses.get(fieldEntry); | ||
| 480 | } | ||
| 481 | |||
| 482 | public ClassInheritanceTreeNode getClassInheritance(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) { | ||
| 483 | |||
| 484 | // get the root node | ||
| 485 | List<String> ancestry = Lists.newArrayList(); | ||
| 486 | ancestry.add(obfClassEntry.getName()); | ||
| 487 | for (ClassEntry classEntry : m_translationIndex.getAncestry(obfClassEntry)) { | ||
| 488 | ancestry.add(classEntry.getName()); | ||
| 489 | } | ||
| 490 | ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode( | ||
| 491 | deobfuscatingTranslator, | ||
| 492 | ancestry.get(ancestry.size() - 1) | ||
| 493 | ); | ||
| 494 | |||
| 495 | // expand all children recursively | ||
| 496 | rootNode.load(m_translationIndex, true); | ||
| 497 | |||
| 498 | return rootNode; | ||
| 499 | } | ||
| 500 | |||
| 501 | public ClassImplementationsTreeNode getClassImplementations(Translator deobfuscatingTranslator, ClassEntry obfClassEntry) { | ||
| 502 | |||
| 503 | // is this even an interface? | ||
| 504 | if (isInterface(obfClassEntry.getClassName())) { | ||
| 505 | ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(deobfuscatingTranslator, obfClassEntry); | ||
| 506 | node.load(this); | ||
| 507 | return node; | ||
| 508 | } | ||
| 509 | return null; | ||
| 510 | } | ||
| 511 | |||
| 512 | public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { | ||
| 513 | |||
| 514 | // travel to the ancestor implementation | ||
| 515 | ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry(); | ||
| 516 | for (ClassEntry ancestorClassEntry : m_translationIndex.getAncestry(obfMethodEntry.getClassEntry())) { | ||
| 517 | MethodEntry ancestorMethodEntry = new MethodEntry( | ||
| 518 | new ClassEntry(ancestorClassEntry), | ||
| 519 | obfMethodEntry.getName(), | ||
| 520 | obfMethodEntry.getSignature() | ||
| 521 | ); | ||
| 522 | if (containsObfBehavior(ancestorMethodEntry)) { | ||
| 523 | baseImplementationClassEntry = ancestorClassEntry; | ||
| 524 | } | ||
| 525 | } | ||
| 526 | |||
| 527 | // make a root node at the base | ||
| 528 | MethodEntry methodEntry = new MethodEntry( | ||
| 529 | baseImplementationClassEntry, | ||
| 530 | obfMethodEntry.getName(), | ||
| 531 | obfMethodEntry.getSignature() | ||
| 532 | ); | ||
| 533 | MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode( | ||
| 534 | deobfuscatingTranslator, | ||
| 535 | methodEntry, | ||
| 536 | containsObfBehavior(methodEntry) | ||
| 537 | ); | ||
| 538 | |||
| 539 | // expand the full tree | ||
| 540 | rootNode.load(this, true); | ||
| 541 | |||
| 542 | return rootNode; | ||
| 543 | } | ||
| 544 | |||
| 545 | public MethodImplementationsTreeNode getMethodImplementations(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { | ||
| 546 | |||
| 547 | MethodEntry interfaceMethodEntry; | ||
| 548 | |||
| 549 | // is this method on an interface? | ||
| 550 | if (isInterface(obfMethodEntry.getClassName())) { | ||
| 551 | interfaceMethodEntry = obfMethodEntry; | ||
| 552 | } else { | ||
| 553 | // get the interface class | ||
| 554 | List<MethodEntry> methodInterfaces = Lists.newArrayList(); | ||
| 555 | for (String interfaceName : getInterfaces(obfMethodEntry.getClassName())) { | ||
| 556 | // is this method defined in this interface? | ||
| 557 | MethodEntry methodInterface = new MethodEntry( | ||
| 558 | new ClassEntry(interfaceName), | ||
| 559 | obfMethodEntry.getName(), | ||
| 560 | obfMethodEntry.getSignature() | ||
| 561 | ); | ||
| 562 | if (containsObfBehavior(methodInterface)) { | ||
| 563 | methodInterfaces.add(methodInterface); | ||
| 564 | } | ||
| 565 | } | ||
| 566 | if (methodInterfaces.isEmpty()) { | ||
| 567 | return null; | ||
| 568 | } | ||
| 569 | if (methodInterfaces.size() > 1) { | ||
| 570 | throw new Error("Too many interfaces define this method! This is not yet supported by Enigma!"); | ||
| 571 | } | ||
| 572 | interfaceMethodEntry = methodInterfaces.get(0); | ||
| 573 | } | ||
| 574 | |||
| 575 | MethodImplementationsTreeNode rootNode = new MethodImplementationsTreeNode(deobfuscatingTranslator, interfaceMethodEntry); | ||
| 576 | rootNode.load(this); | ||
| 577 | return rootNode; | ||
| 578 | } | ||
| 579 | |||
| 580 | public Set<MethodEntry> getRelatedMethodImplementations(MethodEntry obfMethodEntry) { | ||
| 581 | Set<MethodEntry> methodEntries = Sets.newHashSet(); | ||
| 582 | getRelatedMethodImplementations(methodEntries, getMethodInheritance(null, obfMethodEntry)); | ||
| 583 | return methodEntries; | ||
| 584 | } | ||
| 585 | |||
| 586 | private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) { | ||
| 587 | MethodEntry methodEntry = node.getMethodEntry(); | ||
| 588 | if (containsObfBehavior(methodEntry)) { | ||
| 589 | // collect the entry | ||
| 590 | methodEntries.add(methodEntry); | ||
| 591 | } | ||
| 592 | |||
| 593 | // look at interface methods too | ||
| 594 | MethodImplementationsTreeNode implementations = getMethodImplementations(null, methodEntry); | ||
| 595 | if (implementations != null) { | ||
| 596 | getRelatedMethodImplementations(methodEntries, implementations); | ||
| 597 | } | ||
| 598 | |||
| 599 | // recurse | ||
| 600 | for (int i = 0; i < node.getChildCount(); i++) { | ||
| 601 | getRelatedMethodImplementations(methodEntries, (MethodInheritanceTreeNode)node.getChildAt(i)); | ||
| 602 | } | ||
| 603 | } | ||
| 604 | |||
| 605 | private void getRelatedMethodImplementations(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) { | ||
| 606 | MethodEntry methodEntry = node.getMethodEntry(); | ||
| 607 | if (containsObfBehavior(methodEntry)) { | ||
| 608 | // collect the entry | ||
| 609 | methodEntries.add(methodEntry); | ||
| 610 | } | ||
| 611 | |||
| 612 | // recurse | ||
| 613 | for (int i = 0; i < node.getChildCount(); i++) { | ||
| 614 | getRelatedMethodImplementations(methodEntries, (MethodImplementationsTreeNode)node.getChildAt(i)); | ||
| 615 | } | ||
| 616 | } | ||
| 617 | |||
| 618 | public Collection<EntryReference<FieldEntry,BehaviorEntry>> getFieldReferences(FieldEntry fieldEntry) { | ||
| 619 | return m_fieldReferences.get(fieldEntry); | ||
| 620 | } | ||
| 621 | |||
| 622 | public Collection<FieldEntry> getReferencedFields(BehaviorEntry behaviorEntry) { | ||
| 623 | // linear search is fast enough for now | ||
| 624 | Set<FieldEntry> fieldEntries = Sets.newHashSet(); | ||
| 625 | for (EntryReference<FieldEntry,BehaviorEntry> reference : m_fieldReferences.values()) { | ||
| 626 | if (reference.context == behaviorEntry) { | ||
| 627 | fieldEntries.add(reference.entry); | ||
| 628 | } | ||
| 629 | } | ||
| 630 | return fieldEntries; | ||
| 631 | } | ||
| 632 | |||
| 633 | public Collection<EntryReference<BehaviorEntry,BehaviorEntry>> getBehaviorReferences(BehaviorEntry behaviorEntry) { | ||
| 634 | return m_behaviorReferences.get(behaviorEntry); | ||
| 635 | } | ||
| 636 | |||
| 637 | public Collection<BehaviorEntry> getReferencedBehaviors(BehaviorEntry behaviorEntry) { | ||
| 638 | // linear search is fast enough for now | ||
| 639 | Set<BehaviorEntry> behaviorEntries = Sets.newHashSet(); | ||
| 640 | for (EntryReference<BehaviorEntry,BehaviorEntry> reference : m_behaviorReferences.values()) { | ||
| 641 | if (reference.context == behaviorEntry) { | ||
| 642 | behaviorEntries.add(reference.entry); | ||
| 643 | } | ||
| 644 | } | ||
| 645 | return behaviorEntries; | ||
| 646 | } | ||
| 647 | |||
| 648 | public Collection<String> getInnerClasses(String obfOuterClassName) { | ||
| 649 | return m_innerClasses.get(obfOuterClassName); | ||
| 650 | } | ||
| 651 | |||
| 652 | public String getOuterClass(String obfInnerClassName) { | ||
| 653 | // make sure we use the right name | ||
| 654 | if (new ClassEntry(obfInnerClassName).getPackageName() != null) { | ||
| 655 | throw new IllegalArgumentException("Don't reference obfuscated inner classes using packages: " + obfInnerClassName); | ||
| 656 | } | ||
| 657 | return m_outerClasses.get(obfInnerClassName); | ||
| 658 | } | ||
| 659 | |||
| 660 | public boolean isAnonymousClass(String obfInnerClassName) { | ||
| 661 | return m_anonymousClasses.containsKey(obfInnerClassName); | ||
| 662 | } | ||
| 663 | |||
| 664 | public BehaviorEntry getAnonymousClassCaller(String obfInnerClassName) { | ||
| 665 | return m_anonymousClasses.get(obfInnerClassName); | ||
| 666 | } | ||
| 667 | |||
| 668 | public Set<String> getInterfaces(String className) { | ||
| 669 | Set<String> interfaceNames = new HashSet<String>(); | ||
| 670 | interfaceNames.addAll(m_interfaces.get(className)); | ||
| 671 | for (ClassEntry ancestor : m_translationIndex.getAncestry(new ClassEntry(className))) { | ||
| 672 | interfaceNames.addAll(m_interfaces.get(ancestor.getName())); | ||
| 673 | } | ||
| 674 | return interfaceNames; | ||
| 675 | } | ||
| 676 | |||
| 677 | public Set<String> getImplementingClasses(String targetInterfaceName) { | ||
| 678 | // linear search is fast enough for now | ||
| 679 | Set<String> classNames = Sets.newHashSet(); | ||
| 680 | for (Map.Entry<String,String> entry : m_interfaces.entries()) { | ||
| 681 | String className = entry.getKey(); | ||
| 682 | String interfaceName = entry.getValue(); | ||
| 683 | if (interfaceName.equals(targetInterfaceName)) { | ||
| 684 | classNames.add(className); | ||
| 685 | m_translationIndex.getSubclassNamesRecursively(classNames, new ClassEntry(className)); | ||
| 686 | } | ||
| 687 | } | ||
| 688 | return classNames; | ||
| 689 | } | ||
| 690 | |||
| 691 | public boolean isInterface(String className) { | ||
| 692 | return m_interfaces.containsValue(className); | ||
| 693 | } | ||
| 694 | |||
| 695 | public boolean containsObfClass(ClassEntry obfClassEntry) { | ||
| 696 | return m_obfClassEntries.contains(obfClassEntry); | ||
| 697 | } | ||
| 698 | |||
| 699 | public boolean containsObfField(FieldEntry obfFieldEntry) { | ||
| 700 | return m_access.containsKey(obfFieldEntry); | ||
| 701 | } | ||
| 702 | |||
| 703 | public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) { | ||
| 704 | return m_access.containsKey(obfBehaviorEntry); | ||
| 705 | } | ||
| 706 | |||
| 707 | public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) { | ||
| 708 | // check the behavior | ||
| 709 | if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) { | ||
| 710 | return false; | ||
| 711 | } | ||
| 712 | |||
| 713 | // check the argument | ||
| 714 | if (obfArgumentEntry.getIndex() >= obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size()) { | ||
| 715 | return false; | ||
| 716 | } | ||
| 717 | |||
| 718 | return true; | ||
| 719 | } | ||
| 720 | |||
| 721 | public boolean containsObfEntry(Entry obfEntry) { | ||
| 722 | if (obfEntry instanceof ClassEntry) { | ||
| 723 | return containsObfClass((ClassEntry)obfEntry); | ||
| 724 | } else if (obfEntry instanceof FieldEntry) { | ||
| 725 | return containsObfField((FieldEntry)obfEntry); | ||
| 726 | } else if (obfEntry instanceof BehaviorEntry) { | ||
| 727 | return containsObfBehavior((BehaviorEntry)obfEntry); | ||
| 728 | } else if (obfEntry instanceof ArgumentEntry) { | ||
| 729 | return containsObfArgument((ArgumentEntry)obfEntry); | ||
| 730 | } else { | ||
| 731 | throw new Error("Entry type not supported: " + obfEntry.getClass().getName()); | ||
| 732 | } | ||
| 733 | } | ||
| 734 | } | ||
diff --git a/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java new file mode 100644 index 0000000..1009226 --- /dev/null +++ b/src/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java | |||
| @@ -0,0 +1,100 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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 | // get all method implementations | ||
| 67 | List<MethodImplementationsTreeNode> nodes = Lists.newArrayList(); | ||
| 68 | for (String implementingClassName : index.getImplementingClasses(m_entry.getClassName())) { | ||
| 69 | MethodEntry methodEntry = new MethodEntry( | ||
| 70 | new ClassEntry(implementingClassName), | ||
| 71 | m_entry.getName(), | ||
| 72 | m_entry.getSignature() | ||
| 73 | ); | ||
| 74 | if (index.containsObfBehavior(methodEntry)) { | ||
| 75 | nodes.add(new MethodImplementationsTreeNode(m_deobfuscatingTranslator, methodEntry)); | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | // add them to this node | ||
| 80 | for (MethodImplementationsTreeNode node : nodes) { | ||
| 81 | this.add(node); | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | public static MethodImplementationsTreeNode findNode(MethodImplementationsTreeNode node, MethodEntry entry) { | ||
| 86 | // is this the node? | ||
| 87 | if (node.getMethodEntry().equals(entry)) { | ||
| 88 | return node; | ||
| 89 | } | ||
| 90 | |||
| 91 | // recurse | ||
| 92 | for (int i = 0; i < node.getChildCount(); i++) { | ||
| 93 | MethodImplementationsTreeNode foundNode = findNode((MethodImplementationsTreeNode)node.getChildAt(i), entry); | ||
| 94 | if (foundNode != null) { | ||
| 95 | return foundNode; | ||
| 96 | } | ||
| 97 | } | ||
| 98 | return null; | ||
| 99 | } | ||
| 100 | } | ||
diff --git a/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java new file mode 100644 index 0000000..8718220 --- /dev/null +++ b/src/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java | |||
| @@ -0,0 +1,114 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..2b08616 --- /dev/null +++ b/src/cuchaz/enigma/analysis/ReferenceTreeNode.java | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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/SourceIndex.java b/src/cuchaz/enigma/analysis/SourceIndex.java new file mode 100644 index 0000000..b43ab61 --- /dev/null +++ b/src/cuchaz/enigma/analysis/SourceIndex.java | |||
| @@ -0,0 +1,173 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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 | |||
| 36 | public SourceIndex(String source) { | ||
| 37 | m_source = source; | ||
| 38 | m_tokenToReference = Maps.newTreeMap(); | ||
| 39 | m_referenceToTokens = HashMultimap.create(); | ||
| 40 | m_declarationToToken = Maps.newHashMap(); | ||
| 41 | m_lineOffsets = Lists.newArrayList(); | ||
| 42 | |||
| 43 | // count the lines | ||
| 44 | m_lineOffsets.add(0); | ||
| 45 | for (int i = 0; i < source.length(); i++) { | ||
| 46 | if (source.charAt(i) == '\n') { | ||
| 47 | m_lineOffsets.add(i + 1); | ||
| 48 | } | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | public String getSource() { | ||
| 53 | return m_source; | ||
| 54 | } | ||
| 55 | |||
| 56 | public Token getToken(AstNode node) { | ||
| 57 | |||
| 58 | // get the text of the node | ||
| 59 | String name = ""; | ||
| 60 | if (node instanceof Identifier) { | ||
| 61 | name = ((Identifier)node).getName(); | ||
| 62 | } | ||
| 63 | |||
| 64 | // get a token for this node's region | ||
| 65 | Region region = node.getRegion(); | ||
| 66 | if (region.getBeginLine() == 0 || region.getEndLine() == 0) { | ||
| 67 | // DEBUG | ||
| 68 | System.err.println(String.format("WARNING: %s \"%s\" has invalid region: %s", node.getNodeType(), name, region)); | ||
| 69 | return null; | ||
| 70 | } | ||
| 71 | Token token = new Token( | ||
| 72 | toPos(region.getBeginLine(), region.getBeginColumn()), | ||
| 73 | toPos(region.getEndLine(), region.getEndColumn()), | ||
| 74 | m_source | ||
| 75 | ); | ||
| 76 | if (token.start == 0) { | ||
| 77 | // DEBUG | ||
| 78 | System.err.println(String.format("WARNING: %s \"%s\" has invalid start: %s", node.getNodeType(), name, region)); | ||
| 79 | return null; | ||
| 80 | } | ||
| 81 | |||
| 82 | // DEBUG | ||
| 83 | // System.out.println( String.format( "%s \"%s\" region: %s", node.getNodeType(), name, region ) ); | ||
| 84 | |||
| 85 | // for tokens representing inner classes, make sure we only get the simple name | ||
| 86 | int pos = name.lastIndexOf('$'); | ||
| 87 | if (pos >= 0) { | ||
| 88 | token.end -= pos + 1; | ||
| 89 | } | ||
| 90 | |||
| 91 | return token; | ||
| 92 | } | ||
| 93 | |||
| 94 | public void addReference(AstNode node, Entry deobfEntry, Entry deobfContext) { | ||
| 95 | Token token = getToken(node); | ||
| 96 | if (token != null) { | ||
| 97 | EntryReference<Entry,Entry> deobfReference = new EntryReference<Entry,Entry>(deobfEntry, token.text, deobfContext); | ||
| 98 | m_tokenToReference.put(token, deobfReference); | ||
| 99 | m_referenceToTokens.put(deobfReference, token); | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | public void addDeclaration(AstNode node, Entry deobfEntry) { | ||
| 104 | Token token = getToken(node); | ||
| 105 | if (token != null) { | ||
| 106 | EntryReference<Entry,Entry> reference = new EntryReference<Entry,Entry>(deobfEntry, token.text); | ||
| 107 | m_tokenToReference.put(token, reference); | ||
| 108 | m_referenceToTokens.put(reference, token); | ||
| 109 | m_declarationToToken.put(deobfEntry, token); | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | public Token getReferenceToken(int pos) { | ||
| 114 | Token token = m_tokenToReference.floorKey(new Token(pos, pos, null)); | ||
| 115 | if (token != null && token.contains(pos)) { | ||
| 116 | return token; | ||
| 117 | } | ||
| 118 | return null; | ||
| 119 | } | ||
| 120 | |||
| 121 | public Collection<Token> getReferenceTokens(EntryReference<Entry,Entry> deobfReference) { | ||
| 122 | return m_referenceToTokens.get(deobfReference); | ||
| 123 | } | ||
| 124 | |||
| 125 | public EntryReference<Entry,Entry> getDeobfReference(Token token) { | ||
| 126 | if (token == null) { | ||
| 127 | return null; | ||
| 128 | } | ||
| 129 | return m_tokenToReference.get(token); | ||
| 130 | } | ||
| 131 | |||
| 132 | public void replaceDeobfReference(Token token, EntryReference<Entry,Entry> newDeobfReference) { | ||
| 133 | EntryReference<Entry,Entry> oldDeobfReference = m_tokenToReference.get(token); | ||
| 134 | m_tokenToReference.put(token, newDeobfReference); | ||
| 135 | Collection<Token> tokens = m_referenceToTokens.get(oldDeobfReference); | ||
| 136 | m_referenceToTokens.removeAll(oldDeobfReference); | ||
| 137 | m_referenceToTokens.putAll(newDeobfReference, tokens); | ||
| 138 | } | ||
| 139 | |||
| 140 | public Iterable<Token> referenceTokens() { | ||
| 141 | return m_tokenToReference.keySet(); | ||
| 142 | } | ||
| 143 | |||
| 144 | public Iterable<Token> declarationTokens() { | ||
| 145 | return m_declarationToToken.values(); | ||
| 146 | } | ||
| 147 | |||
| 148 | public Token getDeclarationToken(Entry deobfEntry) { | ||
| 149 | return m_declarationToToken.get(deobfEntry); | ||
| 150 | } | ||
| 151 | |||
| 152 | public int getLineNumber(int pos) { | ||
| 153 | // line number is 1-based | ||
| 154 | int line = 0; | ||
| 155 | for (Integer offset : m_lineOffsets) { | ||
| 156 | if (offset > pos) { | ||
| 157 | break; | ||
| 158 | } | ||
| 159 | line++; | ||
| 160 | } | ||
| 161 | return line; | ||
| 162 | } | ||
| 163 | |||
| 164 | public int getColumnNumber(int pos) { | ||
| 165 | // column number is 1-based | ||
| 166 | return pos - m_lineOffsets.get(getLineNumber(pos) - 1) + 1; | ||
| 167 | } | ||
| 168 | |||
| 169 | private int toPos(int line, int col) { | ||
| 170 | // line and col are 1-based | ||
| 171 | return m_lineOffsets.get(line - 1) + col - 1; | ||
| 172 | } | ||
| 173 | } | ||
diff --git a/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java new file mode 100644 index 0000000..4155128 --- /dev/null +++ b/src/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java | |||
| @@ -0,0 +1,164 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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.ConstructorDeclaration; | ||
| 21 | import com.strobel.decompiler.languages.java.ast.IdentifierExpression; | ||
| 22 | import com.strobel.decompiler.languages.java.ast.InvocationExpression; | ||
| 23 | import com.strobel.decompiler.languages.java.ast.Keys; | ||
| 24 | import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; | ||
| 25 | import com.strobel.decompiler.languages.java.ast.MethodDeclaration; | ||
| 26 | import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; | ||
| 27 | import com.strobel.decompiler.languages.java.ast.ParameterDeclaration; | ||
| 28 | import com.strobel.decompiler.languages.java.ast.SimpleType; | ||
| 29 | import com.strobel.decompiler.languages.java.ast.SuperReferenceExpression; | ||
| 30 | import com.strobel.decompiler.languages.java.ast.ThisReferenceExpression; | ||
| 31 | |||
| 32 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 33 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 34 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 35 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 36 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 37 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 38 | import cuchaz.enigma.mapping.Signature; | ||
| 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 visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { | ||
| 50 | return recurse(node, index); | ||
| 51 | } | ||
| 52 | |||
| 53 | @Override | ||
| 54 | public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { | ||
| 55 | return recurse(node, index); | ||
| 56 | } | ||
| 57 | |||
| 58 | @Override | ||
| 59 | public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { | ||
| 60 | MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); | ||
| 61 | |||
| 62 | // get the behavior entry | ||
| 63 | ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); | ||
| 64 | BehaviorEntry behaviorEntry = null; | ||
| 65 | if (ref instanceof MethodReference) { | ||
| 66 | MethodReference methodRef = (MethodReference)ref; | ||
| 67 | if (methodRef.isConstructor()) { | ||
| 68 | behaviorEntry = new ConstructorEntry(classEntry, new Signature(ref.getSignature())); | ||
| 69 | } else if (methodRef.isTypeInitializer()) { | ||
| 70 | behaviorEntry = new ConstructorEntry(classEntry); | ||
| 71 | } else { | ||
| 72 | behaviorEntry = new MethodEntry(classEntry, ref.getName(), new Signature(ref.getSignature())); | ||
| 73 | } | ||
| 74 | } | ||
| 75 | if (behaviorEntry != null) { | ||
| 76 | // get the node for the token | ||
| 77 | AstNode tokenNode = null; | ||
| 78 | if (node.getTarget() instanceof MemberReferenceExpression) { | ||
| 79 | tokenNode = ((MemberReferenceExpression)node.getTarget()).getMemberNameToken(); | ||
| 80 | } else if (node.getTarget() instanceof SuperReferenceExpression) { | ||
| 81 | tokenNode = node.getTarget(); | ||
| 82 | } else if (node.getTarget() instanceof ThisReferenceExpression) { | ||
| 83 | tokenNode = node.getTarget(); | ||
| 84 | } | ||
| 85 | if (tokenNode != null) { | ||
| 86 | index.addReference(tokenNode, behaviorEntry, m_behaviorEntry); | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | return recurse(node, index); | ||
| 91 | } | ||
| 92 | |||
| 93 | @Override | ||
| 94 | public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { | ||
| 95 | MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); | ||
| 96 | if (ref != null) { | ||
| 97 | // make sure this is actually a field | ||
| 98 | if (ref.getSignature().indexOf('(') >= 0) { | ||
| 99 | throw new Error("Expected a field here! got " + ref); | ||
| 100 | } | ||
| 101 | |||
| 102 | ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); | ||
| 103 | FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName()); | ||
| 104 | index.addReference(node.getMemberNameToken(), fieldEntry, m_behaviorEntry); | ||
| 105 | } | ||
| 106 | |||
| 107 | return recurse(node, index); | ||
| 108 | } | ||
| 109 | |||
| 110 | @Override | ||
| 111 | public Void visitSimpleType(SimpleType node, SourceIndex index) { | ||
| 112 | TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); | ||
| 113 | if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { | ||
| 114 | ClassEntry classEntry = new ClassEntry(ref.getInternalName()); | ||
| 115 | index.addReference(node.getIdentifierToken(), classEntry, m_behaviorEntry); | ||
| 116 | } | ||
| 117 | |||
| 118 | return recurse(node, index); | ||
| 119 | } | ||
| 120 | |||
| 121 | @Override | ||
| 122 | public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { | ||
| 123 | ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); | ||
| 124 | ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); | ||
| 125 | MethodDefinition methodDef = (MethodDefinition)def.getMethod(); | ||
| 126 | BehaviorEntry behaviorEntry; | ||
| 127 | if (methodDef.isConstructor()) { | ||
| 128 | behaviorEntry = new ConstructorEntry(classEntry, new Signature(methodDef.getSignature())); | ||
| 129 | } else { | ||
| 130 | behaviorEntry = new MethodEntry(classEntry, methodDef.getName(), new Signature(methodDef.getSignature())); | ||
| 131 | } | ||
| 132 | ArgumentEntry argumentEntry = new ArgumentEntry(behaviorEntry, def.getPosition(), node.getName()); | ||
| 133 | index.addDeclaration(node.getNameToken(), argumentEntry); | ||
| 134 | |||
| 135 | return recurse(node, index); | ||
| 136 | } | ||
| 137 | |||
| 138 | @Override | ||
| 139 | public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { | ||
| 140 | MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); | ||
| 141 | if (ref != null) { | ||
| 142 | ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); | ||
| 143 | FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName()); | ||
| 144 | index.addReference(node.getIdentifierToken(), fieldEntry, m_behaviorEntry); | ||
| 145 | } | ||
| 146 | |||
| 147 | return recurse(node, index); | ||
| 148 | } | ||
| 149 | |||
| 150 | @Override | ||
| 151 | public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { | ||
| 152 | MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); | ||
| 153 | if (ref != null) { | ||
| 154 | ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); | ||
| 155 | ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(ref.getSignature())); | ||
| 156 | if (node.getType() instanceof SimpleType) { | ||
| 157 | SimpleType simpleTypeNode = (SimpleType)node.getType(); | ||
| 158 | index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, m_behaviorEntry); | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 162 | return recurse(node, index); | ||
| 163 | } | ||
| 164 | } | ||
diff --git a/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java new file mode 100644 index 0000000..7222035 --- /dev/null +++ b/src/cuchaz/enigma/analysis/SourceIndexClassVisitor.java | |||
| @@ -0,0 +1,115 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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.BehaviorEntryFactory; | ||
| 30 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 31 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 32 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 33 | import cuchaz.enigma.mapping.Signature; | ||
| 34 | |||
| 35 | public class SourceIndexClassVisitor extends SourceIndexVisitor { | ||
| 36 | |||
| 37 | private ClassEntry m_classEntry; | ||
| 38 | |||
| 39 | public SourceIndexClassVisitor(ClassEntry classEntry) { | ||
| 40 | m_classEntry = classEntry; | ||
| 41 | } | ||
| 42 | |||
| 43 | @Override | ||
| 44 | public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { | ||
| 45 | // is this this class, or a subtype? | ||
| 46 | TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); | ||
| 47 | ClassEntry classEntry = new ClassEntry(def.getInternalName()); | ||
| 48 | if (!classEntry.equals(m_classEntry)) { | ||
| 49 | // it's a sub-type, recurse | ||
| 50 | index.addDeclaration(node.getNameToken(), classEntry); | ||
| 51 | return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); | ||
| 52 | } | ||
| 53 | |||
| 54 | return recurse(node, index); | ||
| 55 | } | ||
| 56 | |||
| 57 | @Override | ||
| 58 | public Void visitSimpleType(SimpleType node, SourceIndex index) { | ||
| 59 | TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); | ||
| 60 | if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { | ||
| 61 | ClassEntry classEntry = new ClassEntry(ref.getInternalName()); | ||
| 62 | index.addReference(node.getIdentifierToken(), classEntry, m_classEntry); | ||
| 63 | } | ||
| 64 | |||
| 65 | return recurse(node, index); | ||
| 66 | } | ||
| 67 | |||
| 68 | @Override | ||
| 69 | public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { | ||
| 70 | MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); | ||
| 71 | ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); | ||
| 72 | BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(classEntry, def.getName(), def.getSignature()); | ||
| 73 | AstNode tokenNode = node.getNameToken(); | ||
| 74 | if (behaviorEntry instanceof ConstructorEntry) { | ||
| 75 | ConstructorEntry constructorEntry = (ConstructorEntry)behaviorEntry; | ||
| 76 | if (constructorEntry.isStatic()) { | ||
| 77 | tokenNode = node.getModifiers().firstOrNullObject(); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | index.addDeclaration(tokenNode, behaviorEntry); | ||
| 81 | return node.acceptVisitor(new SourceIndexBehaviorVisitor(behaviorEntry), index); | ||
| 82 | } | ||
| 83 | |||
| 84 | @Override | ||
| 85 | public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { | ||
| 86 | MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); | ||
| 87 | ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); | ||
| 88 | ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(def.getSignature())); | ||
| 89 | index.addDeclaration(node.getNameToken(), constructorEntry); | ||
| 90 | return node.acceptVisitor(new SourceIndexBehaviorVisitor(constructorEntry), index); | ||
| 91 | } | ||
| 92 | |||
| 93 | @Override | ||
| 94 | public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { | ||
| 95 | FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); | ||
| 96 | ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); | ||
| 97 | FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName()); | ||
| 98 | assert (node.getVariables().size() == 1); | ||
| 99 | VariableInitializer variable = node.getVariables().firstOrNullObject(); | ||
| 100 | index.addDeclaration(variable.getNameToken(), fieldEntry); | ||
| 101 | |||
| 102 | return recurse(node, index); | ||
| 103 | } | ||
| 104 | |||
| 105 | @Override | ||
| 106 | public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { | ||
| 107 | // treat enum declarations as field declarations | ||
| 108 | FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); | ||
| 109 | ClassEntry classEntry = new ClassEntry(def.getDeclaringType().getInternalName()); | ||
| 110 | FieldEntry fieldEntry = new FieldEntry(classEntry, def.getName()); | ||
| 111 | index.addDeclaration(node.getNameToken(), fieldEntry); | ||
| 112 | |||
| 113 | return recurse(node, index); | ||
| 114 | } | ||
| 115 | } | ||
diff --git a/src/cuchaz/enigma/analysis/SourceIndexVisitor.java b/src/cuchaz/enigma/analysis/SourceIndexVisitor.java new file mode 100644 index 0000000..0d5bdc0 --- /dev/null +++ b/src/cuchaz/enigma/analysis/SourceIndexVisitor.java | |||
| @@ -0,0 +1,452 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..481d2f4 --- /dev/null +++ b/src/cuchaz/enigma/analysis/Token.java | |||
| @@ -0,0 +1,56 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..7597c3a --- /dev/null +++ b/src/cuchaz/enigma/analysis/TranslationIndex.java | |||
| @@ -0,0 +1,227 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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.HashMap; | ||
| 20 | import java.util.List; | ||
| 21 | import java.util.Map; | ||
| 22 | import java.util.Set; | ||
| 23 | import java.util.zip.GZIPInputStream; | ||
| 24 | import java.util.zip.GZIPOutputStream; | ||
| 25 | |||
| 26 | import javassist.CtBehavior; | ||
| 27 | import javassist.CtClass; | ||
| 28 | import javassist.CtField; | ||
| 29 | |||
| 30 | import com.google.common.collect.HashMultimap; | ||
| 31 | import com.google.common.collect.Lists; | ||
| 32 | import com.google.common.collect.Maps; | ||
| 33 | import com.google.common.collect.Multimap; | ||
| 34 | |||
| 35 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 36 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 37 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 38 | import cuchaz.enigma.mapping.Entry; | ||
| 39 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 40 | import cuchaz.enigma.mapping.JavassistUtil; | ||
| 41 | import cuchaz.enigma.mapping.Translator; | ||
| 42 | |||
| 43 | public class TranslationIndex implements Serializable { | ||
| 44 | |||
| 45 | private static final long serialVersionUID = 738687982126844179L; | ||
| 46 | |||
| 47 | private Map<ClassEntry,ClassEntry> m_superclasses; | ||
| 48 | private Multimap<ClassEntry,FieldEntry> m_fieldEntries; | ||
| 49 | private Multimap<ClassEntry,BehaviorEntry> m_behaviorEntries; | ||
| 50 | |||
| 51 | public TranslationIndex() { | ||
| 52 | m_superclasses = Maps.newHashMap(); | ||
| 53 | m_fieldEntries = HashMultimap.create(); | ||
| 54 | m_behaviorEntries = HashMultimap.create(); | ||
| 55 | } | ||
| 56 | |||
| 57 | public TranslationIndex(TranslationIndex other, Translator translator) { | ||
| 58 | |||
| 59 | // translate the superclasses | ||
| 60 | m_superclasses = Maps.newHashMap(); | ||
| 61 | for (Map.Entry<ClassEntry,ClassEntry> mapEntry : other.m_superclasses.entrySet()) { | ||
| 62 | m_superclasses.put( | ||
| 63 | translator.translateEntry(mapEntry.getKey()), | ||
| 64 | translator.translateEntry(mapEntry.getValue()) | ||
| 65 | ); | ||
| 66 | } | ||
| 67 | |||
| 68 | // translate the fields | ||
| 69 | m_fieldEntries = HashMultimap.create(); | ||
| 70 | for (Map.Entry<ClassEntry,FieldEntry> mapEntry : other.m_fieldEntries.entries()) { | ||
| 71 | m_fieldEntries.put( | ||
| 72 | translator.translateEntry(mapEntry.getKey()), | ||
| 73 | translator.translateEntry(mapEntry.getValue()) | ||
| 74 | ); | ||
| 75 | } | ||
| 76 | |||
| 77 | m_behaviorEntries = HashMultimap.create(); | ||
| 78 | for (Map.Entry<ClassEntry,BehaviorEntry> mapEntry : other.m_behaviorEntries.entries()) { | ||
| 79 | m_behaviorEntries.put( | ||
| 80 | translator.translateEntry(mapEntry.getKey()), | ||
| 81 | translator.translateEntry(mapEntry.getValue()) | ||
| 82 | ); | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | public void indexClass(CtClass c) { | ||
| 87 | |||
| 88 | ClassEntry classEntry = JavassistUtil.getClassEntry(c); | ||
| 89 | |||
| 90 | // add the superclass | ||
| 91 | ClassEntry superclassEntry = JavassistUtil.getSuperclassEntry(c); | ||
| 92 | if (!isJre(classEntry) && superclassEntry != null && !isJre(superclassEntry)) { | ||
| 93 | m_superclasses.put(classEntry, superclassEntry); | ||
| 94 | } | ||
| 95 | |||
| 96 | // add fields | ||
| 97 | for (CtField field : c.getDeclaredFields()) { | ||
| 98 | FieldEntry fieldEntry = JavassistUtil.getFieldEntry(field); | ||
| 99 | m_fieldEntries.put(fieldEntry.getClassEntry(), fieldEntry); | ||
| 100 | } | ||
| 101 | |||
| 102 | // add behaviors | ||
| 103 | for (CtBehavior behavior : c.getDeclaredBehaviors()) { | ||
| 104 | BehaviorEntry behaviorEntry = JavassistUtil.getBehaviorEntry(behavior); | ||
| 105 | m_behaviorEntries.put(behaviorEntry.getClassEntry(), behaviorEntry); | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | public void renameClasses(Map<String,String> renames) { | ||
| 110 | EntryRenamer.renameClassesInMap(renames, m_superclasses); | ||
| 111 | EntryRenamer.renameClassesInMultimap(renames, m_fieldEntries); | ||
| 112 | EntryRenamer.renameClassesInMultimap(renames, m_behaviorEntries); | ||
| 113 | } | ||
| 114 | |||
| 115 | public ClassEntry getSuperclass(ClassEntry classEntry) { | ||
| 116 | return m_superclasses.get(classEntry); | ||
| 117 | } | ||
| 118 | |||
| 119 | public List<ClassEntry> getAncestry(ClassEntry classEntry) { | ||
| 120 | List<ClassEntry> ancestors = Lists.newArrayList(); | ||
| 121 | while (classEntry != null) { | ||
| 122 | classEntry = getSuperclass(classEntry); | ||
| 123 | if (classEntry != null) { | ||
| 124 | ancestors.add(classEntry); | ||
| 125 | } | ||
| 126 | } | ||
| 127 | return ancestors; | ||
| 128 | } | ||
| 129 | |||
| 130 | public List<ClassEntry> getSubclass(ClassEntry classEntry) { | ||
| 131 | // linear search is fast enough for now | ||
| 132 | List<ClassEntry> subclasses = Lists.newArrayList(); | ||
| 133 | for (Map.Entry<ClassEntry,ClassEntry> entry : m_superclasses.entrySet()) { | ||
| 134 | ClassEntry subclass = entry.getKey(); | ||
| 135 | ClassEntry superclass = entry.getValue(); | ||
| 136 | if (classEntry.equals(superclass)) { | ||
| 137 | subclasses.add(subclass); | ||
| 138 | } | ||
| 139 | } | ||
| 140 | return subclasses; | ||
| 141 | } | ||
| 142 | |||
| 143 | public void getSubclassesRecursively(Set<ClassEntry> out, ClassEntry classEntry) { | ||
| 144 | for (ClassEntry subclassEntry : getSubclass(classEntry)) { | ||
| 145 | out.add(subclassEntry); | ||
| 146 | getSubclassesRecursively(out, subclassEntry); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | public void getSubclassNamesRecursively(Set<String> out, ClassEntry classEntry) { | ||
| 151 | for (ClassEntry subclassEntry : getSubclass(classEntry)) { | ||
| 152 | out.add(subclassEntry.getName()); | ||
| 153 | getSubclassNamesRecursively(out, subclassEntry); | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | public boolean entryExists(Entry entry) { | ||
| 158 | if (entry instanceof FieldEntry) { | ||
| 159 | return fieldExists((FieldEntry)entry); | ||
| 160 | } else if (entry instanceof BehaviorEntry) { | ||
| 161 | return behaviorExists((BehaviorEntry)entry); | ||
| 162 | } else if (entry instanceof ArgumentEntry) { | ||
| 163 | return behaviorExists(((ArgumentEntry)entry).getBehaviorEntry()); | ||
| 164 | } | ||
| 165 | throw new IllegalArgumentException("Cannot check existence for " + entry.getClass()); | ||
| 166 | } | ||
| 167 | |||
| 168 | public boolean fieldExists(FieldEntry fieldEntry) { | ||
| 169 | return m_fieldEntries.containsEntry(fieldEntry.getClassEntry(), fieldEntry); | ||
| 170 | } | ||
| 171 | |||
| 172 | public boolean behaviorExists(BehaviorEntry behaviorEntry) { | ||
| 173 | return m_behaviorEntries.containsEntry(behaviorEntry.getClassEntry(), behaviorEntry); | ||
| 174 | } | ||
| 175 | |||
| 176 | public ClassEntry resolveEntryClass(Entry entry) { | ||
| 177 | |||
| 178 | if (entry instanceof ClassEntry) { | ||
| 179 | return (ClassEntry)entry; | ||
| 180 | } | ||
| 181 | |||
| 182 | // this entry could refer to a method on a class where the method is not actually implemented | ||
| 183 | // travel up the inheritance tree to find the closest implementation | ||
| 184 | while (!entryExists(entry)) { | ||
| 185 | |||
| 186 | // is there a parent class? | ||
| 187 | ClassEntry superclassEntry = getSuperclass(entry.getClassEntry()); | ||
| 188 | if (superclassEntry == null) { | ||
| 189 | // this is probably a method from a class in a library | ||
| 190 | // we can't trace the implementation up any higher unless we index the library | ||
| 191 | return null; | ||
| 192 | } | ||
| 193 | |||
| 194 | // move up to the parent class | ||
| 195 | entry = entry.cloneToNewClass(superclassEntry); | ||
| 196 | } | ||
| 197 | return entry.getClassEntry(); | ||
| 198 | } | ||
| 199 | |||
| 200 | private boolean isJre(ClassEntry classEntry) { | ||
| 201 | String packageName = classEntry.getPackageName(); | ||
| 202 | return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax")); | ||
| 203 | } | ||
| 204 | |||
| 205 | public void write(OutputStream out) | ||
| 206 | throws IOException { | ||
| 207 | GZIPOutputStream gzipout = new GZIPOutputStream(out); | ||
| 208 | ObjectOutputStream oout = new ObjectOutputStream(gzipout); | ||
| 209 | oout.writeObject(m_superclasses); | ||
| 210 | oout.writeObject(m_fieldEntries); | ||
| 211 | oout.writeObject(m_behaviorEntries); | ||
| 212 | gzipout.finish(); | ||
| 213 | } | ||
| 214 | |||
| 215 | @SuppressWarnings("unchecked") | ||
| 216 | public void read(InputStream in) | ||
| 217 | throws IOException { | ||
| 218 | try { | ||
| 219 | ObjectInputStream oin = new ObjectInputStream(new GZIPInputStream(in)); | ||
| 220 | m_superclasses = (HashMap<ClassEntry,ClassEntry>)oin.readObject(); | ||
| 221 | m_fieldEntries = (HashMultimap<ClassEntry,FieldEntry>)oin.readObject(); | ||
| 222 | m_behaviorEntries = (HashMultimap<ClassEntry,BehaviorEntry>)oin.readObject(); | ||
| 223 | } catch (ClassNotFoundException ex) { | ||
| 224 | throw new Error(ex); | ||
| 225 | } | ||
| 226 | } | ||
| 227 | } | ||
diff --git a/src/cuchaz/enigma/analysis/TreeDumpVisitor.java b/src/cuchaz/enigma/analysis/TreeDumpVisitor.java new file mode 100644 index 0000000..23f8089 --- /dev/null +++ b/src/cuchaz/enigma/analysis/TreeDumpVisitor.java | |||
| @@ -0,0 +1,512 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..5284557 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/CheckCastIterator.java | |||
| @@ -0,0 +1,127 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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/ClassRenamer.java b/src/cuchaz/enigma/bytecode/ClassRenamer.java new file mode 100644 index 0000000..a5fea92 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/ClassRenamer.java | |||
| @@ -0,0 +1,110 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import java.util.Map; | ||
| 14 | import java.util.Set; | ||
| 15 | |||
| 16 | import javassist.ClassMap; | ||
| 17 | import javassist.CtClass; | ||
| 18 | import javassist.bytecode.ConstPool; | ||
| 19 | import javassist.bytecode.Descriptor; | ||
| 20 | import javassist.bytecode.InnerClassesAttribute; | ||
| 21 | |||
| 22 | import com.google.common.collect.Maps; | ||
| 23 | import com.google.common.collect.Sets; | ||
| 24 | |||
| 25 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 26 | |||
| 27 | public class ClassRenamer { | ||
| 28 | |||
| 29 | public static void renameClasses(CtClass c, Map<ClassEntry,ClassEntry> map) { | ||
| 30 | |||
| 31 | // build the map used by javassist | ||
| 32 | ClassMap nameMap = new ClassMap(); | ||
| 33 | for (Map.Entry<ClassEntry,ClassEntry> entry : map.entrySet()) { | ||
| 34 | nameMap.put(entry.getKey().getName(), entry.getValue().getName()); | ||
| 35 | } | ||
| 36 | |||
| 37 | c.replaceClassName(nameMap); | ||
| 38 | |||
| 39 | // replace simple names in the InnerClasses attribute too | ||
| 40 | ConstPool constants = c.getClassFile().getConstPool(); | ||
| 41 | InnerClassesAttribute attr = (InnerClassesAttribute)c.getClassFile().getAttribute(InnerClassesAttribute.tag); | ||
| 42 | if (attr != null) { | ||
| 43 | for (int i = 0; i < attr.tableLength(); i++) { | ||
| 44 | ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(attr.innerClass(i))); | ||
| 45 | if (attr.innerNameIndex(i) != 0) { | ||
| 46 | attr.setInnerNameIndex(i, constants.addUtf8Info(classEntry.getInnerClassName())); | ||
| 47 | } | ||
| 48 | |||
| 49 | /* DEBUG | ||
| 50 | System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i))); | ||
| 51 | */ | ||
| 52 | } | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | public static Set<ClassEntry> getAllClassEntries(final CtClass c) { | ||
| 57 | |||
| 58 | // get the classes that javassist knows about | ||
| 59 | final Set<ClassEntry> entries = Sets.newHashSet(); | ||
| 60 | ClassMap map = new ClassMap() { | ||
| 61 | @Override | ||
| 62 | public Object get(Object obj) { | ||
| 63 | if (obj instanceof String) { | ||
| 64 | String str = (String)obj; | ||
| 65 | |||
| 66 | // javassist throws a lot of weird things at this map | ||
| 67 | // I either have to implement my on class scanner, or just try to filter out the weirdness | ||
| 68 | // I'm opting to filter out the weirdness for now | ||
| 69 | |||
| 70 | // skip anything with generic arguments | ||
| 71 | if (str.indexOf('<') >= 0 || str.indexOf('>') >= 0 || str.indexOf(';') >= 0) { | ||
| 72 | return null; | ||
| 73 | } | ||
| 74 | |||
| 75 | // convert path/to/class.inner to path/to/class$inner | ||
| 76 | str = str.replace('.', '$'); | ||
| 77 | |||
| 78 | // remember everything else | ||
| 79 | entries.add(new ClassEntry(str)); | ||
| 80 | } | ||
| 81 | return null; | ||
| 82 | } | ||
| 83 | |||
| 84 | private static final long serialVersionUID = -202160293602070641L; | ||
| 85 | }; | ||
| 86 | c.replaceClassName(map); | ||
| 87 | |||
| 88 | return entries; | ||
| 89 | } | ||
| 90 | |||
| 91 | public static void moveAllClassesOutOfDefaultPackage(CtClass c, String newPackageName) { | ||
| 92 | Map<ClassEntry,ClassEntry> map = Maps.newHashMap(); | ||
| 93 | for (ClassEntry classEntry : ClassRenamer.getAllClassEntries(c)) { | ||
| 94 | if (classEntry.isInDefaultPackage()) { | ||
| 95 | map.put(classEntry, new ClassEntry(newPackageName + "/" + classEntry.getName())); | ||
| 96 | } | ||
| 97 | } | ||
| 98 | ClassRenamer.renameClasses(c, map); | ||
| 99 | } | ||
| 100 | |||
| 101 | public static void moveAllClassesIntoDefaultPackage(CtClass c, String oldPackageName) { | ||
| 102 | Map<ClassEntry,ClassEntry> map = Maps.newHashMap(); | ||
| 103 | for (ClassEntry classEntry : ClassRenamer.getAllClassEntries(c)) { | ||
| 104 | if (classEntry.getPackageName().equals(oldPackageName)) { | ||
| 105 | map.put(classEntry, new ClassEntry(classEntry.getSimpleName())); | ||
| 106 | } | ||
| 107 | } | ||
| 108 | ClassRenamer.renameClasses(c, map); | ||
| 109 | } | ||
| 110 | } | ||
diff --git a/src/cuchaz/enigma/bytecode/ClassTranslator.java b/src/cuchaz/enigma/bytecode/ClassTranslator.java new file mode 100644 index 0000000..afd3a77 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/ClassTranslator.java | |||
| @@ -0,0 +1,144 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import java.util.Map; | ||
| 14 | |||
| 15 | import javassist.CtBehavior; | ||
| 16 | import javassist.CtClass; | ||
| 17 | import javassist.CtField; | ||
| 18 | import javassist.CtMethod; | ||
| 19 | import javassist.bytecode.ConstPool; | ||
| 20 | import javassist.bytecode.Descriptor; | ||
| 21 | import javassist.bytecode.SourceFileAttribute; | ||
| 22 | |||
| 23 | import com.google.common.collect.Maps; | ||
| 24 | |||
| 25 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 26 | import cuchaz.enigma.mapping.BehaviorEntryFactory; | ||
| 27 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 28 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 29 | import cuchaz.enigma.mapping.JavassistUtil; | ||
| 30 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 31 | import cuchaz.enigma.mapping.Signature; | ||
| 32 | import cuchaz.enigma.mapping.Translator; | ||
| 33 | import cuchaz.enigma.mapping.Type; | ||
| 34 | |||
| 35 | public class ClassTranslator { | ||
| 36 | |||
| 37 | private Translator m_translator; | ||
| 38 | |||
| 39 | public ClassTranslator(Translator translator) { | ||
| 40 | m_translator = translator; | ||
| 41 | } | ||
| 42 | |||
| 43 | public void translate(CtClass c) { | ||
| 44 | |||
| 45 | // NOTE: the order of these translations is very important | ||
| 46 | |||
| 47 | // translate all the field and method references in the code by editing the constant pool | ||
| 48 | ConstPool constants = c.getClassFile().getConstPool(); | ||
| 49 | ConstPoolEditor editor = new ConstPoolEditor(constants); | ||
| 50 | for (int i = 1; i < constants.getSize(); i++) { | ||
| 51 | switch (constants.getTag(i)) { | ||
| 52 | |||
| 53 | case ConstPool.CONST_Fieldref: { | ||
| 54 | |||
| 55 | // translate the name | ||
| 56 | FieldEntry entry = new FieldEntry( | ||
| 57 | new ClassEntry(Descriptor.toJvmName(constants.getFieldrefClassName(i))), | ||
| 58 | constants.getFieldrefName(i) | ||
| 59 | ); | ||
| 60 | FieldEntry translatedEntry = m_translator.translateEntry(entry); | ||
| 61 | |||
| 62 | // translate the type | ||
| 63 | Type type = new Type(constants.getFieldrefType(i)); | ||
| 64 | Type translatedType = m_translator.translateType(type); | ||
| 65 | |||
| 66 | if (!entry.equals(translatedEntry) || !type.equals(translatedType)) { | ||
| 67 | editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedType.toString()); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | break; | ||
| 71 | |||
| 72 | case ConstPool.CONST_Methodref: | ||
| 73 | case ConstPool.CONST_InterfaceMethodref: { | ||
| 74 | |||
| 75 | // translate the name and type | ||
| 76 | BehaviorEntry entry = BehaviorEntryFactory.create( | ||
| 77 | Descriptor.toJvmName(editor.getMemberrefClassname(i)), | ||
| 78 | editor.getMemberrefName(i), | ||
| 79 | editor.getMemberrefType(i) | ||
| 80 | ); | ||
| 81 | BehaviorEntry translatedEntry = m_translator.translateEntry(entry); | ||
| 82 | |||
| 83 | if (!entry.getName().equals(translatedEntry.getName()) || !entry.getSignature().equals(translatedEntry.getSignature())) { | ||
| 84 | editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString()); | ||
| 85 | } | ||
| 86 | } | ||
| 87 | break; | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); | ||
| 92 | |||
| 93 | // translate all the fields | ||
| 94 | for (CtField field : c.getDeclaredFields()) { | ||
| 95 | |||
| 96 | // translate the name | ||
| 97 | FieldEntry entry = new FieldEntry(classEntry, field.getName()); | ||
| 98 | String translatedName = m_translator.translate(entry); | ||
| 99 | if (translatedName != null) { | ||
| 100 | field.setName(translatedName); | ||
| 101 | } | ||
| 102 | |||
| 103 | // translate the type | ||
| 104 | Type translatedType = m_translator.translateType(new Type(field.getFieldInfo().getDescriptor())); | ||
| 105 | field.getFieldInfo().setDescriptor(translatedType.toString()); | ||
| 106 | } | ||
| 107 | |||
| 108 | // translate all the methods and constructors | ||
| 109 | for (CtBehavior behavior : c.getDeclaredBehaviors()) { | ||
| 110 | if (behavior instanceof CtMethod) { | ||
| 111 | CtMethod method = (CtMethod)behavior; | ||
| 112 | |||
| 113 | // translate the name | ||
| 114 | MethodEntry entry = JavassistUtil.getMethodEntry(method); | ||
| 115 | String translatedName = m_translator.translate(entry); | ||
| 116 | if (translatedName != null) { | ||
| 117 | method.setName(translatedName); | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | // translate the type | ||
| 122 | Signature translatedSignature = m_translator.translateSignature(new Signature(behavior.getMethodInfo().getDescriptor())); | ||
| 123 | behavior.getMethodInfo().setDescriptor(translatedSignature.toString()); | ||
| 124 | } | ||
| 125 | |||
| 126 | // translate all the class names referenced in the code | ||
| 127 | // the above code only changed method/field/reference names and types, but not the class names themselves | ||
| 128 | Map<ClassEntry,ClassEntry> map = Maps.newHashMap(); | ||
| 129 | for (ClassEntry obfClassEntry : ClassRenamer.getAllClassEntries(c)) { | ||
| 130 | ClassEntry deobfClassEntry = m_translator.translateEntry(obfClassEntry); | ||
| 131 | if (!obfClassEntry.equals(deobfClassEntry)) { | ||
| 132 | map.put(obfClassEntry, deobfClassEntry); | ||
| 133 | } | ||
| 134 | } | ||
| 135 | ClassRenamer.renameClasses(c, map); | ||
| 136 | |||
| 137 | // translate the source file attribute too | ||
| 138 | ClassEntry deobfClassEntry = map.get(classEntry); | ||
| 139 | if (deobfClassEntry != null) { | ||
| 140 | String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOuterClassName()) + ".java"; | ||
| 141 | c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile)); | ||
| 142 | } | ||
| 143 | } | ||
| 144 | } | ||
diff --git a/src/cuchaz/enigma/bytecode/ConstPoolEditor.java b/src/cuchaz/enigma/bytecode/ConstPoolEditor.java new file mode 100644 index 0000000..2dec3b7 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/ConstPoolEditor.java | |||
| @@ -0,0 +1,263 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..deaf623 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/InfoType.java | |||
| @@ -0,0 +1,317 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..817500b --- /dev/null +++ b/src/cuchaz/enigma/bytecode/InnerClassWriter.java | |||
| @@ -0,0 +1,102 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.bytecode; | ||
| 12 | |||
| 13 | import java.util.Collection; | ||
| 14 | |||
| 15 | import javassist.CtClass; | ||
| 16 | import javassist.bytecode.AccessFlag; | ||
| 17 | import javassist.bytecode.ConstPool; | ||
| 18 | import javassist.bytecode.Descriptor; | ||
| 19 | import javassist.bytecode.EnclosingMethodAttribute; | ||
| 20 | import javassist.bytecode.InnerClassesAttribute; | ||
| 21 | import cuchaz.enigma.Constants; | ||
| 22 | import cuchaz.enigma.analysis.JarIndex; | ||
| 23 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 24 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 25 | |||
| 26 | public class InnerClassWriter { | ||
| 27 | |||
| 28 | private JarIndex m_jarIndex; | ||
| 29 | |||
| 30 | public InnerClassWriter(JarIndex jarIndex) { | ||
| 31 | m_jarIndex = jarIndex; | ||
| 32 | } | ||
| 33 | |||
| 34 | public void write(CtClass c) { | ||
| 35 | |||
| 36 | // is this an inner or outer class? | ||
| 37 | String obfInnerClassName = new ClassEntry(Descriptor.toJvmName(c.getName())).getSimpleName(); | ||
| 38 | String obfOuterClassName = m_jarIndex.getOuterClass(obfInnerClassName); | ||
| 39 | if (obfOuterClassName == null) { | ||
| 40 | // this is an outer class | ||
| 41 | obfOuterClassName = Descriptor.toJvmName(c.getName()); | ||
| 42 | } else { | ||
| 43 | // this is an inner class, rename it to outer$inner | ||
| 44 | ClassEntry obfClassEntry = new ClassEntry(obfOuterClassName + "$" + obfInnerClassName); | ||
| 45 | c.setName(obfClassEntry.getName()); | ||
| 46 | |||
| 47 | BehaviorEntry caller = m_jarIndex.getAnonymousClassCaller(obfInnerClassName); | ||
| 48 | if (caller != null) { | ||
| 49 | // write the enclosing method attribute | ||
| 50 | if (caller.getName().equals("<clinit>")) { | ||
| 51 | c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName())); | ||
| 52 | } else { | ||
| 53 | c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature().toString())); | ||
| 54 | } | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | // write the inner classes if needed | ||
| 59 | Collection<String> obfInnerClassNames = m_jarIndex.getInnerClasses(obfOuterClassName); | ||
| 60 | if (obfInnerClassNames != null && !obfInnerClassNames.isEmpty()) { | ||
| 61 | writeInnerClasses(c, obfOuterClassName, obfInnerClassNames); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | private void writeInnerClasses(CtClass c, String obfOuterClassName, Collection<String> obfInnerClassNames) { | ||
| 66 | InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool()); | ||
| 67 | c.getClassFile().addAttribute(attr); | ||
| 68 | for (String obfInnerClassName : obfInnerClassNames) { | ||
| 69 | // get the new inner class name | ||
| 70 | ClassEntry obfClassEntry = new ClassEntry(obfOuterClassName + "$" + obfInnerClassName); | ||
| 71 | |||
| 72 | // here's what the JVM spec says about the InnerClasses attribute | ||
| 73 | // append( inner, outer of inner if inner is member of outer 0 ow, name after $ if inner not anonymous 0 ow, flags ); | ||
| 74 | |||
| 75 | // update the attribute with this inner class | ||
| 76 | ConstPool constPool = c.getClassFile().getConstPool(); | ||
| 77 | int innerClassIndex = constPool.addClassInfo(obfClassEntry.getName()); | ||
| 78 | int outerClassIndex = 0; | ||
| 79 | int innerClassSimpleNameIndex = 0; | ||
| 80 | if (!m_jarIndex.isAnonymousClass(obfInnerClassName)) { | ||
| 81 | outerClassIndex = constPool.addClassInfo(obfClassEntry.getOuterClassName()); | ||
| 82 | innerClassSimpleNameIndex = constPool.addUtf8Info(obfClassEntry.getInnerClassName()); | ||
| 83 | } | ||
| 84 | |||
| 85 | attr.append(innerClassIndex, outerClassIndex, innerClassSimpleNameIndex, c.getClassFile().getAccessFlags() & ~AccessFlag.SUPER); | ||
| 86 | |||
| 87 | /* DEBUG | ||
| 88 | System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)", | ||
| 89 | obfClassEntry, | ||
| 90 | attr.outerClass(attr.tableLength() - 1), | ||
| 91 | attr.innerClass(attr.tableLength() - 1), | ||
| 92 | attr.innerName(attr.tableLength() - 1), | ||
| 93 | Constants.NonePackage + "/" + obfInnerClassName, | ||
| 94 | obfClassEntry.getName() | ||
| 95 | )); | ||
| 96 | */ | ||
| 97 | |||
| 98 | // make sure the outer class references only the new inner class names | ||
| 99 | c.replaceClassName(Constants.NonePackage + "/" + obfInnerClassName, obfClassEntry.getName()); | ||
| 100 | } | ||
| 101 | } | ||
| 102 | } | ||
diff --git a/src/cuchaz/enigma/bytecode/MethodParameterWriter.java b/src/cuchaz/enigma/bytecode/MethodParameterWriter.java new file mode 100644 index 0000000..5d4ca1a --- /dev/null +++ b/src/cuchaz/enigma/bytecode/MethodParameterWriter.java | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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 cuchaz.enigma.mapping.ArgumentEntry; | ||
| 19 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 20 | import cuchaz.enigma.mapping.BehaviorEntryFactory; | ||
| 21 | import cuchaz.enigma.mapping.Translator; | ||
| 22 | |||
| 23 | public class MethodParameterWriter { | ||
| 24 | |||
| 25 | private Translator m_translator; | ||
| 26 | |||
| 27 | public MethodParameterWriter(Translator translator) { | ||
| 28 | m_translator = translator; | ||
| 29 | } | ||
| 30 | |||
| 31 | public void writeMethodArguments(CtClass c) { | ||
| 32 | |||
| 33 | // Procyon will read method arguments from the "MethodParameters" attribute, so write those | ||
| 34 | for (CtBehavior behavior : c.getDeclaredBehaviors()) { | ||
| 35 | BehaviorEntry behaviorEntry = BehaviorEntryFactory.create(behavior); | ||
| 36 | |||
| 37 | // get the number of arguments | ||
| 38 | int numParams = behaviorEntry.getSignature().getArgumentTypes().size(); | ||
| 39 | if (numParams <= 0) { | ||
| 40 | continue; | ||
| 41 | } | ||
| 42 | |||
| 43 | // get the list of argument names | ||
| 44 | List<String> names = new ArrayList<String>(numParams); | ||
| 45 | for (int i = 0; i < numParams; i++) { | ||
| 46 | names.add(m_translator.translate(new ArgumentEntry(behaviorEntry, i, ""))); | ||
| 47 | } | ||
| 48 | |||
| 49 | // save the mappings to the class | ||
| 50 | MethodParametersAttribute.updateClass(behavior.getMethodInfo(), names); | ||
| 51 | } | ||
| 52 | } | ||
| 53 | } | ||
diff --git a/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java b/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java new file mode 100644 index 0000000..bf95956 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/MethodParametersAttribute.java | |||
| @@ -0,0 +1,85 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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 | // add the names to the class const pool | ||
| 31 | ConstPool constPool = info.getConstPool(); | ||
| 32 | List<Integer> parameterNameIndices = new ArrayList<Integer>(); | ||
| 33 | for (String name : names) { | ||
| 34 | if (name != null) { | ||
| 35 | parameterNameIndices.add(constPool.addUtf8Info(name)); | ||
| 36 | } else { | ||
| 37 | parameterNameIndices.add(0); | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | // add the attribute to the method | ||
| 42 | info.addAttribute(new MethodParametersAttribute(constPool, parameterNameIndices)); | ||
| 43 | } | ||
| 44 | |||
| 45 | private static byte[] writeStruct(List<Integer> parameterNameIndices) { | ||
| 46 | // JVM 8 Spec says the struct looks like this: | ||
| 47 | // http://cr.openjdk.java.net/~mr/se/8/java-se-8-fr-spec-01/java-se-8-jvms-fr-diffs.pdf | ||
| 48 | // uint8 num_params | ||
| 49 | // for each param: | ||
| 50 | // uint16 name_index -> points to UTF8 entry in constant pool, or 0 for no entry | ||
| 51 | // uint16 access_flags -> don't care, just set to 0 | ||
| 52 | |||
| 53 | ByteArrayOutputStream buf = new ByteArrayOutputStream(); | ||
| 54 | DataOutputStream out = new DataOutputStream(buf); | ||
| 55 | |||
| 56 | // NOTE: java hates unsigned integers, so we have to be careful here | ||
| 57 | // the writeShort(), writeByte() methods will read 16,8 low-order bits from the int argument | ||
| 58 | // as long as the int argument is in range of the unsigned short/byte type, it will be written as an unsigned short/byte | ||
| 59 | // if the int is out of range, the byte stream won't look the way we want and weird things will happen | ||
| 60 | final int SIZEOF_UINT8 = 1; | ||
| 61 | final int SIZEOF_UINT16 = 2; | ||
| 62 | final int MAX_UINT8 = (1 << 8) - 1; | ||
| 63 | final int MAX_UINT16 = (1 << 16) - 1; | ||
| 64 | |||
| 65 | try { | ||
| 66 | assert (parameterNameIndices.size() >= 0 && parameterNameIndices.size() <= MAX_UINT8); | ||
| 67 | out.writeByte(parameterNameIndices.size()); | ||
| 68 | |||
| 69 | for (Integer index : parameterNameIndices) { | ||
| 70 | assert (index >= 0 && index <= MAX_UINT16); | ||
| 71 | out.writeShort(index); | ||
| 72 | |||
| 73 | // just write 0 for the access flags | ||
| 74 | out.writeShort(0); | ||
| 75 | } | ||
| 76 | |||
| 77 | out.close(); | ||
| 78 | byte[] data = buf.toByteArray(); | ||
| 79 | assert (data.length == SIZEOF_UINT8 + parameterNameIndices.size() * (SIZEOF_UINT16 + SIZEOF_UINT16)); | ||
| 80 | return data; | ||
| 81 | } catch (IOException ex) { | ||
| 82 | throw new Error(ex); | ||
| 83 | } | ||
| 84 | } | ||
| 85 | } | ||
diff --git a/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java b/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java new file mode 100644 index 0000000..d76f056 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..d00c102 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java | |||
| @@ -0,0 +1,156 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..0d780ea --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java | |||
| @@ -0,0 +1,74 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..9fe945f --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java | |||
| @@ -0,0 +1,74 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..4c95b22 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java | |||
| @@ -0,0 +1,74 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..e151117 --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..6e82f3e --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java | |||
| @@ -0,0 +1,74 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..6665ffe --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..2abf60b --- /dev/null +++ b/src/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java new file mode 100644 index 0000000..1be6123 --- /dev/null +++ b/src/cuchaz/enigma/convert/ClassIdentity.java | |||
| @@ -0,0 +1,411 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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 | |||
| 20 | import javassist.CannotCompileException; | ||
| 21 | import javassist.CtBehavior; | ||
| 22 | import javassist.CtClass; | ||
| 23 | import javassist.CtConstructor; | ||
| 24 | import javassist.CtField; | ||
| 25 | import javassist.CtMethod; | ||
| 26 | import javassist.bytecode.BadBytecode; | ||
| 27 | import javassist.bytecode.CodeIterator; | ||
| 28 | import javassist.bytecode.ConstPool; | ||
| 29 | import javassist.bytecode.Descriptor; | ||
| 30 | import javassist.bytecode.Opcode; | ||
| 31 | import javassist.expr.ConstructorCall; | ||
| 32 | import javassist.expr.ExprEditor; | ||
| 33 | import javassist.expr.FieldAccess; | ||
| 34 | import javassist.expr.MethodCall; | ||
| 35 | import javassist.expr.NewExpr; | ||
| 36 | |||
| 37 | import com.google.common.collect.HashMultiset; | ||
| 38 | import com.google.common.collect.Lists; | ||
| 39 | import com.google.common.collect.Maps; | ||
| 40 | import com.google.common.collect.Multiset; | ||
| 41 | |||
| 42 | import cuchaz.enigma.Constants; | ||
| 43 | import cuchaz.enigma.Util; | ||
| 44 | import cuchaz.enigma.analysis.ClassImplementationsTreeNode; | ||
| 45 | import cuchaz.enigma.analysis.EntryReference; | ||
| 46 | import cuchaz.enigma.analysis.JarIndex; | ||
| 47 | import cuchaz.enigma.bytecode.ConstPoolEditor; | ||
| 48 | import cuchaz.enigma.bytecode.InfoType; | ||
| 49 | import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; | ||
| 50 | import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; | ||
| 51 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 52 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 53 | import cuchaz.enigma.mapping.ClassNameReplacer; | ||
| 54 | import cuchaz.enigma.mapping.Entry; | ||
| 55 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 56 | import cuchaz.enigma.mapping.JavassistUtil; | ||
| 57 | import cuchaz.enigma.mapping.Signature; | ||
| 58 | |||
| 59 | public class ClassIdentity { | ||
| 60 | |||
| 61 | private ClassEntry m_classEntry; | ||
| 62 | private SidedClassNamer m_namer; | ||
| 63 | private Multiset<String> m_fields; | ||
| 64 | private Multiset<String> m_methods; | ||
| 65 | private Multiset<String> m_constructors; | ||
| 66 | private String m_staticInitializer; | ||
| 67 | private String m_extends; | ||
| 68 | private Multiset<String> m_implements; | ||
| 69 | private Multiset<String> m_implementations; | ||
| 70 | private Multiset<String> m_references; | ||
| 71 | |||
| 72 | public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) { | ||
| 73 | m_namer = namer; | ||
| 74 | |||
| 75 | // stuff from the bytecode | ||
| 76 | |||
| 77 | m_classEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); | ||
| 78 | m_fields = HashMultiset.create(); | ||
| 79 | for (CtField field : c.getDeclaredFields()) { | ||
| 80 | m_fields.add(scrubSignature(field.getSignature())); | ||
| 81 | } | ||
| 82 | m_methods = HashMultiset.create(); | ||
| 83 | for (CtMethod method : c.getDeclaredMethods()) { | ||
| 84 | m_methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method)); | ||
| 85 | } | ||
| 86 | m_constructors = HashMultiset.create(); | ||
| 87 | for (CtConstructor constructor : c.getDeclaredConstructors()) { | ||
| 88 | m_constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor)); | ||
| 89 | } | ||
| 90 | m_staticInitializer = ""; | ||
| 91 | if (c.getClassInitializer() != null) { | ||
| 92 | m_staticInitializer = getBehaviorSignature(c.getClassInitializer()); | ||
| 93 | } | ||
| 94 | m_extends = ""; | ||
| 95 | if (c.getClassFile().getSuperclass() != null) { | ||
| 96 | m_extends = scrubClassName(c.getClassFile().getSuperclass()); | ||
| 97 | } | ||
| 98 | m_implements = HashMultiset.create(); | ||
| 99 | for (String interfaceName : c.getClassFile().getInterfaces()) { | ||
| 100 | m_implements.add(scrubClassName(interfaceName)); | ||
| 101 | } | ||
| 102 | |||
| 103 | // stuff from the jar index | ||
| 104 | |||
| 105 | m_implementations = HashMultiset.create(); | ||
| 106 | ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, m_classEntry); | ||
| 107 | if (implementationsNode != null) { | ||
| 108 | @SuppressWarnings("unchecked") | ||
| 109 | Enumeration<ClassImplementationsTreeNode> implementations = implementationsNode.children(); | ||
| 110 | while (implementations.hasMoreElements()) { | ||
| 111 | ClassImplementationsTreeNode node = implementations.nextElement(); | ||
| 112 | m_implementations.add(scrubClassName(node.getClassEntry().getName())); | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | m_references = HashMultiset.create(); | ||
| 117 | if (useReferences) { | ||
| 118 | for (CtField field : c.getDeclaredFields()) { | ||
| 119 | FieldEntry fieldEntry = new FieldEntry(m_classEntry, field.getName()); | ||
| 120 | for (EntryReference<FieldEntry,BehaviorEntry> reference : index.getFieldReferences(fieldEntry)) { | ||
| 121 | addReference(reference); | ||
| 122 | } | ||
| 123 | } | ||
| 124 | for (CtBehavior behavior : c.getDeclaredBehaviors()) { | ||
| 125 | BehaviorEntry behaviorEntry = JavassistUtil.getBehaviorEntry(behavior); | ||
| 126 | for (EntryReference<BehaviorEntry,BehaviorEntry> reference : index.getBehaviorReferences(behaviorEntry)) { | ||
| 127 | addReference(reference); | ||
| 128 | } | ||
| 129 | } | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | private void addReference(EntryReference<? extends Entry,BehaviorEntry> reference) { | ||
| 134 | if (reference.context.getSignature() != null) { | ||
| 135 | m_references.add(String.format("%s_%s", scrubClassName(reference.context.getClassName()), scrubSignature(reference.context.getSignature()))); | ||
| 136 | } else { | ||
| 137 | m_references.add(String.format("%s_<clinit>", scrubClassName(reference.context.getClassName()))); | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | public ClassEntry getClassEntry() { | ||
| 142 | return m_classEntry; | ||
| 143 | } | ||
| 144 | |||
| 145 | @Override | ||
| 146 | public String toString() { | ||
| 147 | StringBuilder buf = new StringBuilder(); | ||
| 148 | buf.append("class: "); | ||
| 149 | buf.append(m_classEntry.getName()); | ||
| 150 | buf.append(" "); | ||
| 151 | buf.append(hashCode()); | ||
| 152 | buf.append("\n"); | ||
| 153 | for (String field : m_fields) { | ||
| 154 | buf.append("\tfield "); | ||
| 155 | buf.append(field); | ||
| 156 | buf.append("\n"); | ||
| 157 | } | ||
| 158 | for (String method : m_methods) { | ||
| 159 | buf.append("\tmethod "); | ||
| 160 | buf.append(method); | ||
| 161 | buf.append("\n"); | ||
| 162 | } | ||
| 163 | for (String constructor : m_constructors) { | ||
| 164 | buf.append("\tconstructor "); | ||
| 165 | buf.append(constructor); | ||
| 166 | buf.append("\n"); | ||
| 167 | } | ||
| 168 | if (m_staticInitializer.length() > 0) { | ||
| 169 | buf.append("\tinitializer "); | ||
| 170 | buf.append(m_staticInitializer); | ||
| 171 | buf.append("\n"); | ||
| 172 | } | ||
| 173 | if (m_extends.length() > 0) { | ||
| 174 | buf.append("\textends "); | ||
| 175 | buf.append(m_extends); | ||
| 176 | buf.append("\n"); | ||
| 177 | } | ||
| 178 | for (String interfaceName : m_implements) { | ||
| 179 | buf.append("\timplements "); | ||
| 180 | buf.append(interfaceName); | ||
| 181 | buf.append("\n"); | ||
| 182 | } | ||
| 183 | for (String implementation : m_implementations) { | ||
| 184 | buf.append("\timplemented by "); | ||
| 185 | buf.append(implementation); | ||
| 186 | buf.append("\n"); | ||
| 187 | } | ||
| 188 | for (String reference : m_references) { | ||
| 189 | buf.append("\treference "); | ||
| 190 | buf.append(reference); | ||
| 191 | buf.append("\n"); | ||
| 192 | } | ||
| 193 | return buf.toString(); | ||
| 194 | } | ||
| 195 | |||
| 196 | private String scrubClassName(String className) { | ||
| 197 | return scrubSignature("L" + Descriptor.toJvmName(className) + ";"); | ||
| 198 | } | ||
| 199 | |||
| 200 | private String scrubSignature(String signature) { | ||
| 201 | return scrubSignature(new Signature(signature)); | ||
| 202 | } | ||
| 203 | |||
| 204 | private String scrubSignature(Signature signature) { | ||
| 205 | |||
| 206 | return new Signature(signature, new ClassNameReplacer() { | ||
| 207 | |||
| 208 | private Map<String,String> m_classNames = Maps.newHashMap(); | ||
| 209 | |||
| 210 | @Override | ||
| 211 | public String replace(String className) { | ||
| 212 | |||
| 213 | // classes not in the none package can be passed through | ||
| 214 | ClassEntry classEntry = new ClassEntry(className); | ||
| 215 | if (!classEntry.getPackageName().equals(Constants.NonePackage)) { | ||
| 216 | return className; | ||
| 217 | } | ||
| 218 | |||
| 219 | // is this class ourself? | ||
| 220 | if (className.equals(m_classEntry.getName())) { | ||
| 221 | return "CSelf"; | ||
| 222 | } | ||
| 223 | |||
| 224 | // try the namer | ||
| 225 | if (m_namer != null) { | ||
| 226 | String newName = m_namer.getName(className); | ||
| 227 | if (newName != null) { | ||
| 228 | return newName; | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | // otherwise, use local naming | ||
| 233 | if (!m_classNames.containsKey(className)) { | ||
| 234 | m_classNames.put(className, getNewClassName()); | ||
| 235 | } | ||
| 236 | return m_classNames.get(className); | ||
| 237 | } | ||
| 238 | |||
| 239 | private String getNewClassName() { | ||
| 240 | return String.format("C%03d", m_classNames.size()); | ||
| 241 | } | ||
| 242 | }).toString(); | ||
| 243 | } | ||
| 244 | |||
| 245 | private boolean isClassMatchedUniquely(String className) { | ||
| 246 | return m_namer != null && m_namer.getName(Descriptor.toJvmName(className)) != null; | ||
| 247 | } | ||
| 248 | |||
| 249 | private String getBehaviorSignature(CtBehavior behavior) { | ||
| 250 | try { | ||
| 251 | // does this method have an implementation? | ||
| 252 | if (behavior.getMethodInfo().getCodeAttribute() == null) { | ||
| 253 | return "(none)"; | ||
| 254 | } | ||
| 255 | |||
| 256 | // compute the hash from the opcodes | ||
| 257 | ConstPool constants = behavior.getMethodInfo().getConstPool(); | ||
| 258 | final MessageDigest digest = MessageDigest.getInstance("MD5"); | ||
| 259 | CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator(); | ||
| 260 | while (iter.hasNext()) { | ||
| 261 | int pos = iter.next(); | ||
| 262 | |||
| 263 | // update the hash with the opcode | ||
| 264 | int opcode = iter.byteAt(pos); | ||
| 265 | digest.update((byte)opcode); | ||
| 266 | |||
| 267 | switch (opcode) { | ||
| 268 | case Opcode.LDC: { | ||
| 269 | int constIndex = iter.byteAt(pos + 1); | ||
| 270 | updateHashWithConstant(digest, constants, constIndex); | ||
| 271 | } | ||
| 272 | break; | ||
| 273 | |||
| 274 | case Opcode.LDC_W: | ||
| 275 | case Opcode.LDC2_W: { | ||
| 276 | int constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2); | ||
| 277 | updateHashWithConstant(digest, constants, constIndex); | ||
| 278 | } | ||
| 279 | break; | ||
| 280 | } | ||
| 281 | } | ||
| 282 | |||
| 283 | // update hash with method and field accesses | ||
| 284 | behavior.instrument(new ExprEditor() { | ||
| 285 | @Override | ||
| 286 | public void edit(MethodCall call) { | ||
| 287 | updateHashWithString(digest, scrubClassName(call.getClassName())); | ||
| 288 | updateHashWithString(digest, scrubSignature(call.getSignature())); | ||
| 289 | if (isClassMatchedUniquely(call.getClassName())) { | ||
| 290 | updateHashWithString(digest, call.getMethodName()); | ||
| 291 | } | ||
| 292 | } | ||
| 293 | |||
| 294 | @Override | ||
| 295 | public void edit(FieldAccess access) { | ||
| 296 | updateHashWithString(digest, scrubClassName(access.getClassName())); | ||
| 297 | updateHashWithString(digest, scrubSignature(access.getSignature())); | ||
| 298 | if (isClassMatchedUniquely(access.getClassName())) { | ||
| 299 | updateHashWithString(digest, access.getFieldName()); | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | @Override | ||
| 304 | public void edit(ConstructorCall call) { | ||
| 305 | updateHashWithString(digest, scrubClassName(call.getClassName())); | ||
| 306 | updateHashWithString(digest, scrubSignature(call.getSignature())); | ||
| 307 | } | ||
| 308 | |||
| 309 | @Override | ||
| 310 | public void edit(NewExpr expr) { | ||
| 311 | updateHashWithString(digest, scrubClassName(expr.getClassName())); | ||
| 312 | } | ||
| 313 | }); | ||
| 314 | |||
| 315 | // convert the hash to a hex string | ||
| 316 | return toHex(digest.digest()); | ||
| 317 | } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) { | ||
| 318 | throw new Error(ex); | ||
| 319 | } | ||
| 320 | } | ||
| 321 | |||
| 322 | private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) { | ||
| 323 | ConstPoolEditor editor = new ConstPoolEditor(constants); | ||
| 324 | ConstInfoAccessor item = editor.getItem(index); | ||
| 325 | if (item.getType() == InfoType.StringInfo) { | ||
| 326 | updateHashWithString(digest, constants.getStringInfo(index)); | ||
| 327 | } | ||
| 328 | // TODO: other constants | ||
| 329 | } | ||
| 330 | |||
| 331 | private void updateHashWithString(MessageDigest digest, String val) { | ||
| 332 | try { | ||
| 333 | digest.update(val.getBytes("UTF8")); | ||
| 334 | } catch (UnsupportedEncodingException ex) { | ||
| 335 | throw new Error(ex); | ||
| 336 | } | ||
| 337 | } | ||
| 338 | |||
| 339 | private String toHex(byte[] bytes) { | ||
| 340 | // function taken from: | ||
| 341 | // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java | ||
| 342 | final char[] hexArray = "0123456789ABCDEF".toCharArray(); | ||
| 343 | char[] hexChars = new char[bytes.length * 2]; | ||
| 344 | for (int j = 0; j < bytes.length; j++) { | ||
| 345 | int v = bytes[j] & 0xFF; | ||
| 346 | hexChars[j * 2] = hexArray[v >>> 4]; | ||
| 347 | hexChars[j * 2 + 1] = hexArray[v & 0x0F]; | ||
| 348 | } | ||
| 349 | return new String(hexChars); | ||
| 350 | } | ||
| 351 | |||
| 352 | @Override | ||
| 353 | public boolean equals(Object other) { | ||
| 354 | if (other instanceof ClassIdentity) { | ||
| 355 | return equals((ClassIdentity)other); | ||
| 356 | } | ||
| 357 | return false; | ||
| 358 | } | ||
| 359 | |||
| 360 | public boolean equals(ClassIdentity other) { | ||
| 361 | return m_fields.equals(other.m_fields) | ||
| 362 | && m_methods.equals(other.m_methods) | ||
| 363 | && m_constructors.equals(other.m_constructors) | ||
| 364 | && m_staticInitializer.equals(other.m_staticInitializer) | ||
| 365 | && m_extends.equals(other.m_extends) | ||
| 366 | && m_implements.equals(other.m_implements) | ||
| 367 | && m_implementations.equals(other.m_implementations) | ||
| 368 | && m_references.equals(other.m_references); | ||
| 369 | } | ||
| 370 | |||
| 371 | @Override | ||
| 372 | public int hashCode() { | ||
| 373 | List<Object> objs = Lists.newArrayList(); | ||
| 374 | objs.addAll(m_fields); | ||
| 375 | objs.addAll(m_methods); | ||
| 376 | objs.addAll(m_constructors); | ||
| 377 | objs.add(m_staticInitializer); | ||
| 378 | objs.add(m_extends); | ||
| 379 | objs.addAll(m_implements); | ||
| 380 | objs.addAll(m_implementations); | ||
| 381 | objs.addAll(m_references); | ||
| 382 | return Util.combineHashesOrdered(objs); | ||
| 383 | } | ||
| 384 | |||
| 385 | public int getMatchScore(ClassIdentity other) { | ||
| 386 | return getNumMatches(m_fields, other.m_fields) | ||
| 387 | + getNumMatches(m_methods, other.m_methods) | ||
| 388 | + getNumMatches(m_constructors, other.m_constructors); | ||
| 389 | } | ||
| 390 | |||
| 391 | public int getMaxMatchScore() { | ||
| 392 | return m_fields.size() + m_methods.size() + m_constructors.size(); | ||
| 393 | } | ||
| 394 | |||
| 395 | public boolean matches(CtClass c) { | ||
| 396 | // just compare declaration counts | ||
| 397 | return m_fields.size() == c.getDeclaredFields().length | ||
| 398 | && m_methods.size() == c.getDeclaredMethods().length | ||
| 399 | && m_constructors.size() == c.getDeclaredConstructors().length; | ||
| 400 | } | ||
| 401 | |||
| 402 | private int getNumMatches(Multiset<String> a, Multiset<String> b) { | ||
| 403 | int numMatches = 0; | ||
| 404 | for (String val : a) { | ||
| 405 | if (b.contains(val)) { | ||
| 406 | numMatches++; | ||
| 407 | } | ||
| 408 | } | ||
| 409 | return numMatches; | ||
| 410 | } | ||
| 411 | } | ||
diff --git a/src/cuchaz/enigma/convert/ClassMatcher.java b/src/cuchaz/enigma/convert/ClassMatcher.java new file mode 100644 index 0000000..ccf6b78 --- /dev/null +++ b/src/cuchaz/enigma/convert/ClassMatcher.java | |||
| @@ -0,0 +1,406 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 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.ArrayList; | ||
| 18 | import java.util.Arrays; | ||
| 19 | import java.util.Collection; | ||
| 20 | import java.util.Collections; | ||
| 21 | import java.util.Comparator; | ||
| 22 | import java.util.Iterator; | ||
| 23 | import java.util.LinkedHashMap; | ||
| 24 | import java.util.List; | ||
| 25 | import java.util.Map; | ||
| 26 | import java.util.Set; | ||
| 27 | import java.util.jar.JarFile; | ||
| 28 | |||
| 29 | import javassist.CtBehavior; | ||
| 30 | import javassist.CtClass; | ||
| 31 | |||
| 32 | import com.google.common.collect.ArrayListMultimap; | ||
| 33 | import com.google.common.collect.BiMap; | ||
| 34 | import com.google.common.collect.HashBiMap; | ||
| 35 | import com.google.common.collect.Lists; | ||
| 36 | import com.google.common.collect.Maps; | ||
| 37 | import com.google.common.collect.Multimap; | ||
| 38 | import com.google.common.collect.Sets; | ||
| 39 | |||
| 40 | import cuchaz.enigma.TranslatingTypeLoader; | ||
| 41 | import cuchaz.enigma.analysis.JarIndex; | ||
| 42 | import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; | ||
| 43 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 44 | import cuchaz.enigma.mapping.ClassMapping; | ||
| 45 | import cuchaz.enigma.mapping.JavassistUtil; | ||
| 46 | import cuchaz.enigma.mapping.MappingParseException; | ||
| 47 | import cuchaz.enigma.mapping.Mappings; | ||
| 48 | import cuchaz.enigma.mapping.MappingsReader; | ||
| 49 | import cuchaz.enigma.mapping.MappingsWriter; | ||
| 50 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 51 | import cuchaz.enigma.mapping.MethodMapping; | ||
| 52 | |||
| 53 | public class ClassMatcher { | ||
| 54 | |||
| 55 | public static void main(String[] args) throws IOException, MappingParseException { | ||
| 56 | // TEMP | ||
| 57 | JarFile sourceJar = new JarFile(new File("input/1.8-pre3.jar")); | ||
| 58 | JarFile destJar = new JarFile(new File("input/1.8.jar")); | ||
| 59 | File inMappingsFile = new File("../Enigma Mappings/1.8-pre3.mappings"); | ||
| 60 | File outMappingsFile = new File("../Enigma Mappings/1.8.mappings"); | ||
| 61 | |||
| 62 | // define a matching to use when the automated system cannot find a match | ||
| 63 | Map<String,String> fallbackMatching = Maps.newHashMap(); | ||
| 64 | fallbackMatching.put("none/ayb", "none/ayf"); | ||
| 65 | fallbackMatching.put("none/ayd", "none/ayd"); | ||
| 66 | fallbackMatching.put("none/bgk", "unknown/bgk"); | ||
| 67 | |||
| 68 | // do the conversion | ||
| 69 | Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile)); | ||
| 70 | convertMappings(sourceJar, destJar, mappings, fallbackMatching); | ||
| 71 | |||
| 72 | // write out the converted mappings | ||
| 73 | FileWriter writer = new FileWriter(outMappingsFile); | ||
| 74 | new MappingsWriter().write(writer, mappings); | ||
| 75 | writer.close(); | ||
| 76 | System.out.println("Wrote converted mappings to:\n\t" + outMappingsFile.getAbsolutePath()); | ||
| 77 | } | ||
| 78 | |||
| 79 | private static void convertMappings(JarFile sourceJar, JarFile destJar, Mappings mappings, Map<String,String> fallbackMatching) { | ||
| 80 | // index jars | ||
| 81 | System.out.println("Indexing source jar..."); | ||
| 82 | JarIndex sourceIndex = new JarIndex(); | ||
| 83 | sourceIndex.indexJar(sourceJar, false); | ||
| 84 | System.out.println("Indexing dest jar..."); | ||
| 85 | JarIndex destIndex = new JarIndex(); | ||
| 86 | destIndex.indexJar(destJar, false); | ||
| 87 | TranslatingTypeLoader sourceLoader = new TranslatingTypeLoader(sourceJar, sourceIndex); | ||
| 88 | TranslatingTypeLoader destLoader = new TranslatingTypeLoader(destJar, destIndex); | ||
| 89 | |||
| 90 | // compute the matching | ||
| 91 | ClassMatching matching = computeMatching(sourceIndex, sourceLoader, destIndex, destLoader); | ||
| 92 | Map<String,Map.Entry<ClassIdentity,List<ClassIdentity>>> matchingIndex = matching.getIndex(); | ||
| 93 | |||
| 94 | // get all the obf class names used in the mappings | ||
| 95 | Set<String> usedClassNames = mappings.getAllObfClassNames(); | ||
| 96 | Set<String> allClassNames = Sets.newHashSet(); | ||
| 97 | for (ClassEntry classEntry : sourceIndex.getObfClassEntries()) { | ||
| 98 | allClassNames.add(classEntry.getName()); | ||
| 99 | } | ||
| 100 | usedClassNames.retainAll(allClassNames); | ||
| 101 | System.out.println("Used " + usedClassNames.size() + " classes in the mappings"); | ||
| 102 | |||
| 103 | // probabilistically match the non-uniquely-matched source classes | ||
| 104 | for (Map.Entry<ClassIdentity,List<ClassIdentity>> entry : matchingIndex.values()) { | ||
| 105 | ClassIdentity sourceClass = entry.getKey(); | ||
| 106 | List<ClassIdentity> destClasses = entry.getValue(); | ||
| 107 | |||
| 108 | // skip classes that are uniquely matched | ||
| 109 | if (destClasses.size() == 1) { | ||
| 110 | continue; | ||
| 111 | } | ||
| 112 | |||
| 113 | // skip classes that aren't used in the mappings | ||
| 114 | if (!usedClassNames.contains(sourceClass.getClassEntry().getName())) { | ||
| 115 | continue; | ||
| 116 | } | ||
| 117 | |||
| 118 | System.out.println("No exact match for source class " + sourceClass.getClassEntry()); | ||
| 119 | |||
| 120 | // find the closest classes | ||
| 121 | Multimap<Integer,ClassIdentity> scoredMatches = ArrayListMultimap.create(); | ||
| 122 | for (ClassIdentity c : destClasses) { | ||
| 123 | scoredMatches.put(sourceClass.getMatchScore(c), c); | ||
| 124 | } | ||
| 125 | List<Integer> scores = new ArrayList<Integer>(scoredMatches.keySet()); | ||
| 126 | Collections.sort(scores, Collections.reverseOrder()); | ||
| 127 | printScoredMatches(sourceClass.getMaxMatchScore(), scores, scoredMatches); | ||
| 128 | |||
| 129 | // does the best match have a non-zero score and the same name? | ||
| 130 | int bestScore = scores.get(0); | ||
| 131 | Collection<ClassIdentity> bestMatches = scoredMatches.get(bestScore); | ||
| 132 | if (bestScore > 0 && bestMatches.size() == 1) { | ||
| 133 | ClassIdentity bestMatch = bestMatches.iterator().next(); | ||
| 134 | if (bestMatch.getClassEntry().equals(sourceClass.getClassEntry())) { | ||
| 135 | // use it | ||
| 136 | System.out.println("\tAutomatically choosing likely match: " + bestMatch.getClassEntry().getName()); | ||
| 137 | destClasses.clear(); | ||
| 138 | destClasses.add(bestMatch); | ||
| 139 | } | ||
| 140 | } | ||
| 141 | } | ||
| 142 | |||
| 143 | // group the matching into unique and non-unique matches | ||
| 144 | BiMap<String,String> matchedClassNames = HashBiMap.create(); | ||
| 145 | Set<String> unmatchedSourceClassNames = Sets.newHashSet(); | ||
| 146 | for (String className : usedClassNames) { | ||
| 147 | // is there a match for this class? | ||
| 148 | Map.Entry<ClassIdentity,List<ClassIdentity>> entry = matchingIndex.get(className); | ||
| 149 | ClassIdentity sourceClass = entry.getKey(); | ||
| 150 | List<ClassIdentity> matches = entry.getValue(); | ||
| 151 | |||
| 152 | if (matches.size() == 1) { | ||
| 153 | // unique match! We're good to go! | ||
| 154 | matchedClassNames.put(sourceClass.getClassEntry().getName(), matches.get(0).getClassEntry().getName()); | ||
| 155 | } else { | ||
| 156 | // no match, check the fallback matching | ||
| 157 | String fallbackMatch = fallbackMatching.get(className); | ||
| 158 | if (fallbackMatch != null) { | ||
| 159 | matchedClassNames.put(sourceClass.getClassEntry().getName(), fallbackMatch); | ||
| 160 | } else { | ||
| 161 | unmatchedSourceClassNames.add(className); | ||
| 162 | } | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | // report unmatched classes | ||
| 167 | if (!unmatchedSourceClassNames.isEmpty()) { | ||
| 168 | System.err.println("ERROR: there were unmatched classes!"); | ||
| 169 | for (String className : unmatchedSourceClassNames) { | ||
| 170 | System.err.println("\t" + className); | ||
| 171 | } | ||
| 172 | return; | ||
| 173 | } | ||
| 174 | |||
| 175 | // get the class name changes from the matched class names | ||
| 176 | Map<String,String> classChanges = Maps.newHashMap(); | ||
| 177 | for (Map.Entry<String,String> entry : matchedClassNames.entrySet()) { | ||
| 178 | if (!entry.getKey().equals(entry.getValue())) { | ||
| 179 | classChanges.put(entry.getKey(), entry.getValue()); | ||
| 180 | System.out.println(String.format("Class change: %s -> %s", entry.getKey(), entry.getValue())); | ||
| 181 | /* DEBUG | ||
| 182 | System.out.println(String.format("\n%s\n%s", | ||
| 183 | new ClassIdentity(sourceLoader.loadClass(entry.getKey()), null, sourceIndex, false, false), | ||
| 184 | new ClassIdentity( destLoader.loadClass(entry.getValue()), null, destIndex, false, false) | ||
| 185 | )); | ||
| 186 | */ | ||
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | // sort the changes so classes are renamed in the correct order | ||
| 191 | // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b | ||
| 192 | LinkedHashMap<String,String> orderedClassChanges = Maps.newLinkedHashMap(); | ||
| 193 | int numChangesLeft = classChanges.size(); | ||
| 194 | while (!classChanges.isEmpty()) { | ||
| 195 | Iterator<Map.Entry<String,String>> iter = classChanges.entrySet().iterator(); | ||
| 196 | while (iter.hasNext()) { | ||
| 197 | Map.Entry<String,String> entry = iter.next(); | ||
| 198 | if (classChanges.get(entry.getValue()) == null) { | ||
| 199 | orderedClassChanges.put(entry.getKey(), entry.getValue()); | ||
| 200 | iter.remove(); | ||
| 201 | } | ||
| 202 | } | ||
| 203 | |||
| 204 | // did we remove any changes? | ||
| 205 | if (numChangesLeft - classChanges.size() > 0) { | ||
| 206 | // keep going | ||
| 207 | numChangesLeft = classChanges.size(); | ||
| 208 | } else { | ||
| 209 | // can't sort anymore. There must be a loop | ||
| 210 | break; | ||
| 211 | } | ||
| 212 | } | ||
| 213 | if (classChanges.size() > 0) { | ||
| 214 | throw new Error(String.format("Unable to sort %d/%d class changes!", classChanges.size(), matchedClassNames.size())); | ||
| 215 | } | ||
| 216 | |||
| 217 | // convert the mappings in the correct class order | ||
| 218 | for (Map.Entry<String,String> entry : orderedClassChanges.entrySet()) { | ||
| 219 | mappings.renameObfClass(entry.getKey(), entry.getValue()); | ||
| 220 | } | ||
| 221 | |||
| 222 | // check the method matches | ||
| 223 | System.out.println("Checking methods..."); | ||
| 224 | for (ClassMapping classMapping : mappings.classes()) { | ||
| 225 | ClassEntry classEntry = new ClassEntry(classMapping.getObfName()); | ||
| 226 | for (MethodMapping methodMapping : classMapping.methods()) { | ||
| 227 | |||
| 228 | // skip constructors | ||
| 229 | if (methodMapping.getObfName().equals("<init>")) { | ||
| 230 | continue; | ||
| 231 | } | ||
| 232 | |||
| 233 | MethodEntry methodEntry = new MethodEntry( | ||
| 234 | classEntry, | ||
| 235 | methodMapping.getObfName(), | ||
| 236 | methodMapping.getObfSignature() | ||
| 237 | ); | ||
| 238 | if (!destIndex.containsObfBehavior(methodEntry)) { | ||
| 239 | System.err.println("WARNING: method doesn't match: " + methodEntry); | ||
| 240 | |||
| 241 | // show the available methods | ||
| 242 | System.err.println("\tAvailable dest methods:"); | ||
| 243 | CtClass c = destLoader.loadClass(classMapping.getObfName()); | ||
| 244 | for (CtBehavior behavior : c.getDeclaredBehaviors()) { | ||
| 245 | System.err.println("\t\t" + JavassistUtil.getBehaviorEntry(behavior)); | ||
| 246 | } | ||
| 247 | |||
| 248 | System.err.println("\tAvailable source methods:"); | ||
| 249 | c = sourceLoader.loadClass(matchedClassNames.inverse().get(classMapping.getObfName())); | ||
| 250 | for (CtBehavior behavior : c.getDeclaredBehaviors()) { | ||
| 251 | System.err.println("\t\t" + JavassistUtil.getBehaviorEntry(behavior)); | ||
| 252 | } | ||
| 253 | } | ||
| 254 | } | ||
| 255 | } | ||
| 256 | |||
| 257 | System.out.println("Done!"); | ||
| 258 | } | ||
| 259 | |||
| 260 | public static ClassMatching computeMatching(JarIndex sourceIndex, TranslatingTypeLoader sourceLoader, JarIndex destIndex, TranslatingTypeLoader destLoader) { | ||
| 261 | |||
| 262 | System.out.println("Matching classes..."); | ||
| 263 | |||
| 264 | ClassMatching matching = null; | ||
| 265 | for (boolean useReferences : Arrays.asList(false, true)) { | ||
| 266 | int numMatches = 0; | ||
| 267 | do { | ||
| 268 | SidedClassNamer sourceNamer = null; | ||
| 269 | SidedClassNamer destNamer = null; | ||
| 270 | if (matching != null) { | ||
| 271 | // build a class namer | ||
| 272 | ClassNamer namer = new ClassNamer(matching.getUniqueMatches()); | ||
| 273 | sourceNamer = namer.getSourceNamer(); | ||
| 274 | destNamer = namer.getDestNamer(); | ||
| 275 | |||
| 276 | // note the number of matches | ||
| 277 | numMatches = matching.getUniqueMatches().size(); | ||
| 278 | } | ||
| 279 | |||
| 280 | // get the entries left to match | ||
| 281 | Set<ClassEntry> sourceClassEntries = Sets.newHashSet(); | ||
| 282 | Set<ClassEntry> destClassEntries = Sets.newHashSet(); | ||
| 283 | if (matching == null) { | ||
| 284 | sourceClassEntries.addAll(sourceIndex.getObfClassEntries()); | ||
| 285 | destClassEntries.addAll(destIndex.getObfClassEntries()); | ||
| 286 | matching = new ClassMatching(); | ||
| 287 | } else { | ||
| 288 | for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : matching.getAmbiguousMatches().entrySet()) { | ||
| 289 | for (ClassIdentity c : entry.getKey()) { | ||
| 290 | sourceClassEntries.add(c.getClassEntry()); | ||
| 291 | matching.removeSource(c); | ||
| 292 | } | ||
| 293 | for (ClassIdentity c : entry.getValue()) { | ||
| 294 | destClassEntries.add(c.getClassEntry()); | ||
| 295 | matching.removeDest(c); | ||
| 296 | } | ||
| 297 | } | ||
| 298 | for (ClassIdentity c : matching.getUnmatchedSourceClasses()) { | ||
| 299 | sourceClassEntries.add(c.getClassEntry()); | ||
| 300 | matching.removeSource(c); | ||
| 301 | } | ||
| 302 | for (ClassIdentity c : matching.getUnmatchedDestClasses()) { | ||
| 303 | destClassEntries.add(c.getClassEntry()); | ||
| 304 | matching.removeDest(c); | ||
| 305 | } | ||
| 306 | } | ||
| 307 | |||
| 308 | // compute a matching for the classes | ||
| 309 | for (ClassEntry classEntry : sourceClassEntries) { | ||
| 310 | CtClass c = sourceLoader.loadClass(classEntry.getName()); | ||
| 311 | ClassIdentity sourceClass = new ClassIdentity(c, sourceNamer, sourceIndex, useReferences); | ||
| 312 | matching.addSource(sourceClass); | ||
| 313 | } | ||
| 314 | for (ClassEntry classEntry : destClassEntries) { | ||
| 315 | CtClass c = destLoader.loadClass(classEntry.getName()); | ||
| 316 | ClassIdentity destClass = new ClassIdentity(c, destNamer, destIndex, useReferences); | ||
| 317 | matching.matchDestClass(destClass); | ||
| 318 | } | ||
| 319 | |||
| 320 | // TEMP | ||
| 321 | System.out.println(matching); | ||
| 322 | } while (matching.getUniqueMatches().size() - numMatches > 0); | ||
| 323 | } | ||
| 324 | |||
| 325 | // check the class matches | ||
| 326 | System.out.println("Checking class matches..."); | ||
| 327 | ClassNamer namer = new ClassNamer(matching.getUniqueMatches()); | ||
| 328 | SidedClassNamer sourceNamer = namer.getSourceNamer(); | ||
| 329 | SidedClassNamer destNamer = namer.getDestNamer(); | ||
| 330 | for (Map.Entry<ClassIdentity,ClassIdentity> entry : matching.getUniqueMatches().entrySet()) { | ||
| 331 | |||
| 332 | // check source | ||
| 333 | ClassIdentity sourceClass = entry.getKey(); | ||
| 334 | CtClass sourceC = sourceLoader.loadClass(sourceClass.getClassEntry().getName()); | ||
| 335 | assert (sourceC != null) : "Unable to load source class " + sourceClass.getClassEntry(); | ||
| 336 | assert (sourceClass.matches(sourceC)) : "Source " + sourceClass + " doesn't match " + new ClassIdentity(sourceC, sourceNamer, sourceIndex, false); | ||
| 337 | |||
| 338 | // check dest | ||
| 339 | ClassIdentity destClass = entry.getValue(); | ||
| 340 | CtClass destC = destLoader.loadClass(destClass.getClassEntry().getName()); | ||
| 341 | assert (destC != null) : "Unable to load dest class " + destClass.getClassEntry(); | ||
| 342 | assert (destClass.matches(destC)) : "Dest " + destClass + " doesn't match " + new ClassIdentity(destC, destNamer, destIndex, false); | ||
| 343 | } | ||
| 344 | |||
| 345 | // warn about the ambiguous matchings | ||
| 346 | List<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>> ambiguousMatches = new ArrayList<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>>(matching.getAmbiguousMatches().entrySet()); | ||
| 347 | Collections.sort(ambiguousMatches, new Comparator<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>>() { | ||
| 348 | @Override | ||
| 349 | public int compare(Map.Entry<List<ClassIdentity>,List<ClassIdentity>> a, Map.Entry<List<ClassIdentity>,List<ClassIdentity>> b) { | ||
| 350 | String aName = a.getKey().get(0).getClassEntry().getName(); | ||
| 351 | String bName = b.getKey().get(0).getClassEntry().getName(); | ||
| 352 | return aName.compareTo(bName); | ||
| 353 | } | ||
| 354 | }); | ||
| 355 | for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : ambiguousMatches) { | ||
| 356 | System.out.println("Ambiguous matching:"); | ||
| 357 | System.out.println("\tSource: " + getClassNames(entry.getKey())); | ||
| 358 | System.out.println("\tDest: " + getClassNames(entry.getValue())); | ||
| 359 | } | ||
| 360 | |||
| 361 | /* DEBUG | ||
| 362 | Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry = ambiguousMatches.get( 7 ); | ||
| 363 | for (ClassIdentity c : entry.getKey()) { | ||
| 364 | System.out.println(c); | ||
| 365 | } | ||
| 366 | for(ClassIdentity c : entry.getKey()) { | ||
| 367 | System.out.println(decompile(sourceLoader, c.getClassEntry())); | ||
| 368 | } | ||
| 369 | */ | ||
| 370 | |||
| 371 | return matching; | ||
| 372 | } | ||
| 373 | |||
| 374 | private static void printScoredMatches(int maxScore, List<Integer> scores, Multimap<Integer,ClassIdentity> scoredMatches) { | ||
| 375 | int numScoredMatchesShown = 0; | ||
| 376 | for (int score : scores) { | ||
| 377 | for (ClassIdentity scoredMatch : scoredMatches.get(score)) { | ||
| 378 | System.out.println(String.format("\tScore: %3d %3.0f%% %s", score, 100.0 * score / maxScore, scoredMatch.getClassEntry().getName())); | ||
| 379 | if (numScoredMatchesShown++ > 10) { | ||
| 380 | return; | ||
| 381 | } | ||
| 382 | } | ||
| 383 | } | ||
| 384 | } | ||
| 385 | |||
| 386 | private static List<String> getClassNames(Collection<ClassIdentity> classes) { | ||
| 387 | List<String> out = Lists.newArrayList(); | ||
| 388 | for (ClassIdentity c : classes) { | ||
| 389 | out.add(c.getClassEntry().getName()); | ||
| 390 | } | ||
| 391 | Collections.sort(out); | ||
| 392 | return out; | ||
| 393 | } | ||
| 394 | |||
| 395 | /* DEBUG | ||
| 396 | private static String decompile(TranslatingTypeLoader loader, ClassEntry classEntry) { | ||
| 397 | PlainTextOutput output = new PlainTextOutput(); | ||
| 398 | DecompilerSettings settings = DecompilerSettings.javaDefaults(); | ||
| 399 | settings.setForceExplicitImports(true); | ||
| 400 | settings.setShowSyntheticMembers(true); | ||
| 401 | settings.setTypeLoader(loader); | ||
| 402 | Decompiler.decompile(classEntry.getName(), output, settings); | ||
| 403 | return output.toString(); | ||
| 404 | } | ||
| 405 | */ | ||
| 406 | } | ||
diff --git a/src/cuchaz/enigma/convert/ClassMatching.java b/src/cuchaz/enigma/convert/ClassMatching.java new file mode 100644 index 0000000..53b6f7f --- /dev/null +++ b/src/cuchaz/enigma/convert/ClassMatching.java | |||
| @@ -0,0 +1,173 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.convert; | ||
| 12 | |||
| 13 | import java.util.AbstractMap; | ||
| 14 | import java.util.ArrayList; | ||
| 15 | import java.util.Arrays; | ||
| 16 | import java.util.Collection; | ||
| 17 | import java.util.List; | ||
| 18 | import java.util.Map; | ||
| 19 | |||
| 20 | import com.google.common.collect.ArrayListMultimap; | ||
| 21 | import com.google.common.collect.BiMap; | ||
| 22 | import com.google.common.collect.HashBiMap; | ||
| 23 | import com.google.common.collect.Lists; | ||
| 24 | import com.google.common.collect.Maps; | ||
| 25 | import com.google.common.collect.Multimap; | ||
| 26 | |||
| 27 | public class ClassMatching { | ||
| 28 | |||
| 29 | private Multimap<ClassIdentity,ClassIdentity> m_sourceClasses; | ||
| 30 | private Multimap<ClassIdentity,ClassIdentity> m_matchedDestClasses; | ||
| 31 | private List<ClassIdentity> m_unmatchedDestClasses; | ||
| 32 | |||
| 33 | public ClassMatching() { | ||
| 34 | m_sourceClasses = ArrayListMultimap.create(); | ||
| 35 | m_matchedDestClasses = ArrayListMultimap.create(); | ||
| 36 | m_unmatchedDestClasses = Lists.newArrayList(); | ||
| 37 | } | ||
| 38 | |||
| 39 | public void addSource(ClassIdentity c) { | ||
| 40 | m_sourceClasses.put(c, c); | ||
| 41 | } | ||
| 42 | |||
| 43 | public void matchDestClass(ClassIdentity destClass) { | ||
| 44 | Collection<ClassIdentity> matchedSourceClasses = m_sourceClasses.get(destClass); | ||
| 45 | if (matchedSourceClasses.isEmpty()) { | ||
| 46 | // no match | ||
| 47 | m_unmatchedDestClasses.add(destClass); | ||
| 48 | } else { | ||
| 49 | // found a match | ||
| 50 | m_matchedDestClasses.put(destClass, destClass); | ||
| 51 | |||
| 52 | // DEBUG | ||
| 53 | ClassIdentity sourceClass = matchedSourceClasses.iterator().next(); | ||
| 54 | assert (sourceClass.hashCode() == destClass.hashCode()); | ||
| 55 | assert (sourceClass.equals(destClass)); | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | public void removeSource(ClassIdentity sourceClass) { | ||
| 60 | m_sourceClasses.remove(sourceClass, sourceClass); | ||
| 61 | } | ||
| 62 | |||
| 63 | public void removeDest(ClassIdentity destClass) { | ||
| 64 | m_matchedDestClasses.remove(destClass, destClass); | ||
| 65 | m_unmatchedDestClasses.remove(destClass); | ||
| 66 | } | ||
| 67 | |||
| 68 | public List<ClassIdentity> getSourceClasses() { | ||
| 69 | return new ArrayList<ClassIdentity>(m_sourceClasses.values()); | ||
| 70 | } | ||
| 71 | |||
| 72 | public List<ClassIdentity> getDestClasses() { | ||
| 73 | List<ClassIdentity> classes = Lists.newArrayList(); | ||
| 74 | classes.addAll(m_matchedDestClasses.values()); | ||
| 75 | classes.addAll(m_unmatchedDestClasses); | ||
| 76 | return classes; | ||
| 77 | } | ||
| 78 | |||
| 79 | public BiMap<ClassIdentity,ClassIdentity> getUniqueMatches() { | ||
| 80 | BiMap<ClassIdentity,ClassIdentity> uniqueMatches = HashBiMap.create(); | ||
| 81 | for (ClassIdentity sourceClass : m_sourceClasses.keySet()) { | ||
| 82 | Collection<ClassIdentity> matchedSourceClasses = m_sourceClasses.get(sourceClass); | ||
| 83 | Collection<ClassIdentity> matchedDestClasses = m_matchedDestClasses.get(sourceClass); | ||
| 84 | if (matchedSourceClasses.size() == 1 && matchedDestClasses.size() == 1) { | ||
| 85 | ClassIdentity matchedSourceClass = matchedSourceClasses.iterator().next(); | ||
| 86 | ClassIdentity matchedDestClass = matchedDestClasses.iterator().next(); | ||
| 87 | uniqueMatches.put(matchedSourceClass, matchedDestClass); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | return uniqueMatches; | ||
| 91 | } | ||
| 92 | |||
| 93 | public BiMap<List<ClassIdentity>,List<ClassIdentity>> getAmbiguousMatches() { | ||
| 94 | BiMap<List<ClassIdentity>,List<ClassIdentity>> ambiguousMatches = HashBiMap.create(); | ||
| 95 | for (ClassIdentity sourceClass : m_sourceClasses.keySet()) { | ||
| 96 | Collection<ClassIdentity> matchedSourceClasses = m_sourceClasses.get(sourceClass); | ||
| 97 | Collection<ClassIdentity> matchedDestClasses = m_matchedDestClasses.get(sourceClass); | ||
| 98 | if (matchedSourceClasses.size() > 1 && matchedDestClasses.size() > 1) { | ||
| 99 | ambiguousMatches.put( | ||
| 100 | new ArrayList<ClassIdentity>(matchedSourceClasses), | ||
| 101 | new ArrayList<ClassIdentity>(matchedDestClasses) | ||
| 102 | ); | ||
| 103 | } | ||
| 104 | } | ||
| 105 | return ambiguousMatches; | ||
| 106 | } | ||
| 107 | |||
| 108 | public int getNumAmbiguousSourceMatches() { | ||
| 109 | int num = 0; | ||
| 110 | for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : getAmbiguousMatches().entrySet()) { | ||
| 111 | num += entry.getKey().size(); | ||
| 112 | } | ||
| 113 | return num; | ||
| 114 | } | ||
| 115 | |||
| 116 | public int getNumAmbiguousDestMatches() { | ||
| 117 | int num = 0; | ||
| 118 | for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : getAmbiguousMatches().entrySet()) { | ||
| 119 | num += entry.getValue().size(); | ||
| 120 | } | ||
| 121 | return num; | ||
| 122 | } | ||
| 123 | |||
| 124 | public List<ClassIdentity> getUnmatchedSourceClasses() { | ||
| 125 | List<ClassIdentity> classes = Lists.newArrayList(); | ||
| 126 | for (ClassIdentity sourceClass : getSourceClasses()) { | ||
| 127 | if (m_matchedDestClasses.get(sourceClass).isEmpty()) { | ||
| 128 | classes.add(sourceClass); | ||
| 129 | } | ||
| 130 | } | ||
| 131 | return classes; | ||
| 132 | } | ||
| 133 | |||
| 134 | public List<ClassIdentity> getUnmatchedDestClasses() { | ||
| 135 | return new ArrayList<ClassIdentity>(m_unmatchedDestClasses); | ||
| 136 | } | ||
| 137 | |||
| 138 | public Map<String,Map.Entry<ClassIdentity,List<ClassIdentity>>> getIndex() { | ||
| 139 | Map<String,Map.Entry<ClassIdentity,List<ClassIdentity>>> conversion = Maps.newHashMap(); | ||
| 140 | for (Map.Entry<ClassIdentity,ClassIdentity> entry : getUniqueMatches().entrySet()) { | ||
| 141 | conversion.put( | ||
| 142 | entry.getKey().getClassEntry().getName(), | ||
| 143 | new AbstractMap.SimpleEntry<ClassIdentity,List<ClassIdentity>>(entry.getKey(), Arrays.asList(entry.getValue())) | ||
| 144 | ); | ||
| 145 | } | ||
| 146 | for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : getAmbiguousMatches().entrySet()) { | ||
| 147 | for (ClassIdentity sourceClass : entry.getKey()) { | ||
| 148 | conversion.put( | ||
| 149 | sourceClass.getClassEntry().getName(), | ||
| 150 | new AbstractMap.SimpleEntry<ClassIdentity,List<ClassIdentity>>(sourceClass, entry.getValue()) | ||
| 151 | ); | ||
| 152 | } | ||
| 153 | } | ||
| 154 | for (ClassIdentity sourceClass : getUnmatchedSourceClasses()) { | ||
| 155 | conversion.put( | ||
| 156 | sourceClass.getClassEntry().getName(), | ||
| 157 | new AbstractMap.SimpleEntry<ClassIdentity,List<ClassIdentity>>(sourceClass, getUnmatchedDestClasses()) | ||
| 158 | ); | ||
| 159 | } | ||
| 160 | return conversion; | ||
| 161 | } | ||
| 162 | |||
| 163 | @Override | ||
| 164 | public String toString() { | ||
| 165 | StringBuilder buf = new StringBuilder(); | ||
| 166 | buf.append(String.format("%12s%8s%8s\n", "", "Source", "Dest")); | ||
| 167 | buf.append(String.format("%12s%8d%8d\n", "Classes", getSourceClasses().size(), getDestClasses().size())); | ||
| 168 | buf.append(String.format("%12s%8d%8d\n", "Unique", getUniqueMatches().size(), getUniqueMatches().size())); | ||
| 169 | buf.append(String.format("%12s%8d%8d\n", "Ambiguous", getNumAmbiguousSourceMatches(), getNumAmbiguousDestMatches())); | ||
| 170 | buf.append(String.format("%12s%8d%8d\n", "Unmatched", getUnmatchedSourceClasses().size(), getUnmatchedDestClasses().size())); | ||
| 171 | return buf.toString(); | ||
| 172 | } | ||
| 173 | } | ||
diff --git a/src/cuchaz/enigma/convert/ClassNamer.java b/src/cuchaz/enigma/convert/ClassNamer.java new file mode 100644 index 0000000..1b6e81c --- /dev/null +++ b/src/cuchaz/enigma/convert/ClassNamer.java | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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 | public class ClassNamer { | ||
| 19 | |||
| 20 | public interface SidedClassNamer { | ||
| 21 | String getName(String name); | ||
| 22 | } | ||
| 23 | |||
| 24 | private Map<String,String> m_sourceNames; | ||
| 25 | private Map<String,String> m_destNames; | ||
| 26 | |||
| 27 | public ClassNamer(BiMap<ClassIdentity,ClassIdentity> mappings) { | ||
| 28 | // convert the identity mappings to name maps | ||
| 29 | m_sourceNames = Maps.newHashMap(); | ||
| 30 | m_destNames = Maps.newHashMap(); | ||
| 31 | int i = 0; | ||
| 32 | for (Map.Entry<ClassIdentity,ClassIdentity> entry : mappings.entrySet()) { | ||
| 33 | String name = String.format("M%04d", i++); | ||
| 34 | m_sourceNames.put(entry.getKey().getClassEntry().getName(), name); | ||
| 35 | m_destNames.put(entry.getValue().getClassEntry().getName(), name); | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | public String getSourceName(String name) { | ||
| 40 | return m_sourceNames.get(name); | ||
| 41 | } | ||
| 42 | |||
| 43 | public String getDestName(String name) { | ||
| 44 | return m_destNames.get(name); | ||
| 45 | } | ||
| 46 | |||
| 47 | public SidedClassNamer getSourceNamer() { | ||
| 48 | return new SidedClassNamer() { | ||
| 49 | @Override | ||
| 50 | public String getName(String name) { | ||
| 51 | return getSourceName(name); | ||
| 52 | } | ||
| 53 | }; | ||
| 54 | } | ||
| 55 | |||
| 56 | public SidedClassNamer getDestNamer() { | ||
| 57 | return new SidedClassNamer() { | ||
| 58 | @Override | ||
| 59 | public String getName(String name) { | ||
| 60 | return getDestName(name); | ||
| 61 | } | ||
| 62 | }; | ||
| 63 | } | ||
| 64 | } | ||
diff --git a/src/cuchaz/enigma/gui/AboutDialog.java b/src/cuchaz/enigma/gui/AboutDialog.java new file mode 100644 index 0000000..2476b56 --- /dev/null +++ b/src/cuchaz/enigma/gui/AboutDialog.java | |||
| @@ -0,0 +1,86 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..db7c85b --- /dev/null +++ b/src/cuchaz/enigma/gui/BoxHighlightPainter.java | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..acee483 --- /dev/null +++ b/src/cuchaz/enigma/gui/BrowserCaret.java | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..d0f01e6 --- /dev/null +++ b/src/cuchaz/enigma/gui/ClassListCellRenderer.java | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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/ClassSelector.java b/src/cuchaz/enigma/gui/ClassSelector.java new file mode 100644 index 0000000..654bfbe --- /dev/null +++ b/src/cuchaz/enigma/gui/ClassSelector.java | |||
| @@ -0,0 +1,164 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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.List; | ||
| 19 | import java.util.Map; | ||
| 20 | |||
| 21 | import javax.swing.JTree; | ||
| 22 | import javax.swing.tree.DefaultMutableTreeNode; | ||
| 23 | import javax.swing.tree.DefaultTreeModel; | ||
| 24 | import javax.swing.tree.TreePath; | ||
| 25 | |||
| 26 | import com.google.common.collect.ArrayListMultimap; | ||
| 27 | import com.google.common.collect.Lists; | ||
| 28 | import com.google.common.collect.Maps; | ||
| 29 | import com.google.common.collect.Multimap; | ||
| 30 | |||
| 31 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 32 | |||
| 33 | public class ClassSelector extends JTree { | ||
| 34 | |||
| 35 | private static final long serialVersionUID = -7632046902384775977L; | ||
| 36 | |||
| 37 | public interface ClassSelectionListener { | ||
| 38 | void onSelectClass(ClassEntry classEntry); | ||
| 39 | } | ||
| 40 | |||
| 41 | public static Comparator<ClassEntry> ObfuscatedClassEntryComparator; | ||
| 42 | public static Comparator<ClassEntry> DeobfuscatedClassEntryComparator; | ||
| 43 | |||
| 44 | static { | ||
| 45 | ObfuscatedClassEntryComparator = new Comparator<ClassEntry>() { | ||
| 46 | @Override | ||
| 47 | public int compare(ClassEntry a, ClassEntry b) { | ||
| 48 | if (a.getName().length() != b.getName().length()) { | ||
| 49 | return a.getName().length() - b.getName().length(); | ||
| 50 | } | ||
| 51 | return a.getName().compareTo(b.getName()); | ||
| 52 | } | ||
| 53 | }; | ||
| 54 | |||
| 55 | DeobfuscatedClassEntryComparator = new Comparator<ClassEntry>() { | ||
| 56 | @Override | ||
| 57 | public int compare(ClassEntry a, ClassEntry b) { | ||
| 58 | return a.getName().compareTo(b.getName()); | ||
| 59 | } | ||
| 60 | }; | ||
| 61 | } | ||
| 62 | |||
| 63 | private ClassSelectionListener m_listener; | ||
| 64 | private Comparator<ClassEntry> m_comparator; | ||
| 65 | |||
| 66 | public ClassSelector(Comparator<ClassEntry> comparator) { | ||
| 67 | m_comparator = comparator; | ||
| 68 | |||
| 69 | // configure the tree control | ||
| 70 | setRootVisible(false); | ||
| 71 | setShowsRootHandles(false); | ||
| 72 | setModel(null); | ||
| 73 | |||
| 74 | // hook events | ||
| 75 | addMouseListener(new MouseAdapter() { | ||
| 76 | @Override | ||
| 77 | public void mouseClicked(MouseEvent event) { | ||
| 78 | if (m_listener != null && event.getClickCount() == 2) { | ||
| 79 | // get the selected node | ||
| 80 | TreePath path = getSelectionPath(); | ||
| 81 | if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) { | ||
| 82 | ClassSelectorClassNode node = (ClassSelectorClassNode)path.getLastPathComponent(); | ||
| 83 | m_listener.onSelectClass(node.getClassEntry()); | ||
| 84 | } | ||
| 85 | } | ||
| 86 | } | ||
| 87 | }); | ||
| 88 | |||
| 89 | // init defaults | ||
| 90 | m_listener = null; | ||
| 91 | } | ||
| 92 | |||
| 93 | public void setListener(ClassSelectionListener val) { | ||
| 94 | m_listener = val; | ||
| 95 | } | ||
| 96 | |||
| 97 | public void setClasses(Collection<ClassEntry> classEntries) { | ||
| 98 | if (classEntries == null) { | ||
| 99 | setModel(null); | ||
| 100 | return; | ||
| 101 | } | ||
| 102 | |||
| 103 | // build the package names | ||
| 104 | Map<String,ClassSelectorPackageNode> packages = Maps.newHashMap(); | ||
| 105 | for (ClassEntry classEntry : classEntries) { | ||
| 106 | packages.put(classEntry.getPackageName(), null); | ||
| 107 | } | ||
| 108 | |||
| 109 | // sort the packages | ||
| 110 | List<String> sortedPackageNames = Lists.newArrayList(packages.keySet()); | ||
| 111 | Collections.sort(sortedPackageNames, new Comparator<String>() { | ||
| 112 | @Override | ||
| 113 | public int compare(String a, String b) { | ||
| 114 | // I can never keep this rule straight when writing these damn things... | ||
| 115 | // a < b => -1, a == b => 0, a > b => +1 | ||
| 116 | |||
| 117 | String[] aparts = a.split("/"); | ||
| 118 | String[] bparts = b.split("/"); | ||
| 119 | for (int i = 0; true; i++) { | ||
| 120 | if (i >= aparts.length) { | ||
| 121 | return -1; | ||
| 122 | } else if (i >= bparts.length) { | ||
| 123 | return 1; | ||
| 124 | } | ||
| 125 | |||
| 126 | int result = aparts[i].compareTo(bparts[i]); | ||
| 127 | if (result != 0) { | ||
| 128 | return result; | ||
| 129 | } | ||
| 130 | } | ||
| 131 | } | ||
| 132 | }); | ||
| 133 | |||
| 134 | // create the root node and the package nodes | ||
| 135 | DefaultMutableTreeNode root = new DefaultMutableTreeNode(); | ||
| 136 | for (String packageName : sortedPackageNames) { | ||
| 137 | ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName); | ||
| 138 | packages.put(packageName, node); | ||
| 139 | root.add(node); | ||
| 140 | } | ||
| 141 | |||
| 142 | // put the classes into packages | ||
| 143 | Multimap<String,ClassEntry> packagedClassEntries = ArrayListMultimap.create(); | ||
| 144 | for (ClassEntry classEntry : classEntries) { | ||
| 145 | packagedClassEntries.put(classEntry.getPackageName(), classEntry); | ||
| 146 | } | ||
| 147 | |||
| 148 | // build the class nodes | ||
| 149 | for (String packageName : packagedClassEntries.keySet()) { | ||
| 150 | // sort the class entries | ||
| 151 | List<ClassEntry> classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName)); | ||
| 152 | Collections.sort(classEntriesInPackage, m_comparator); | ||
| 153 | |||
| 154 | // create the nodes in order | ||
| 155 | for (ClassEntry classEntry : classEntriesInPackage) { | ||
| 156 | ClassSelectorPackageNode node = packages.get(packageName); | ||
| 157 | node.add(new ClassSelectorClassNode(classEntry)); | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | // finally, update the tree control | ||
| 162 | setModel(new DefaultTreeModel(root)); | ||
| 163 | } | ||
| 164 | } | ||
diff --git a/src/cuchaz/enigma/gui/ClassSelectorClassNode.java b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java new file mode 100644 index 0000000..66e931b --- /dev/null +++ b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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 | return m_classEntry.getSimpleName(); | ||
| 34 | } | ||
| 35 | } | ||
diff --git a/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java new file mode 100644 index 0000000..451d380 --- /dev/null +++ b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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 | } | ||
diff --git a/src/cuchaz/enigma/gui/CrashDialog.java b/src/cuchaz/enigma/gui/CrashDialog.java new file mode 100644 index 0000000..360091a --- /dev/null +++ b/src/cuchaz/enigma/gui/CrashDialog.java | |||
| @@ -0,0 +1,101 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..26a3163 --- /dev/null +++ b/src/cuchaz/enigma/gui/DeobfuscatedHighlightPainter.java | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..ca39c42 --- /dev/null +++ b/src/cuchaz/enigma/gui/Gui.java | |||
| @@ -0,0 +1,1165 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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.Rectangle; | ||
| 20 | import java.awt.event.ActionEvent; | ||
| 21 | import java.awt.event.ActionListener; | ||
| 22 | import java.awt.event.InputEvent; | ||
| 23 | import java.awt.event.KeyAdapter; | ||
| 24 | import java.awt.event.KeyEvent; | ||
| 25 | import java.awt.event.MouseAdapter; | ||
| 26 | import java.awt.event.MouseEvent; | ||
| 27 | import java.awt.event.WindowAdapter; | ||
| 28 | import java.awt.event.WindowEvent; | ||
| 29 | import java.io.File; | ||
| 30 | import java.io.IOException; | ||
| 31 | import java.lang.Thread.UncaughtExceptionHandler; | ||
| 32 | import java.util.Collection; | ||
| 33 | import java.util.Collections; | ||
| 34 | import java.util.List; | ||
| 35 | import java.util.Vector; | ||
| 36 | import java.util.jar.JarFile; | ||
| 37 | |||
| 38 | import javax.swing.BorderFactory; | ||
| 39 | import javax.swing.JEditorPane; | ||
| 40 | import javax.swing.JFileChooser; | ||
| 41 | import javax.swing.JFrame; | ||
| 42 | import javax.swing.JLabel; | ||
| 43 | import javax.swing.JList; | ||
| 44 | import javax.swing.JMenu; | ||
| 45 | import javax.swing.JMenuBar; | ||
| 46 | import javax.swing.JMenuItem; | ||
| 47 | import javax.swing.JOptionPane; | ||
| 48 | import javax.swing.JPanel; | ||
| 49 | import javax.swing.JPopupMenu; | ||
| 50 | import javax.swing.JScrollPane; | ||
| 51 | import javax.swing.JSplitPane; | ||
| 52 | import javax.swing.JTabbedPane; | ||
| 53 | import javax.swing.JTextField; | ||
| 54 | import javax.swing.JTree; | ||
| 55 | import javax.swing.KeyStroke; | ||
| 56 | import javax.swing.ListSelectionModel; | ||
| 57 | import javax.swing.SwingUtilities; | ||
| 58 | import javax.swing.Timer; | ||
| 59 | import javax.swing.WindowConstants; | ||
| 60 | import javax.swing.event.CaretEvent; | ||
| 61 | import javax.swing.event.CaretListener; | ||
| 62 | import javax.swing.text.BadLocationException; | ||
| 63 | import javax.swing.text.Highlighter; | ||
| 64 | import javax.swing.tree.DefaultTreeModel; | ||
| 65 | import javax.swing.tree.TreeNode; | ||
| 66 | import javax.swing.tree.TreePath; | ||
| 67 | |||
| 68 | import jsyntaxpane.DefaultSyntaxKit; | ||
| 69 | |||
| 70 | import com.google.common.collect.Lists; | ||
| 71 | |||
| 72 | import cuchaz.enigma.Constants; | ||
| 73 | import cuchaz.enigma.analysis.BehaviorReferenceTreeNode; | ||
| 74 | import cuchaz.enigma.analysis.ClassImplementationsTreeNode; | ||
| 75 | import cuchaz.enigma.analysis.ClassInheritanceTreeNode; | ||
| 76 | import cuchaz.enigma.analysis.EntryReference; | ||
| 77 | import cuchaz.enigma.analysis.FieldReferenceTreeNode; | ||
| 78 | import cuchaz.enigma.analysis.MethodImplementationsTreeNode; | ||
| 79 | import cuchaz.enigma.analysis.MethodInheritanceTreeNode; | ||
| 80 | import cuchaz.enigma.analysis.ReferenceTreeNode; | ||
| 81 | import cuchaz.enigma.analysis.Token; | ||
| 82 | import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener; | ||
| 83 | import cuchaz.enigma.mapping.ArgumentEntry; | ||
| 84 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 85 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 86 | import cuchaz.enigma.mapping.Entry; | ||
| 87 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 88 | import cuchaz.enigma.mapping.IllegalNameException; | ||
| 89 | import cuchaz.enigma.mapping.MappingParseException; | ||
| 90 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 91 | import cuchaz.enigma.mapping.Signature; | ||
| 92 | |||
| 93 | public class Gui { | ||
| 94 | |||
| 95 | private GuiController m_controller; | ||
| 96 | |||
| 97 | // controls | ||
| 98 | private JFrame m_frame; | ||
| 99 | private ClassSelector m_obfClasses; | ||
| 100 | private ClassSelector m_deobfClasses; | ||
| 101 | private JEditorPane m_editor; | ||
| 102 | private JPanel m_classesPanel; | ||
| 103 | private JSplitPane m_splitClasses; | ||
| 104 | private JPanel m_infoPanel; | ||
| 105 | private ObfuscatedHighlightPainter m_obfuscatedHighlightPainter; | ||
| 106 | private DeobfuscatedHighlightPainter m_deobfuscatedHighlightPainter; | ||
| 107 | private OtherHighlightPainter m_otherHighlightPainter; | ||
| 108 | private SelectionHighlightPainter m_selectionHighlightPainter; | ||
| 109 | private JTree m_inheritanceTree; | ||
| 110 | private JTree m_implementationsTree; | ||
| 111 | private JTree m_callsTree; | ||
| 112 | private JList<Token> m_tokens; | ||
| 113 | private JTabbedPane m_tabs; | ||
| 114 | |||
| 115 | // dynamic menu items | ||
| 116 | private JMenuItem m_closeJarMenu; | ||
| 117 | private JMenuItem m_openMappingsMenu; | ||
| 118 | private JMenuItem m_saveMappingsMenu; | ||
| 119 | private JMenuItem m_saveMappingsAsMenu; | ||
| 120 | private JMenuItem m_closeMappingsMenu; | ||
| 121 | private JMenuItem m_renameMenu; | ||
| 122 | private JMenuItem m_showInheritanceMenu; | ||
| 123 | private JMenuItem m_openEntryMenu; | ||
| 124 | private JMenuItem m_openPreviousMenu; | ||
| 125 | private JMenuItem m_showCallsMenu; | ||
| 126 | private JMenuItem m_showImplementationsMenu; | ||
| 127 | private JMenuItem m_toggleMappingMenu; | ||
| 128 | private JMenuItem m_exportSourceMenu; | ||
| 129 | private JMenuItem m_exportJarMenu; | ||
| 130 | |||
| 131 | // state | ||
| 132 | private EntryReference<Entry,Entry> m_reference; | ||
| 133 | private JFileChooser m_jarFileChooser; | ||
| 134 | private JFileChooser m_mappingsFileChooser; | ||
| 135 | private JFileChooser m_exportSourceFileChooser; | ||
| 136 | private JFileChooser m_exportJarFileChooser; | ||
| 137 | |||
| 138 | public Gui() { | ||
| 139 | |||
| 140 | // init frame | ||
| 141 | m_frame = new JFrame(Constants.Name); | ||
| 142 | final Container pane = m_frame.getContentPane(); | ||
| 143 | pane.setLayout(new BorderLayout()); | ||
| 144 | |||
| 145 | if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) { | ||
| 146 | // install a global exception handler to the event thread | ||
| 147 | CrashDialog.init(m_frame); | ||
| 148 | Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { | ||
| 149 | @Override | ||
| 150 | public void uncaughtException(Thread thread, Throwable ex) { | ||
| 151 | ex.printStackTrace(System.err); | ||
| 152 | CrashDialog.show(ex); | ||
| 153 | } | ||
| 154 | }); | ||
| 155 | } | ||
| 156 | |||
| 157 | m_controller = new GuiController(this); | ||
| 158 | |||
| 159 | // init file choosers | ||
| 160 | m_jarFileChooser = new JFileChooser(); | ||
| 161 | m_mappingsFileChooser = new JFileChooser(); | ||
| 162 | m_exportSourceFileChooser = new JFileChooser(); | ||
| 163 | m_exportSourceFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); | ||
| 164 | m_exportJarFileChooser = new JFileChooser(); | ||
| 165 | |||
| 166 | // init obfuscated classes list | ||
| 167 | m_obfClasses = new ClassSelector(ClassSelector.ObfuscatedClassEntryComparator); | ||
| 168 | m_obfClasses.setListener(new ClassSelectionListener() { | ||
| 169 | @Override | ||
| 170 | public void onSelectClass(ClassEntry classEntry) { | ||
| 171 | navigateTo(classEntry); | ||
| 172 | } | ||
| 173 | }); | ||
| 174 | JScrollPane obfScroller = new JScrollPane(m_obfClasses); | ||
| 175 | JPanel obfPanel = new JPanel(); | ||
| 176 | obfPanel.setLayout(new BorderLayout()); | ||
| 177 | obfPanel.add(new JLabel("Obfuscated Classes"), BorderLayout.NORTH); | ||
| 178 | obfPanel.add(obfScroller, BorderLayout.CENTER); | ||
| 179 | |||
| 180 | // init deobfuscated classes list | ||
| 181 | m_deobfClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator); | ||
| 182 | m_deobfClasses.setListener(new ClassSelectionListener() { | ||
| 183 | @Override | ||
| 184 | public void onSelectClass(ClassEntry classEntry) { | ||
| 185 | navigateTo(classEntry); | ||
| 186 | } | ||
| 187 | }); | ||
| 188 | JScrollPane deobfScroller = new JScrollPane(m_deobfClasses); | ||
| 189 | JPanel deobfPanel = new JPanel(); | ||
| 190 | deobfPanel.setLayout(new BorderLayout()); | ||
| 191 | deobfPanel.add(new JLabel("De-obfuscated Classes"), BorderLayout.NORTH); | ||
| 192 | deobfPanel.add(deobfScroller, BorderLayout.CENTER); | ||
| 193 | |||
| 194 | // set up classes panel (don't add the splitter yet) | ||
| 195 | m_splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, obfPanel, deobfPanel); | ||
| 196 | m_splitClasses.setResizeWeight(0.3); | ||
| 197 | m_classesPanel = new JPanel(); | ||
| 198 | m_classesPanel.setLayout(new BorderLayout()); | ||
| 199 | m_classesPanel.setPreferredSize(new Dimension(250, 0)); | ||
| 200 | |||
| 201 | // init info panel | ||
| 202 | m_infoPanel = new JPanel(); | ||
| 203 | m_infoPanel.setLayout(new GridLayout(4, 1, 0, 0)); | ||
| 204 | m_infoPanel.setPreferredSize(new Dimension(0, 100)); | ||
| 205 | m_infoPanel.setBorder(BorderFactory.createTitledBorder("Identifier Info")); | ||
| 206 | clearReference(); | ||
| 207 | |||
| 208 | // init editor | ||
| 209 | DefaultSyntaxKit.initKit(); | ||
| 210 | m_obfuscatedHighlightPainter = new ObfuscatedHighlightPainter(); | ||
| 211 | m_deobfuscatedHighlightPainter = new DeobfuscatedHighlightPainter(); | ||
| 212 | m_otherHighlightPainter = new OtherHighlightPainter(); | ||
| 213 | m_selectionHighlightPainter = new SelectionHighlightPainter(); | ||
| 214 | m_editor = new JEditorPane(); | ||
| 215 | m_editor.setEditable(false); | ||
| 216 | m_editor.setCaret(new BrowserCaret()); | ||
| 217 | JScrollPane sourceScroller = new JScrollPane(m_editor); | ||
| 218 | m_editor.setContentType("text/java"); | ||
| 219 | m_editor.addCaretListener(new CaretListener() { | ||
| 220 | @Override | ||
| 221 | public void caretUpdate(CaretEvent event) { | ||
| 222 | onCaretMove(event.getDot()); | ||
| 223 | } | ||
| 224 | }); | ||
| 225 | m_editor.addKeyListener(new KeyAdapter() { | ||
| 226 | @Override | ||
| 227 | public void keyPressed(KeyEvent event) { | ||
| 228 | switch (event.getKeyCode()) { | ||
| 229 | case KeyEvent.VK_R: | ||
| 230 | m_renameMenu.doClick(); | ||
| 231 | break; | ||
| 232 | |||
| 233 | case KeyEvent.VK_I: | ||
| 234 | m_showInheritanceMenu.doClick(); | ||
| 235 | break; | ||
| 236 | |||
| 237 | case KeyEvent.VK_M: | ||
| 238 | m_showImplementationsMenu.doClick(); | ||
| 239 | break; | ||
| 240 | |||
| 241 | case KeyEvent.VK_N: | ||
| 242 | m_openEntryMenu.doClick(); | ||
| 243 | break; | ||
| 244 | |||
| 245 | case KeyEvent.VK_P: | ||
| 246 | m_openPreviousMenu.doClick(); | ||
| 247 | break; | ||
| 248 | |||
| 249 | case KeyEvent.VK_C: | ||
| 250 | m_showCallsMenu.doClick(); | ||
| 251 | break; | ||
| 252 | |||
| 253 | case KeyEvent.VK_T: | ||
| 254 | m_toggleMappingMenu.doClick(); | ||
| 255 | break; | ||
| 256 | } | ||
| 257 | } | ||
| 258 | }); | ||
| 259 | |||
| 260 | // turn off token highlighting (it's wrong most of the time anyway...) | ||
| 261 | DefaultSyntaxKit kit = (DefaultSyntaxKit)m_editor.getEditorKit(); | ||
| 262 | kit.toggleComponent(m_editor, "jsyntaxpane.components.TokenMarker"); | ||
| 263 | |||
| 264 | // init editor popup menu | ||
| 265 | JPopupMenu popupMenu = new JPopupMenu(); | ||
| 266 | m_editor.setComponentPopupMenu(popupMenu); | ||
| 267 | { | ||
| 268 | JMenuItem menu = new JMenuItem("Rename"); | ||
| 269 | menu.addActionListener(new ActionListener() { | ||
| 270 | @Override | ||
| 271 | public void actionPerformed(ActionEvent event) { | ||
| 272 | startRename(); | ||
| 273 | } | ||
| 274 | }); | ||
| 275 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0)); | ||
| 276 | menu.setEnabled(false); | ||
| 277 | popupMenu.add(menu); | ||
| 278 | m_renameMenu = menu; | ||
| 279 | } | ||
| 280 | { | ||
| 281 | JMenuItem menu = new JMenuItem("Show Inheritance"); | ||
| 282 | menu.addActionListener(new ActionListener() { | ||
| 283 | @Override | ||
| 284 | public void actionPerformed(ActionEvent event) { | ||
| 285 | showInheritance(); | ||
| 286 | } | ||
| 287 | }); | ||
| 288 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, 0)); | ||
| 289 | menu.setEnabled(false); | ||
| 290 | popupMenu.add(menu); | ||
| 291 | m_showInheritanceMenu = menu; | ||
| 292 | } | ||
| 293 | { | ||
| 294 | JMenuItem menu = new JMenuItem("Show Implementations"); | ||
| 295 | menu.addActionListener(new ActionListener() { | ||
| 296 | @Override | ||
| 297 | public void actionPerformed(ActionEvent event) { | ||
| 298 | showImplementations(); | ||
| 299 | } | ||
| 300 | }); | ||
| 301 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0)); | ||
| 302 | menu.setEnabled(false); | ||
| 303 | popupMenu.add(menu); | ||
| 304 | m_showImplementationsMenu = menu; | ||
| 305 | } | ||
| 306 | { | ||
| 307 | JMenuItem menu = new JMenuItem("Show Calls"); | ||
| 308 | menu.addActionListener(new ActionListener() { | ||
| 309 | @Override | ||
| 310 | public void actionPerformed(ActionEvent event) { | ||
| 311 | showCalls(); | ||
| 312 | } | ||
| 313 | }); | ||
| 314 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0)); | ||
| 315 | menu.setEnabled(false); | ||
| 316 | popupMenu.add(menu); | ||
| 317 | m_showCallsMenu = menu; | ||
| 318 | } | ||
| 319 | { | ||
| 320 | JMenuItem menu = new JMenuItem("Go to Declaration"); | ||
| 321 | menu.addActionListener(new ActionListener() { | ||
| 322 | @Override | ||
| 323 | public void actionPerformed(ActionEvent event) { | ||
| 324 | navigateTo(m_reference.entry); | ||
| 325 | } | ||
| 326 | }); | ||
| 327 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0)); | ||
| 328 | menu.setEnabled(false); | ||
| 329 | popupMenu.add(menu); | ||
| 330 | m_openEntryMenu = menu; | ||
| 331 | } | ||
| 332 | { | ||
| 333 | JMenuItem menu = new JMenuItem("Go to previous"); | ||
| 334 | menu.addActionListener(new ActionListener() { | ||
| 335 | @Override | ||
| 336 | public void actionPerformed(ActionEvent event) { | ||
| 337 | m_controller.openPreviousReference(); | ||
| 338 | } | ||
| 339 | }); | ||
| 340 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 0)); | ||
| 341 | menu.setEnabled(false); | ||
| 342 | popupMenu.add(menu); | ||
| 343 | m_openPreviousMenu = menu; | ||
| 344 | } | ||
| 345 | { | ||
| 346 | JMenuItem menu = new JMenuItem("Mark as deobfuscated"); | ||
| 347 | menu.addActionListener(new ActionListener() { | ||
| 348 | @Override | ||
| 349 | public void actionPerformed(ActionEvent event) { | ||
| 350 | toggleMapping(); | ||
| 351 | } | ||
| 352 | }); | ||
| 353 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0)); | ||
| 354 | menu.setEnabled(false); | ||
| 355 | popupMenu.add(menu); | ||
| 356 | m_toggleMappingMenu = menu; | ||
| 357 | } | ||
| 358 | |||
| 359 | // init inheritance panel | ||
| 360 | m_inheritanceTree = new JTree(); | ||
| 361 | m_inheritanceTree.setModel(null); | ||
| 362 | m_inheritanceTree.addMouseListener(new MouseAdapter() { | ||
| 363 | @Override | ||
| 364 | public void mouseClicked(MouseEvent event) { | ||
| 365 | if (event.getClickCount() == 2) { | ||
| 366 | // get the selected node | ||
| 367 | TreePath path = m_inheritanceTree.getSelectionPath(); | ||
| 368 | if (path == null) { | ||
| 369 | return; | ||
| 370 | } | ||
| 371 | |||
| 372 | Object node = path.getLastPathComponent(); | ||
| 373 | if (node instanceof ClassInheritanceTreeNode) { | ||
| 374 | ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode)node; | ||
| 375 | navigateTo(new ClassEntry(classNode.getObfClassName())); | ||
| 376 | } else if (node instanceof MethodInheritanceTreeNode) { | ||
| 377 | MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode)node; | ||
| 378 | if (methodNode.isImplemented()) { | ||
| 379 | navigateTo(methodNode.getMethodEntry()); | ||
| 380 | } | ||
| 381 | } | ||
| 382 | } | ||
| 383 | } | ||
| 384 | }); | ||
| 385 | JPanel inheritancePanel = new JPanel(); | ||
| 386 | inheritancePanel.setLayout(new BorderLayout()); | ||
| 387 | inheritancePanel.add(new JScrollPane(m_inheritanceTree)); | ||
| 388 | |||
| 389 | // init implementations panel | ||
| 390 | m_implementationsTree = new JTree(); | ||
| 391 | m_implementationsTree.setModel(null); | ||
| 392 | m_implementationsTree.addMouseListener(new MouseAdapter() { | ||
| 393 | @Override | ||
| 394 | public void mouseClicked(MouseEvent event) { | ||
| 395 | if (event.getClickCount() == 2) { | ||
| 396 | // get the selected node | ||
| 397 | TreePath path = m_implementationsTree.getSelectionPath(); | ||
| 398 | if (path == null) { | ||
| 399 | return; | ||
| 400 | } | ||
| 401 | |||
| 402 | Object node = path.getLastPathComponent(); | ||
| 403 | if (node instanceof ClassImplementationsTreeNode) { | ||
| 404 | ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode)node; | ||
| 405 | navigateTo(classNode.getClassEntry()); | ||
| 406 | } else if (node instanceof MethodImplementationsTreeNode) { | ||
| 407 | MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode)node; | ||
| 408 | navigateTo(methodNode.getMethodEntry()); | ||
| 409 | } | ||
| 410 | } | ||
| 411 | } | ||
| 412 | }); | ||
| 413 | JPanel implementationsPanel = new JPanel(); | ||
| 414 | implementationsPanel.setLayout(new BorderLayout()); | ||
| 415 | implementationsPanel.add(new JScrollPane(m_implementationsTree)); | ||
| 416 | |||
| 417 | // init call panel | ||
| 418 | m_callsTree = new JTree(); | ||
| 419 | m_callsTree.setModel(null); | ||
| 420 | m_callsTree.addMouseListener(new MouseAdapter() { | ||
| 421 | @SuppressWarnings("unchecked") | ||
| 422 | @Override | ||
| 423 | public void mouseClicked(MouseEvent event) { | ||
| 424 | if (event.getClickCount() == 2) { | ||
| 425 | // get the selected node | ||
| 426 | TreePath path = m_callsTree.getSelectionPath(); | ||
| 427 | if (path == null) { | ||
| 428 | return; | ||
| 429 | } | ||
| 430 | |||
| 431 | Object node = path.getLastPathComponent(); | ||
| 432 | if (node instanceof ReferenceTreeNode) { | ||
| 433 | ReferenceTreeNode<Entry,Entry> referenceNode = ((ReferenceTreeNode<Entry,Entry>)node); | ||
| 434 | if (referenceNode.getReference() != null) { | ||
| 435 | navigateTo(referenceNode.getReference()); | ||
| 436 | } else { | ||
| 437 | navigateTo(referenceNode.getEntry()); | ||
| 438 | } | ||
| 439 | } | ||
| 440 | } | ||
| 441 | } | ||
| 442 | }); | ||
| 443 | m_tokens = new JList<Token>(); | ||
| 444 | m_tokens.setCellRenderer(new TokenListCellRenderer(m_controller)); | ||
| 445 | m_tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); | ||
| 446 | m_tokens.setLayoutOrientation(JList.VERTICAL); | ||
| 447 | m_tokens.addMouseListener(new MouseAdapter() { | ||
| 448 | @Override | ||
| 449 | public void mouseClicked(MouseEvent event) { | ||
| 450 | if (event.getClickCount() == 2) { | ||
| 451 | Token selected = m_tokens.getSelectedValue(); | ||
| 452 | if (selected != null) { | ||
| 453 | showToken(selected); | ||
| 454 | } | ||
| 455 | } | ||
| 456 | } | ||
| 457 | }); | ||
| 458 | m_tokens.setPreferredSize(new Dimension(0, 200)); | ||
| 459 | m_tokens.setMinimumSize(new Dimension(0, 200)); | ||
| 460 | JSplitPane callPanel = new JSplitPane( | ||
| 461 | JSplitPane.VERTICAL_SPLIT, | ||
| 462 | true, | ||
| 463 | new JScrollPane(m_callsTree), | ||
| 464 | new JScrollPane(m_tokens) | ||
| 465 | ); | ||
| 466 | callPanel.setResizeWeight(1); // let the top side take all the slack | ||
| 467 | callPanel.resetToPreferredSizes(); | ||
| 468 | |||
| 469 | // layout controls | ||
| 470 | JPanel centerPanel = new JPanel(); | ||
| 471 | centerPanel.setLayout(new BorderLayout()); | ||
| 472 | centerPanel.add(m_infoPanel, BorderLayout.NORTH); | ||
| 473 | centerPanel.add(sourceScroller, BorderLayout.CENTER); | ||
| 474 | m_tabs = new JTabbedPane(); | ||
| 475 | m_tabs.setPreferredSize(new Dimension(250, 0)); | ||
| 476 | m_tabs.addTab("Inheritance", inheritancePanel); | ||
| 477 | m_tabs.addTab("Implementations", implementationsPanel); | ||
| 478 | m_tabs.addTab("Call Graph", callPanel); | ||
| 479 | JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, m_tabs); | ||
| 480 | splitRight.setResizeWeight(1); // let the left side take all the slack | ||
| 481 | splitRight.resetToPreferredSizes(); | ||
| 482 | JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, m_classesPanel, splitRight); | ||
| 483 | splitCenter.setResizeWeight(0); // let the right side take all the slack | ||
| 484 | pane.add(splitCenter, BorderLayout.CENTER); | ||
| 485 | |||
| 486 | // init menus | ||
| 487 | JMenuBar menuBar = new JMenuBar(); | ||
| 488 | m_frame.setJMenuBar(menuBar); | ||
| 489 | { | ||
| 490 | JMenu menu = new JMenu("File"); | ||
| 491 | menuBar.add(menu); | ||
| 492 | { | ||
| 493 | JMenuItem item = new JMenuItem("Open Jar..."); | ||
| 494 | menu.add(item); | ||
| 495 | item.addActionListener(new ActionListener() { | ||
| 496 | @Override | ||
| 497 | public void actionPerformed(ActionEvent event) { | ||
| 498 | if (m_jarFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) { | ||
| 499 | // load the jar in a separate thread | ||
| 500 | new Thread() { | ||
| 501 | @Override | ||
| 502 | public void run() { | ||
| 503 | try { | ||
| 504 | m_controller.openJar(new JarFile(m_jarFileChooser.getSelectedFile())); | ||
| 505 | } catch (IOException ex) { | ||
| 506 | throw new Error(ex); | ||
| 507 | } | ||
| 508 | } | ||
| 509 | }.start(); | ||
| 510 | } | ||
| 511 | } | ||
| 512 | }); | ||
| 513 | } | ||
| 514 | { | ||
| 515 | JMenuItem item = new JMenuItem("Close Jar"); | ||
| 516 | menu.add(item); | ||
| 517 | item.addActionListener(new ActionListener() { | ||
| 518 | @Override | ||
| 519 | public void actionPerformed(ActionEvent event) { | ||
| 520 | m_controller.closeJar(); | ||
| 521 | } | ||
| 522 | }); | ||
| 523 | m_closeJarMenu = item; | ||
| 524 | } | ||
| 525 | menu.addSeparator(); | ||
| 526 | { | ||
| 527 | JMenuItem item = new JMenuItem("Open Mappings..."); | ||
| 528 | menu.add(item); | ||
| 529 | item.addActionListener(new ActionListener() { | ||
| 530 | @Override | ||
| 531 | public void actionPerformed(ActionEvent event) { | ||
| 532 | if (m_mappingsFileChooser.showOpenDialog(m_frame) == JFileChooser.APPROVE_OPTION) { | ||
| 533 | try { | ||
| 534 | m_controller.openMappings(m_mappingsFileChooser.getSelectedFile()); | ||
| 535 | } catch (IOException ex) { | ||
| 536 | throw new Error(ex); | ||
| 537 | } catch (MappingParseException ex) { | ||
| 538 | JOptionPane.showMessageDialog(m_frame, ex.getMessage()); | ||
| 539 | } | ||
| 540 | } | ||
| 541 | } | ||
| 542 | }); | ||
| 543 | m_openMappingsMenu = item; | ||
| 544 | } | ||
| 545 | { | ||
| 546 | JMenuItem item = new JMenuItem("Save Mappings"); | ||
| 547 | menu.add(item); | ||
| 548 | item.addActionListener(new ActionListener() { | ||
| 549 | @Override | ||
| 550 | public void actionPerformed(ActionEvent event) { | ||
| 551 | try { | ||
| 552 | m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile()); | ||
| 553 | } catch (IOException ex) { | ||
| 554 | throw new Error(ex); | ||
| 555 | } | ||
| 556 | } | ||
| 557 | }); | ||
| 558 | item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); | ||
| 559 | m_saveMappingsMenu = item; | ||
| 560 | } | ||
| 561 | { | ||
| 562 | JMenuItem item = new JMenuItem("Save Mappings As..."); | ||
| 563 | menu.add(item); | ||
| 564 | item.addActionListener(new ActionListener() { | ||
| 565 | @Override | ||
| 566 | public void actionPerformed(ActionEvent event) { | ||
| 567 | if (m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) { | ||
| 568 | try { | ||
| 569 | m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile()); | ||
| 570 | m_saveMappingsMenu.setEnabled(true); | ||
| 571 | } catch (IOException ex) { | ||
| 572 | throw new Error(ex); | ||
| 573 | } | ||
| 574 | } | ||
| 575 | } | ||
| 576 | }); | ||
| 577 | item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); | ||
| 578 | m_saveMappingsAsMenu = item; | ||
| 579 | } | ||
| 580 | { | ||
| 581 | JMenuItem item = new JMenuItem("Close Mappings"); | ||
| 582 | menu.add(item); | ||
| 583 | item.addActionListener(new ActionListener() { | ||
| 584 | @Override | ||
| 585 | public void actionPerformed(ActionEvent event) { | ||
| 586 | m_controller.closeMappings(); | ||
| 587 | } | ||
| 588 | }); | ||
| 589 | m_closeMappingsMenu = item; | ||
| 590 | } | ||
| 591 | menu.addSeparator(); | ||
| 592 | { | ||
| 593 | JMenuItem item = new JMenuItem("Export Source..."); | ||
| 594 | menu.add(item); | ||
| 595 | item.addActionListener(new ActionListener() { | ||
| 596 | @Override | ||
| 597 | public void actionPerformed(ActionEvent event) { | ||
| 598 | if (m_exportSourceFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) { | ||
| 599 | m_controller.exportSource(m_exportSourceFileChooser.getSelectedFile()); | ||
| 600 | } | ||
| 601 | } | ||
| 602 | }); | ||
| 603 | m_exportSourceMenu = item; | ||
| 604 | } | ||
| 605 | { | ||
| 606 | JMenuItem item = new JMenuItem("Export Jar..."); | ||
| 607 | menu.add(item); | ||
| 608 | item.addActionListener(new ActionListener() { | ||
| 609 | @Override | ||
| 610 | public void actionPerformed(ActionEvent event) { | ||
| 611 | if (m_exportJarFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) { | ||
| 612 | m_controller.exportJar(m_exportJarFileChooser.getSelectedFile()); | ||
| 613 | } | ||
| 614 | } | ||
| 615 | }); | ||
| 616 | m_exportJarMenu = item; | ||
| 617 | } | ||
| 618 | menu.addSeparator(); | ||
| 619 | { | ||
| 620 | JMenuItem item = new JMenuItem("Exit"); | ||
| 621 | menu.add(item); | ||
| 622 | item.addActionListener(new ActionListener() { | ||
| 623 | @Override | ||
| 624 | public void actionPerformed(ActionEvent event) { | ||
| 625 | close(); | ||
| 626 | } | ||
| 627 | }); | ||
| 628 | } | ||
| 629 | } | ||
| 630 | { | ||
| 631 | JMenu menu = new JMenu("Help"); | ||
| 632 | menuBar.add(menu); | ||
| 633 | { | ||
| 634 | JMenuItem item = new JMenuItem("About"); | ||
| 635 | menu.add(item); | ||
| 636 | item.addActionListener(new ActionListener() { | ||
| 637 | @Override | ||
| 638 | public void actionPerformed(ActionEvent event) { | ||
| 639 | AboutDialog.show(m_frame); | ||
| 640 | } | ||
| 641 | }); | ||
| 642 | } | ||
| 643 | } | ||
| 644 | |||
| 645 | // init state | ||
| 646 | onCloseJar(); | ||
| 647 | |||
| 648 | m_frame.addWindowListener(new WindowAdapter() { | ||
| 649 | @Override | ||
| 650 | public void windowClosing(WindowEvent event) { | ||
| 651 | close(); | ||
| 652 | } | ||
| 653 | }); | ||
| 654 | |||
| 655 | // show the frame | ||
| 656 | pane.doLayout(); | ||
| 657 | m_frame.setSize(1024, 576); | ||
| 658 | m_frame.setMinimumSize(new Dimension(640, 480)); | ||
| 659 | m_frame.setVisible(true); | ||
| 660 | m_frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); | ||
| 661 | } | ||
| 662 | |||
| 663 | public JFrame getFrame() { | ||
| 664 | return m_frame; | ||
| 665 | } | ||
| 666 | |||
| 667 | public GuiController getController() { | ||
| 668 | return m_controller; | ||
| 669 | } | ||
| 670 | |||
| 671 | public void onStartOpenJar() { | ||
| 672 | m_classesPanel.removeAll(); | ||
| 673 | JPanel panel = new JPanel(); | ||
| 674 | panel.setLayout(new FlowLayout()); | ||
| 675 | panel.add(new JLabel("Loading...")); | ||
| 676 | m_classesPanel.add(panel); | ||
| 677 | redraw(); | ||
| 678 | } | ||
| 679 | |||
| 680 | public void onFinishOpenJar(String jarName) { | ||
| 681 | // update gui | ||
| 682 | m_frame.setTitle(Constants.Name + " - " + jarName); | ||
| 683 | m_classesPanel.removeAll(); | ||
| 684 | m_classesPanel.add(m_splitClasses); | ||
| 685 | setSource(null); | ||
| 686 | |||
| 687 | // update menu | ||
| 688 | m_closeJarMenu.setEnabled(true); | ||
| 689 | m_openMappingsMenu.setEnabled(true); | ||
| 690 | m_saveMappingsMenu.setEnabled(false); | ||
| 691 | m_saveMappingsAsMenu.setEnabled(true); | ||
| 692 | m_closeMappingsMenu.setEnabled(true); | ||
| 693 | m_exportSourceMenu.setEnabled(true); | ||
| 694 | m_exportJarMenu.setEnabled(true); | ||
| 695 | |||
| 696 | redraw(); | ||
| 697 | } | ||
| 698 | |||
| 699 | public void onCloseJar() { | ||
| 700 | // update gui | ||
| 701 | m_frame.setTitle(Constants.Name); | ||
| 702 | setObfClasses(null); | ||
| 703 | setDeobfClasses(null); | ||
| 704 | setSource(null); | ||
| 705 | m_classesPanel.removeAll(); | ||
| 706 | |||
| 707 | // update menu | ||
| 708 | m_closeJarMenu.setEnabled(false); | ||
| 709 | m_openMappingsMenu.setEnabled(false); | ||
| 710 | m_saveMappingsMenu.setEnabled(false); | ||
| 711 | m_saveMappingsAsMenu.setEnabled(false); | ||
| 712 | m_closeMappingsMenu.setEnabled(false); | ||
| 713 | m_exportSourceMenu.setEnabled(false); | ||
| 714 | m_exportJarMenu.setEnabled(false); | ||
| 715 | |||
| 716 | redraw(); | ||
| 717 | } | ||
| 718 | |||
| 719 | public void setObfClasses(Collection<ClassEntry> obfClasses) { | ||
| 720 | m_obfClasses.setClasses(obfClasses); | ||
| 721 | } | ||
| 722 | |||
| 723 | public void setDeobfClasses(Collection<ClassEntry> deobfClasses) { | ||
| 724 | m_deobfClasses.setClasses(deobfClasses); | ||
| 725 | } | ||
| 726 | |||
| 727 | public void setMappingsFile(File file) { | ||
| 728 | m_mappingsFileChooser.setSelectedFile(file); | ||
| 729 | m_saveMappingsMenu.setEnabled(file != null); | ||
| 730 | } | ||
| 731 | |||
| 732 | public void setSource(String source) { | ||
| 733 | m_editor.getHighlighter().removeAllHighlights(); | ||
| 734 | m_editor.setText(source); | ||
| 735 | } | ||
| 736 | |||
| 737 | public void showToken(final Token token) { | ||
| 738 | if (token == null) { | ||
| 739 | throw new IllegalArgumentException("Token cannot be null!"); | ||
| 740 | } | ||
| 741 | |||
| 742 | // set the caret position to the token | ||
| 743 | m_editor.setCaretPosition(token.start); | ||
| 744 | m_editor.grabFocus(); | ||
| 745 | |||
| 746 | try { | ||
| 747 | // make sure the token is visible in the scroll window | ||
| 748 | Rectangle start = m_editor.modelToView(token.start); | ||
| 749 | Rectangle end = m_editor.modelToView(token.end); | ||
| 750 | final Rectangle show = start.union(end); | ||
| 751 | show.grow(start.width * 10, start.height * 6); | ||
| 752 | SwingUtilities.invokeLater(new Runnable() { | ||
| 753 | @Override | ||
| 754 | public void run() { | ||
| 755 | m_editor.scrollRectToVisible(show); | ||
| 756 | } | ||
| 757 | }); | ||
| 758 | } catch (BadLocationException ex) { | ||
| 759 | throw new Error(ex); | ||
| 760 | } | ||
| 761 | |||
| 762 | // highlight the token momentarily | ||
| 763 | final Timer timer = new Timer(200, new ActionListener() { | ||
| 764 | private int m_counter = 0; | ||
| 765 | private Object m_highlight = null; | ||
| 766 | |||
| 767 | @Override | ||
| 768 | public void actionPerformed(ActionEvent event) { | ||
| 769 | if (m_counter % 2 == 0) { | ||
| 770 | try { | ||
| 771 | m_highlight = m_editor.getHighlighter().addHighlight(token.start, token.end, m_selectionHighlightPainter); | ||
| 772 | } catch (BadLocationException ex) { | ||
| 773 | // don't care | ||
| 774 | } | ||
| 775 | } else if (m_highlight != null) { | ||
| 776 | m_editor.getHighlighter().removeHighlight(m_highlight); | ||
| 777 | } | ||
| 778 | |||
| 779 | if (m_counter++ > 6) { | ||
| 780 | Timer timer = (Timer)event.getSource(); | ||
| 781 | timer.stop(); | ||
| 782 | } | ||
| 783 | } | ||
| 784 | }); | ||
| 785 | timer.start(); | ||
| 786 | |||
| 787 | redraw(); | ||
| 788 | } | ||
| 789 | |||
| 790 | public void showTokens(Collection<Token> tokens) { | ||
| 791 | Vector<Token> sortedTokens = new Vector<Token>(tokens); | ||
| 792 | Collections.sort(sortedTokens); | ||
| 793 | if (sortedTokens.size() > 1) { | ||
| 794 | // sort the tokens and update the tokens panel | ||
| 795 | m_tokens.setListData(sortedTokens); | ||
| 796 | m_tokens.setSelectedIndex(0); | ||
| 797 | } else { | ||
| 798 | m_tokens.setListData(new Vector<Token>()); | ||
| 799 | } | ||
| 800 | |||
| 801 | // show the first token | ||
| 802 | showToken(sortedTokens.get(0)); | ||
| 803 | } | ||
| 804 | |||
| 805 | public void setHighlightedTokens(Iterable<Token> obfuscatedTokens, Iterable<Token> deobfuscatedTokens, Iterable<Token> otherTokens) { | ||
| 806 | |||
| 807 | // remove any old highlighters | ||
| 808 | m_editor.getHighlighter().removeAllHighlights(); | ||
| 809 | |||
| 810 | // color things based on the index | ||
| 811 | if (obfuscatedTokens != null) { | ||
| 812 | setHighlightedTokens(obfuscatedTokens, m_obfuscatedHighlightPainter); | ||
| 813 | } | ||
| 814 | if (deobfuscatedTokens != null) { | ||
| 815 | setHighlightedTokens(deobfuscatedTokens, m_deobfuscatedHighlightPainter); | ||
| 816 | } | ||
| 817 | if (otherTokens != null) { | ||
| 818 | setHighlightedTokens(otherTokens, m_otherHighlightPainter); | ||
| 819 | } | ||
| 820 | |||
| 821 | redraw(); | ||
| 822 | } | ||
| 823 | |||
| 824 | private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) { | ||
| 825 | for (Token token : tokens) { | ||
| 826 | try { | ||
| 827 | m_editor.getHighlighter().addHighlight(token.start, token.end, painter); | ||
| 828 | } catch (BadLocationException ex) { | ||
| 829 | throw new IllegalArgumentException(ex); | ||
| 830 | } | ||
| 831 | } | ||
| 832 | } | ||
| 833 | |||
| 834 | private void clearReference() { | ||
| 835 | m_infoPanel.removeAll(); | ||
| 836 | JLabel label = new JLabel("No identifier selected"); | ||
| 837 | GuiTricks.unboldLabel(label); | ||
| 838 | label.setHorizontalAlignment(JLabel.CENTER); | ||
| 839 | m_infoPanel.add(label); | ||
| 840 | |||
| 841 | redraw(); | ||
| 842 | } | ||
| 843 | |||
| 844 | private void showReference(EntryReference<Entry,Entry> reference) { | ||
| 845 | if (reference == null) { | ||
| 846 | clearReference(); | ||
| 847 | return; | ||
| 848 | } | ||
| 849 | |||
| 850 | m_reference = reference; | ||
| 851 | |||
| 852 | m_infoPanel.removeAll(); | ||
| 853 | if (reference.entry instanceof ClassEntry) { | ||
| 854 | showClassEntry((ClassEntry)m_reference.entry); | ||
| 855 | } else if (m_reference.entry instanceof FieldEntry) { | ||
| 856 | showFieldEntry((FieldEntry)m_reference.entry); | ||
| 857 | } else if (m_reference.entry instanceof MethodEntry) { | ||
| 858 | showMethodEntry((MethodEntry)m_reference.entry); | ||
| 859 | } else if (m_reference.entry instanceof ConstructorEntry) { | ||
| 860 | showConstructorEntry((ConstructorEntry)m_reference.entry); | ||
| 861 | } else if (m_reference.entry instanceof ArgumentEntry) { | ||
| 862 | showArgumentEntry((ArgumentEntry)m_reference.entry); | ||
| 863 | } else { | ||
| 864 | throw new Error("Unknown entry type: " + m_reference.entry.getClass().getName()); | ||
| 865 | } | ||
| 866 | |||
| 867 | redraw(); | ||
| 868 | } | ||
| 869 | |||
| 870 | private void showClassEntry(ClassEntry entry) { | ||
| 871 | addNameValue(m_infoPanel, "Class", entry.getName()); | ||
| 872 | } | ||
| 873 | |||
| 874 | private void showFieldEntry(FieldEntry entry) { | ||
| 875 | addNameValue(m_infoPanel, "Field", entry.getName()); | ||
| 876 | addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName()); | ||
| 877 | } | ||
| 878 | |||
| 879 | private void showMethodEntry(MethodEntry entry) { | ||
| 880 | addNameValue(m_infoPanel, "Method", entry.getName()); | ||
| 881 | addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName()); | ||
| 882 | addNameValue(m_infoPanel, "Signature", entry.getSignature().toString()); | ||
| 883 | } | ||
| 884 | |||
| 885 | private void showConstructorEntry(ConstructorEntry entry) { | ||
| 886 | addNameValue(m_infoPanel, "Constructor", entry.getClassEntry().getName()); | ||
| 887 | addNameValue(m_infoPanel, "Signature", entry.getSignature().toString()); | ||
| 888 | } | ||
| 889 | |||
| 890 | private void showArgumentEntry(ArgumentEntry entry) { | ||
| 891 | addNameValue(m_infoPanel, "Argument", entry.getName()); | ||
| 892 | addNameValue(m_infoPanel, "Class", entry.getClassEntry().getName()); | ||
| 893 | addNameValue(m_infoPanel, "Method", entry.getBehaviorEntry().getName()); | ||
| 894 | addNameValue(m_infoPanel, "Index", Integer.toString(entry.getIndex())); | ||
| 895 | } | ||
| 896 | |||
| 897 | private void addNameValue(JPanel container, String name, String value) { | ||
| 898 | JPanel panel = new JPanel(); | ||
| 899 | panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); | ||
| 900 | container.add(panel); | ||
| 901 | |||
| 902 | JLabel label = new JLabel(name + ":", JLabel.RIGHT); | ||
| 903 | label.setPreferredSize(new Dimension(100, label.getPreferredSize().height)); | ||
| 904 | panel.add(label); | ||
| 905 | |||
| 906 | panel.add(GuiTricks.unboldLabel(new JLabel(value, JLabel.LEFT))); | ||
| 907 | } | ||
| 908 | |||
| 909 | private void onCaretMove(int pos) { | ||
| 910 | |||
| 911 | Token token = m_controller.getToken(pos); | ||
| 912 | boolean isToken = token != null; | ||
| 913 | |||
| 914 | m_reference = m_controller.getDeobfReference(token); | ||
| 915 | boolean isClassEntry = isToken && m_reference.entry instanceof ClassEntry; | ||
| 916 | boolean isFieldEntry = isToken && m_reference.entry instanceof FieldEntry; | ||
| 917 | boolean isMethodEntry = isToken && m_reference.entry instanceof MethodEntry; | ||
| 918 | boolean isConstructorEntry = isToken && m_reference.entry instanceof ConstructorEntry; | ||
| 919 | boolean isInJar = isToken && m_controller.entryIsInJar(m_reference.entry); | ||
| 920 | boolean isRenameable = isToken && m_controller.referenceIsRenameable(m_reference); | ||
| 921 | |||
| 922 | if (isToken) { | ||
| 923 | showReference(m_reference); | ||
| 924 | } else { | ||
| 925 | clearReference(); | ||
| 926 | } | ||
| 927 | |||
| 928 | m_renameMenu.setEnabled(isRenameable && isToken); | ||
| 929 | m_showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry); | ||
| 930 | m_showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry); | ||
| 931 | m_showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry); | ||
| 932 | m_openEntryMenu.setEnabled(isInJar && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry)); | ||
| 933 | m_openPreviousMenu.setEnabled(m_controller.hasPreviousLocation()); | ||
| 934 | m_toggleMappingMenu.setEnabled(isRenameable && isToken); | ||
| 935 | |||
| 936 | if (isToken && m_controller.entryHasDeobfuscatedName(m_reference.entry)) { | ||
| 937 | m_toggleMappingMenu.setText("Reset to obfuscated"); | ||
| 938 | } else { | ||
| 939 | m_toggleMappingMenu.setText("Mark as deobfuscated"); | ||
| 940 | } | ||
| 941 | } | ||
| 942 | |||
| 943 | private void navigateTo(Entry entry) { | ||
| 944 | if (!m_controller.entryIsInJar(entry)) { | ||
| 945 | // entry is not in the jar. Ignore it | ||
| 946 | return; | ||
| 947 | } | ||
| 948 | if (m_reference != null) { | ||
| 949 | m_controller.savePreviousReference(m_reference); | ||
| 950 | } | ||
| 951 | m_controller.openDeclaration(entry); | ||
| 952 | } | ||
| 953 | |||
| 954 | private void navigateTo(EntryReference<Entry,Entry> reference) { | ||
| 955 | if (!m_controller.entryIsInJar(reference.getLocationClassEntry())) { | ||
| 956 | // reference is not in the jar. Ignore it | ||
| 957 | return; | ||
| 958 | } | ||
| 959 | if (m_reference != null) { | ||
| 960 | m_controller.savePreviousReference(m_reference); | ||
| 961 | } | ||
| 962 | m_controller.openReference(reference); | ||
| 963 | } | ||
| 964 | |||
| 965 | private void startRename() { | ||
| 966 | |||
| 967 | // init the text box | ||
| 968 | final JTextField text = new JTextField(); | ||
| 969 | text.setText(m_reference.getNamableName()); | ||
| 970 | text.setPreferredSize(new Dimension(360, text.getPreferredSize().height)); | ||
| 971 | text.addKeyListener(new KeyAdapter() { | ||
| 972 | @Override | ||
| 973 | public void keyPressed(KeyEvent event) { | ||
| 974 | switch (event.getKeyCode()) { | ||
| 975 | case KeyEvent.VK_ENTER: | ||
| 976 | finishRename(text, true); | ||
| 977 | break; | ||
| 978 | |||
| 979 | case KeyEvent.VK_ESCAPE: | ||
| 980 | finishRename(text, false); | ||
| 981 | break; | ||
| 982 | } | ||
| 983 | } | ||
| 984 | }); | ||
| 985 | |||
| 986 | // find the label with the name and replace it with the text box | ||
| 987 | JPanel panel = (JPanel)m_infoPanel.getComponent(0); | ||
| 988 | panel.remove(panel.getComponentCount() - 1); | ||
| 989 | panel.add(text); | ||
| 990 | text.grabFocus(); | ||
| 991 | text.selectAll(); | ||
| 992 | |||
| 993 | redraw(); | ||
| 994 | } | ||
| 995 | |||
| 996 | private void finishRename(JTextField text, boolean saveName) { | ||
| 997 | String newName = text.getText(); | ||
| 998 | if (saveName && newName != null && newName.length() > 0) { | ||
| 999 | try { | ||
| 1000 | m_controller.rename(m_reference, newName); | ||
| 1001 | } catch (IllegalNameException ex) { | ||
| 1002 | text.setBorder(BorderFactory.createLineBorder(Color.red, 1)); | ||
| 1003 | text.setToolTipText(ex.getReason()); | ||
| 1004 | GuiTricks.showToolTipNow(text); | ||
| 1005 | } | ||
| 1006 | return; | ||
| 1007 | } | ||
| 1008 | |||
| 1009 | // abort the rename | ||
| 1010 | JPanel panel = (JPanel)m_infoPanel.getComponent(0); | ||
| 1011 | panel.remove(panel.getComponentCount() - 1); | ||
| 1012 | panel.add(GuiTricks.unboldLabel(new JLabel(m_reference.getNamableName(), JLabel.LEFT))); | ||
| 1013 | |||
| 1014 | m_editor.grabFocus(); | ||
| 1015 | |||
| 1016 | redraw(); | ||
| 1017 | } | ||
| 1018 | |||
| 1019 | private void showInheritance() { | ||
| 1020 | |||
| 1021 | if (m_reference == null) { | ||
| 1022 | return; | ||
| 1023 | } | ||
| 1024 | |||
| 1025 | m_inheritanceTree.setModel(null); | ||
| 1026 | |||
| 1027 | if (m_reference.entry instanceof ClassEntry) { | ||
| 1028 | // get the class inheritance | ||
| 1029 | ClassInheritanceTreeNode classNode = m_controller.getClassInheritance((ClassEntry)m_reference.entry); | ||
| 1030 | |||
| 1031 | // show the tree at the root | ||
| 1032 | TreePath path = getPathToRoot(classNode); | ||
| 1033 | m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0))); | ||
| 1034 | m_inheritanceTree.expandPath(path); | ||
| 1035 | m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path)); | ||
| 1036 | } else if (m_reference.entry instanceof MethodEntry) { | ||
| 1037 | // get the method inheritance | ||
| 1038 | MethodInheritanceTreeNode classNode = m_controller.getMethodInheritance((MethodEntry)m_reference.entry); | ||
| 1039 | |||
| 1040 | // show the tree at the root | ||
| 1041 | TreePath path = getPathToRoot(classNode); | ||
| 1042 | m_inheritanceTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0))); | ||
| 1043 | m_inheritanceTree.expandPath(path); | ||
| 1044 | m_inheritanceTree.setSelectionRow(m_inheritanceTree.getRowForPath(path)); | ||
| 1045 | } | ||
| 1046 | |||
| 1047 | m_tabs.setSelectedIndex(0); | ||
| 1048 | redraw(); | ||
| 1049 | } | ||
| 1050 | |||
| 1051 | private void showImplementations() { | ||
| 1052 | |||
| 1053 | if (m_reference == null) { | ||
| 1054 | return; | ||
| 1055 | } | ||
| 1056 | |||
| 1057 | m_implementationsTree.setModel(null); | ||
| 1058 | |||
| 1059 | if (m_reference.entry instanceof ClassEntry) { | ||
| 1060 | // get the class implementations | ||
| 1061 | ClassImplementationsTreeNode node = m_controller.getClassImplementations((ClassEntry)m_reference.entry); | ||
| 1062 | if (node != null) { | ||
| 1063 | // show the tree at the root | ||
| 1064 | TreePath path = getPathToRoot(node); | ||
| 1065 | m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0))); | ||
| 1066 | m_implementationsTree.expandPath(path); | ||
| 1067 | m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path)); | ||
| 1068 | } | ||
| 1069 | } else if (m_reference.entry instanceof MethodEntry) { | ||
| 1070 | // get the method implementations | ||
| 1071 | MethodImplementationsTreeNode node = m_controller.getMethodImplementations((MethodEntry)m_reference.entry); | ||
| 1072 | if (node != null) { | ||
| 1073 | // show the tree at the root | ||
| 1074 | TreePath path = getPathToRoot(node); | ||
| 1075 | m_implementationsTree.setModel(new DefaultTreeModel((TreeNode)path.getPathComponent(0))); | ||
| 1076 | m_implementationsTree.expandPath(path); | ||
| 1077 | m_implementationsTree.setSelectionRow(m_implementationsTree.getRowForPath(path)); | ||
| 1078 | } | ||
| 1079 | } | ||
| 1080 | |||
| 1081 | m_tabs.setSelectedIndex(1); | ||
| 1082 | redraw(); | ||
| 1083 | } | ||
| 1084 | |||
| 1085 | private void showCalls() { | ||
| 1086 | |||
| 1087 | if (m_reference == null) { | ||
| 1088 | return; | ||
| 1089 | } | ||
| 1090 | |||
| 1091 | if (m_reference.entry instanceof ClassEntry) { | ||
| 1092 | // look for calls to the default constructor | ||
| 1093 | // TODO: get a list of all the constructors and find calls to all of them | ||
| 1094 | BehaviorReferenceTreeNode node = m_controller.getMethodReferences(new ConstructorEntry((ClassEntry)m_reference.entry, new Signature("()V"))); | ||
| 1095 | m_callsTree.setModel(new DefaultTreeModel(node)); | ||
| 1096 | } else if (m_reference.entry instanceof FieldEntry) { | ||
| 1097 | FieldReferenceTreeNode node = m_controller.getFieldReferences((FieldEntry)m_reference.entry); | ||
| 1098 | m_callsTree.setModel(new DefaultTreeModel(node)); | ||
| 1099 | } else if (m_reference.entry instanceof MethodEntry) { | ||
| 1100 | BehaviorReferenceTreeNode node = m_controller.getMethodReferences((MethodEntry)m_reference.entry); | ||
| 1101 | m_callsTree.setModel(new DefaultTreeModel(node)); | ||
| 1102 | } else if (m_reference.entry instanceof ConstructorEntry) { | ||
| 1103 | BehaviorReferenceTreeNode node = m_controller.getMethodReferences((ConstructorEntry)m_reference.entry); | ||
| 1104 | m_callsTree.setModel(new DefaultTreeModel(node)); | ||
| 1105 | } | ||
| 1106 | |||
| 1107 | m_tabs.setSelectedIndex(2); | ||
| 1108 | redraw(); | ||
| 1109 | } | ||
| 1110 | |||
| 1111 | private void toggleMapping() { | ||
| 1112 | if (m_controller.entryHasDeobfuscatedName(m_reference.entry)) { | ||
| 1113 | m_controller.removeMapping(m_reference); | ||
| 1114 | } else { | ||
| 1115 | m_controller.markAsDeobfuscated(m_reference); | ||
| 1116 | } | ||
| 1117 | } | ||
| 1118 | |||
| 1119 | private TreePath getPathToRoot(TreeNode node) { | ||
| 1120 | List<TreeNode> nodes = Lists.newArrayList(); | ||
| 1121 | TreeNode n = node; | ||
| 1122 | do { | ||
| 1123 | nodes.add(n); | ||
| 1124 | n = n.getParent(); | ||
| 1125 | } while (n != null); | ||
| 1126 | Collections.reverse(nodes); | ||
| 1127 | return new TreePath(nodes.toArray()); | ||
| 1128 | } | ||
| 1129 | |||
| 1130 | private void close() { | ||
| 1131 | if (!m_controller.isDirty()) { | ||
| 1132 | // everything is saved, we can exit safely | ||
| 1133 | m_frame.dispose(); | ||
| 1134 | } else { | ||
| 1135 | // ask to save before closing | ||
| 1136 | String[] options = { "Save and exit", "Discard changes", "Cancel" }; | ||
| 1137 | int response = JOptionPane.showOptionDialog(m_frame, "Your mappings have not been saved yet. Do you want to save?", "Save your changes?", JOptionPane.YES_NO_CANCEL_OPTION, | ||
| 1138 | JOptionPane.QUESTION_MESSAGE, null, options, options[2]); | ||
| 1139 | switch (response) { | ||
| 1140 | case JOptionPane.YES_OPTION: // save and exit | ||
| 1141 | if (m_mappingsFileChooser.getSelectedFile() != null || m_mappingsFileChooser.showSaveDialog(m_frame) == JFileChooser.APPROVE_OPTION) { | ||
| 1142 | try { | ||
| 1143 | m_controller.saveMappings(m_mappingsFileChooser.getSelectedFile()); | ||
| 1144 | m_frame.dispose(); | ||
| 1145 | } catch (IOException ex) { | ||
| 1146 | throw new Error(ex); | ||
| 1147 | } | ||
| 1148 | } | ||
| 1149 | break; | ||
| 1150 | |||
| 1151 | case JOptionPane.NO_OPTION: | ||
| 1152 | // don't save, exit | ||
| 1153 | m_frame.dispose(); | ||
| 1154 | break; | ||
| 1155 | |||
| 1156 | // cancel means do nothing | ||
| 1157 | } | ||
| 1158 | } | ||
| 1159 | } | ||
| 1160 | |||
| 1161 | private void redraw() { | ||
| 1162 | m_frame.validate(); | ||
| 1163 | m_frame.repaint(); | ||
| 1164 | } | ||
| 1165 | } | ||
diff --git a/src/cuchaz/enigma/gui/GuiController.java b/src/cuchaz/enigma/gui/GuiController.java new file mode 100644 index 0000000..61fea9c --- /dev/null +++ b/src/cuchaz/enigma/gui/GuiController.java | |||
| @@ -0,0 +1,355 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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 | MethodImplementationsTreeNode rootNode = m_deobfuscator.getJarIndex().getMethodImplementations( | ||
| 190 | m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating), | ||
| 191 | obfMethodEntry | ||
| 192 | ); | ||
| 193 | if (rootNode == null) { | ||
| 194 | return null; | ||
| 195 | } | ||
| 196 | return MethodImplementationsTreeNode.findNode(rootNode, obfMethodEntry); | ||
| 197 | } | ||
| 198 | |||
| 199 | public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) { | ||
| 200 | FieldEntry obfFieldEntry = m_deobfuscator.obfuscateEntry(deobfFieldEntry); | ||
| 201 | FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode( | ||
| 202 | m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating), | ||
| 203 | obfFieldEntry | ||
| 204 | ); | ||
| 205 | rootNode.load(m_deobfuscator.getJarIndex(), true); | ||
| 206 | return rootNode; | ||
| 207 | } | ||
| 208 | |||
| 209 | public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) { | ||
| 210 | BehaviorEntry obfBehaviorEntry = m_deobfuscator.obfuscateEntry(deobfBehaviorEntry); | ||
| 211 | BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode( | ||
| 212 | m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating), | ||
| 213 | obfBehaviorEntry | ||
| 214 | ); | ||
| 215 | rootNode.load(m_deobfuscator.getJarIndex(), true); | ||
| 216 | return rootNode; | ||
| 217 | } | ||
| 218 | |||
| 219 | public void rename(EntryReference<Entry,Entry> deobfReference, String newName) { | ||
| 220 | EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference); | ||
| 221 | m_deobfuscator.rename(obfReference.getNameableEntry(), newName); | ||
| 222 | m_isDirty = true; | ||
| 223 | refreshClasses(); | ||
| 224 | refreshCurrentClass(obfReference); | ||
| 225 | } | ||
| 226 | |||
| 227 | public void removeMapping(EntryReference<Entry,Entry> deobfReference) { | ||
| 228 | EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference); | ||
| 229 | m_deobfuscator.removeMapping(obfReference.getNameableEntry()); | ||
| 230 | m_isDirty = true; | ||
| 231 | refreshClasses(); | ||
| 232 | refreshCurrentClass(obfReference); | ||
| 233 | } | ||
| 234 | |||
| 235 | public void markAsDeobfuscated(EntryReference<Entry,Entry> deobfReference) { | ||
| 236 | EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference); | ||
| 237 | m_deobfuscator.markAsDeobfuscated(obfReference.getNameableEntry()); | ||
| 238 | m_isDirty = true; | ||
| 239 | refreshClasses(); | ||
| 240 | refreshCurrentClass(obfReference); | ||
| 241 | } | ||
| 242 | |||
| 243 | public void openDeclaration(Entry deobfEntry) { | ||
| 244 | if (deobfEntry == null) { | ||
| 245 | throw new IllegalArgumentException("Entry cannot be null!"); | ||
| 246 | } | ||
| 247 | openReference(new EntryReference<Entry,Entry>(deobfEntry, deobfEntry.getName())); | ||
| 248 | } | ||
| 249 | |||
| 250 | public void openReference(EntryReference<Entry,Entry> deobfReference) { | ||
| 251 | if (deobfReference == null) { | ||
| 252 | throw new IllegalArgumentException("Reference cannot be null!"); | ||
| 253 | } | ||
| 254 | |||
| 255 | // get the reference target class | ||
| 256 | EntryReference<Entry,Entry> obfReference = m_deobfuscator.obfuscateReference(deobfReference); | ||
| 257 | ClassEntry obfClassEntry = obfReference.getLocationClassEntry().getOuterClassEntry(); | ||
| 258 | if (!m_deobfuscator.isObfuscatedIdentifier(obfClassEntry)) { | ||
| 259 | throw new IllegalArgumentException("Obfuscated class " + obfClassEntry + " was not found in the jar!"); | ||
| 260 | } | ||
| 261 | if (m_currentObfClass == null || !m_currentObfClass.equals(obfClassEntry)) { | ||
| 262 | // deobfuscate the class, then navigate to the reference | ||
| 263 | m_currentObfClass = obfClassEntry; | ||
| 264 | deobfuscate(m_currentObfClass, obfReference); | ||
| 265 | } else { | ||
| 266 | showReference(obfReference); | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | private void showReference(EntryReference<Entry,Entry> obfReference) { | ||
| 271 | EntryReference<Entry,Entry> deobfReference = m_deobfuscator.deobfuscateReference(obfReference); | ||
| 272 | Collection<Token> tokens = m_index.getReferenceTokens(deobfReference); | ||
| 273 | if (tokens.isEmpty()) { | ||
| 274 | // DEBUG | ||
| 275 | System.err.println(String.format("WARNING: no tokens found for %s in %s", deobfReference, m_currentObfClass)); | ||
| 276 | } else { | ||
| 277 | m_gui.showTokens(tokens); | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | public void savePreviousReference(EntryReference<Entry,Entry> deobfReference) { | ||
| 282 | m_referenceStack.push(m_deobfuscator.obfuscateReference(deobfReference)); | ||
| 283 | } | ||
| 284 | |||
| 285 | public void openPreviousReference() { | ||
| 286 | if (hasPreviousLocation()) { | ||
| 287 | openReference(m_deobfuscator.deobfuscateReference(m_referenceStack.pop())); | ||
| 288 | } | ||
| 289 | } | ||
| 290 | |||
| 291 | public boolean hasPreviousLocation() { | ||
| 292 | return !m_referenceStack.isEmpty(); | ||
| 293 | } | ||
| 294 | |||
| 295 | private void refreshClasses() { | ||
| 296 | List<ClassEntry> obfClasses = Lists.newArrayList(); | ||
| 297 | List<ClassEntry> deobfClasses = Lists.newArrayList(); | ||
| 298 | m_deobfuscator.getSeparatedClasses(obfClasses, deobfClasses); | ||
| 299 | m_gui.setObfClasses(obfClasses); | ||
| 300 | m_gui.setDeobfClasses(deobfClasses); | ||
| 301 | } | ||
| 302 | |||
| 303 | private void refreshCurrentClass() { | ||
| 304 | refreshCurrentClass(null); | ||
| 305 | } | ||
| 306 | |||
| 307 | private void refreshCurrentClass(EntryReference<Entry,Entry> obfReference) { | ||
| 308 | if (m_currentObfClass != null) { | ||
| 309 | deobfuscate(m_currentObfClass, obfReference); | ||
| 310 | } | ||
| 311 | } | ||
| 312 | |||
| 313 | private void deobfuscate(final ClassEntry classEntry, final EntryReference<Entry,Entry> obfReference) { | ||
| 314 | |||
| 315 | m_gui.setSource("(deobfuscating...)"); | ||
| 316 | |||
| 317 | // run the deobfuscator in a separate thread so we don't block the GUI event queue | ||
| 318 | new Thread() { | ||
| 319 | @Override | ||
| 320 | public void run() { | ||
| 321 | // decompile,deobfuscate the bytecode | ||
| 322 | CompilationUnit sourceTree = m_deobfuscator.getSourceTree(classEntry.getClassName()); | ||
| 323 | if (sourceTree == null) { | ||
| 324 | // decompilation of this class is not supported | ||
| 325 | m_gui.setSource("Unable to find class: " + classEntry); | ||
| 326 | return; | ||
| 327 | } | ||
| 328 | String source = m_deobfuscator.getSource(sourceTree); | ||
| 329 | m_index = m_deobfuscator.getSourceIndex(sourceTree, source); | ||
| 330 | m_gui.setSource(m_index.getSource()); | ||
| 331 | if (obfReference != null) { | ||
| 332 | showReference(obfReference); | ||
| 333 | } | ||
| 334 | |||
| 335 | // set the highlighted tokens | ||
| 336 | List<Token> obfuscatedTokens = Lists.newArrayList(); | ||
| 337 | List<Token> deobfuscatedTokens = Lists.newArrayList(); | ||
| 338 | List<Token> otherTokens = Lists.newArrayList(); | ||
| 339 | for (Token token : m_index.referenceTokens()) { | ||
| 340 | EntryReference<Entry,Entry> reference = m_index.getDeobfReference(token); | ||
| 341 | if (referenceIsRenameable(reference)) { | ||
| 342 | if (entryHasDeobfuscatedName(reference.getNameableEntry())) { | ||
| 343 | deobfuscatedTokens.add(token); | ||
| 344 | } else { | ||
| 345 | obfuscatedTokens.add(token); | ||
| 346 | } | ||
| 347 | } else { | ||
| 348 | otherTokens.add(token); | ||
| 349 | } | ||
| 350 | } | ||
| 351 | m_gui.setHighlightedTokens(obfuscatedTokens, deobfuscatedTokens, otherTokens); | ||
| 352 | } | ||
| 353 | }.start(); | ||
| 354 | } | ||
| 355 | } | ||
diff --git a/src/cuchaz/enigma/gui/GuiTricks.java b/src/cuchaz/enigma/gui/GuiTricks.java new file mode 100644 index 0000000..df9e221 --- /dev/null +++ b/src/cuchaz/enigma/gui/GuiTricks.java | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.gui; | ||
| 12 | |||
| 13 | import java.awt.Font; | ||
| 14 | import java.awt.event.MouseEvent; | ||
| 15 | |||
| 16 | import javax.swing.JComponent; | ||
| 17 | import javax.swing.JLabel; | ||
| 18 | import javax.swing.ToolTipManager; | ||
| 19 | |||
| 20 | public class GuiTricks { | ||
| 21 | |||
| 22 | public static JLabel unboldLabel(JLabel label) { | ||
| 23 | Font font = label.getFont(); | ||
| 24 | label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD)); | ||
| 25 | return label; | ||
| 26 | } | ||
| 27 | |||
| 28 | public static void showToolTipNow(JComponent component) { | ||
| 29 | // HACKHACK: trick the tooltip manager into showing the tooltip right now | ||
| 30 | ToolTipManager manager = ToolTipManager.sharedInstance(); | ||
| 31 | int oldDelay = manager.getInitialDelay(); | ||
| 32 | manager.setInitialDelay(0); | ||
| 33 | manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false)); | ||
| 34 | manager.setInitialDelay(oldDelay); | ||
| 35 | } | ||
| 36 | } | ||
diff --git a/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java b/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java new file mode 100644 index 0000000..177835f --- /dev/null +++ b/src/cuchaz/enigma/gui/ObfuscatedHighlightPainter.java | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..4e9c870 --- /dev/null +++ b/src/cuchaz/enigma/gui/OtherHighlightPainter.java | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..b864fdb --- /dev/null +++ b/src/cuchaz/enigma/gui/ProgressDialog.java | |||
| @@ -0,0 +1,105 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..66bcbc2 --- /dev/null +++ b/src/cuchaz/enigma/gui/ReadableToken.java | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..abeda0c --- /dev/null +++ b/src/cuchaz/enigma/gui/RenameListener.java | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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/SelectionHighlightPainter.java b/src/cuchaz/enigma/gui/SelectionHighlightPainter.java new file mode 100644 index 0000000..5e189d2 --- /dev/null +++ b/src/cuchaz/enigma/gui/SelectionHighlightPainter.java | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..a49be37 --- /dev/null +++ b/src/cuchaz/enigma/gui/TokenListCellRenderer.java | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..aa22265 --- /dev/null +++ b/src/cuchaz/enigma/mapping/ArgumentEntry.java | |||
| @@ -0,0 +1,116 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..f4d8e77 --- /dev/null +++ b/src/cuchaz/enigma/mapping/ArgumentMapping.java | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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 int getIndex() { | ||
| 29 | return m_index; | ||
| 30 | } | ||
| 31 | |||
| 32 | public String getName() { | ||
| 33 | return m_name; | ||
| 34 | } | ||
| 35 | |||
| 36 | public void setName(String val) { | ||
| 37 | m_name = NameValidator.validateArgumentName(val); | ||
| 38 | } | ||
| 39 | |||
| 40 | @Override | ||
| 41 | public int compareTo(ArgumentMapping other) { | ||
| 42 | return Integer.compare(m_index, other.m_index); | ||
| 43 | } | ||
| 44 | } | ||
diff --git a/src/cuchaz/enigma/mapping/BehaviorEntry.java b/src/cuchaz/enigma/mapping/BehaviorEntry.java new file mode 100644 index 0000000..535788f --- /dev/null +++ b/src/cuchaz/enigma/mapping/BehaviorEntry.java | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | public interface BehaviorEntry extends Entry { | ||
| 14 | Signature getSignature(); | ||
| 15 | } | ||
diff --git a/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java b/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java new file mode 100644 index 0000000..61e501b --- /dev/null +++ b/src/cuchaz/enigma/mapping/BehaviorEntryFactory.java | |||
| @@ -0,0 +1,57 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import javassist.CtBehavior; | ||
| 14 | import javassist.CtConstructor; | ||
| 15 | import javassist.CtMethod; | ||
| 16 | import javassist.bytecode.Descriptor; | ||
| 17 | |||
| 18 | public class BehaviorEntryFactory { | ||
| 19 | |||
| 20 | public static BehaviorEntry create(String className, String name, String signature) { | ||
| 21 | return create(new ClassEntry(className), name, signature); | ||
| 22 | } | ||
| 23 | |||
| 24 | public static BehaviorEntry create(ClassEntry classEntry, String name, String signature) { | ||
| 25 | if (name.equals("<init>")) { | ||
| 26 | return new ConstructorEntry(classEntry, new Signature(signature)); | ||
| 27 | } else if (name.equals("<clinit>")) { | ||
| 28 | return new ConstructorEntry(classEntry); | ||
| 29 | } else { | ||
| 30 | return new MethodEntry(classEntry, name, new Signature(signature)); | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | public static BehaviorEntry create(CtBehavior behavior) { | ||
| 35 | String className = Descriptor.toJvmName(behavior.getDeclaringClass().getName()); | ||
| 36 | if (behavior instanceof CtMethod) { | ||
| 37 | return create(className, behavior.getName(), behavior.getSignature()); | ||
| 38 | } else if (behavior instanceof CtConstructor) { | ||
| 39 | CtConstructor constructor = (CtConstructor)behavior; | ||
| 40 | if (constructor.isClassInitializer()) { | ||
| 41 | return create(className, "<clinit>", null); | ||
| 42 | } else { | ||
| 43 | return create(className, "<init>", constructor.getSignature()); | ||
| 44 | } | ||
| 45 | } else { | ||
| 46 | throw new IllegalArgumentException("Unable to create BehaviorEntry from " + behavior); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | public static BehaviorEntry createObf(ClassEntry classEntry, MethodMapping methodMapping) { | ||
| 51 | return create(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature().toString()); | ||
| 52 | } | ||
| 53 | |||
| 54 | public static BehaviorEntry createDeobf(ClassEntry classEntry, MethodMapping methodMapping) { | ||
| 55 | return create(classEntry, methodMapping.getDeobfName(), methodMapping.getObfSignature().toString()); | ||
| 56 | } | ||
| 57 | } | ||
diff --git a/src/cuchaz/enigma/mapping/ClassEntry.java b/src/cuchaz/enigma/mapping/ClassEntry.java new file mode 100644 index 0000000..cf41001 --- /dev/null +++ b/src/cuchaz/enigma/mapping/ClassEntry.java | |||
| @@ -0,0 +1,123 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | |||
| 15 | public class ClassEntry implements Entry, Serializable { | ||
| 16 | |||
| 17 | private static final long serialVersionUID = 4235460580973955811L; | ||
| 18 | |||
| 19 | private String m_name; | ||
| 20 | |||
| 21 | public ClassEntry(String className) { | ||
| 22 | if (className == null) { | ||
| 23 | throw new IllegalArgumentException("Class name cannot be null!"); | ||
| 24 | } | ||
| 25 | if (className.indexOf('.') >= 0) { | ||
| 26 | throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className); | ||
| 27 | } | ||
| 28 | |||
| 29 | m_name = className; | ||
| 30 | |||
| 31 | if (isInnerClass() && getInnerClassName().indexOf('/') >= 0) { | ||
| 32 | throw new IllegalArgumentException("Inner class must not have a package: " + className); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | public ClassEntry(ClassEntry other) { | ||
| 37 | m_name = other.m_name; | ||
| 38 | } | ||
| 39 | |||
| 40 | @Override | ||
| 41 | public String getName() { | ||
| 42 | return m_name; | ||
| 43 | } | ||
| 44 | |||
| 45 | @Override | ||
| 46 | public String getClassName() { | ||
| 47 | return m_name; | ||
| 48 | } | ||
| 49 | |||
| 50 | @Override | ||
| 51 | public ClassEntry getClassEntry() { | ||
| 52 | return this; | ||
| 53 | } | ||
| 54 | |||
| 55 | @Override | ||
| 56 | public ClassEntry cloneToNewClass(ClassEntry classEntry) { | ||
| 57 | return classEntry; | ||
| 58 | } | ||
| 59 | |||
| 60 | @Override | ||
| 61 | public int hashCode() { | ||
| 62 | return m_name.hashCode(); | ||
| 63 | } | ||
| 64 | |||
| 65 | @Override | ||
| 66 | public boolean equals(Object other) { | ||
| 67 | if (other instanceof ClassEntry) { | ||
| 68 | return equals((ClassEntry)other); | ||
| 69 | } | ||
| 70 | return false; | ||
| 71 | } | ||
| 72 | |||
| 73 | public boolean equals(ClassEntry other) { | ||
| 74 | return m_name.equals(other.m_name); | ||
| 75 | } | ||
| 76 | |||
| 77 | @Override | ||
| 78 | public String toString() { | ||
| 79 | return m_name; | ||
| 80 | } | ||
| 81 | |||
| 82 | public boolean isInnerClass() { | ||
| 83 | return m_name.lastIndexOf('$') >= 0; | ||
| 84 | } | ||
| 85 | |||
| 86 | public String getOuterClassName() { | ||
| 87 | if (isInnerClass()) { | ||
| 88 | return m_name.substring(0, m_name.lastIndexOf('$')); | ||
| 89 | } | ||
| 90 | return m_name; | ||
| 91 | } | ||
| 92 | |||
| 93 | public String getInnerClassName() { | ||
| 94 | if (!isInnerClass()) { | ||
| 95 | throw new Error("This is not an inner class!"); | ||
| 96 | } | ||
| 97 | return m_name.substring(m_name.lastIndexOf('$') + 1); | ||
| 98 | } | ||
| 99 | |||
| 100 | public ClassEntry getOuterClassEntry() { | ||
| 101 | return new ClassEntry(getOuterClassName()); | ||
| 102 | } | ||
| 103 | |||
| 104 | public boolean isInDefaultPackage() { | ||
| 105 | return m_name.indexOf('/') < 0; | ||
| 106 | } | ||
| 107 | |||
| 108 | public String getPackageName() { | ||
| 109 | int pos = m_name.lastIndexOf('/'); | ||
| 110 | if (pos > 0) { | ||
| 111 | return m_name.substring(0, pos); | ||
| 112 | } | ||
| 113 | return null; | ||
| 114 | } | ||
| 115 | |||
| 116 | public String getSimpleName() { | ||
| 117 | int pos = m_name.lastIndexOf('/'); | ||
| 118 | if (pos > 0) { | ||
| 119 | return m_name.substring(pos + 1); | ||
| 120 | } | ||
| 121 | return m_name; | ||
| 122 | } | ||
| 123 | } | ||
diff --git a/src/cuchaz/enigma/mapping/ClassMapping.java b/src/cuchaz/enigma/mapping/ClassMapping.java new file mode 100644 index 0000000..e2c3d56 --- /dev/null +++ b/src/cuchaz/enigma/mapping/ClassMapping.java | |||
| @@ -0,0 +1,405 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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_obfName; | ||
| 24 | private String m_deobfName; | ||
| 25 | private Map<String,ClassMapping> m_innerClassesByObf; | ||
| 26 | private Map<String,ClassMapping> m_innerClassesByDeobf; | ||
| 27 | private Map<String,FieldMapping> m_fieldsByObf; | ||
| 28 | private Map<String,FieldMapping> m_fieldsByDeobf; | ||
| 29 | private Map<String,MethodMapping> m_methodsByObf; | ||
| 30 | private Map<String,MethodMapping> m_methodsByDeobf; | ||
| 31 | |||
| 32 | public ClassMapping(String obfName) { | ||
| 33 | this(obfName, null); | ||
| 34 | } | ||
| 35 | |||
| 36 | public ClassMapping(String obfName, String deobfName) { | ||
| 37 | m_obfName = obfName; | ||
| 38 | m_deobfName = NameValidator.validateClassName(deobfName, false); | ||
| 39 | m_innerClassesByObf = Maps.newHashMap(); | ||
| 40 | m_innerClassesByDeobf = Maps.newHashMap(); | ||
| 41 | m_fieldsByObf = Maps.newHashMap(); | ||
| 42 | m_fieldsByDeobf = Maps.newHashMap(); | ||
| 43 | m_methodsByObf = Maps.newHashMap(); | ||
| 44 | m_methodsByDeobf = Maps.newHashMap(); | ||
| 45 | } | ||
| 46 | |||
| 47 | public String getObfName() { | ||
| 48 | return m_obfName; | ||
| 49 | } | ||
| 50 | |||
| 51 | public String getDeobfName() { | ||
| 52 | return m_deobfName; | ||
| 53 | } | ||
| 54 | |||
| 55 | public void setDeobfName(String val) { | ||
| 56 | m_deobfName = NameValidator.validateClassName(val, false); | ||
| 57 | } | ||
| 58 | |||
| 59 | //// INNER CLASSES //////// | ||
| 60 | |||
| 61 | public Iterable<ClassMapping> innerClasses() { | ||
| 62 | assert (m_innerClassesByObf.size() >= m_innerClassesByDeobf.size()); | ||
| 63 | return m_innerClassesByObf.values(); | ||
| 64 | } | ||
| 65 | |||
| 66 | public void addInnerClassMapping(ClassMapping classMapping) { | ||
| 67 | assert (isSimpleClassName(classMapping.getObfName())); | ||
| 68 | boolean obfWasAdded = m_innerClassesByObf.put(classMapping.getObfName(), classMapping) == null; | ||
| 69 | assert (obfWasAdded); | ||
| 70 | if (classMapping.getDeobfName() != null) { | ||
| 71 | assert (isSimpleClassName(classMapping.getDeobfName())); | ||
| 72 | boolean deobfWasAdded = m_innerClassesByDeobf.put(classMapping.getDeobfName(), classMapping) == null; | ||
| 73 | assert (deobfWasAdded); | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | public void removeInnerClassMapping(ClassMapping classMapping) { | ||
| 78 | boolean obfWasRemoved = m_innerClassesByObf.remove(classMapping.getObfName()) != null; | ||
| 79 | assert (obfWasRemoved); | ||
| 80 | if (classMapping.getDeobfName() != null) { | ||
| 81 | boolean deobfWasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null; | ||
| 82 | assert (deobfWasRemoved); | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | public ClassMapping getOrCreateInnerClass(String obfName) { | ||
| 87 | assert (isSimpleClassName(obfName)); | ||
| 88 | ClassMapping classMapping = m_innerClassesByObf.get(obfName); | ||
| 89 | if (classMapping == null) { | ||
| 90 | classMapping = new ClassMapping(obfName); | ||
| 91 | boolean wasAdded = m_innerClassesByObf.put(obfName, classMapping) == null; | ||
| 92 | assert (wasAdded); | ||
| 93 | } | ||
| 94 | return classMapping; | ||
| 95 | } | ||
| 96 | |||
| 97 | public ClassMapping getInnerClassByObf(String obfName) { | ||
| 98 | assert (isSimpleClassName(obfName)); | ||
| 99 | return m_innerClassesByObf.get(obfName); | ||
| 100 | } | ||
| 101 | |||
| 102 | public ClassMapping getInnerClassByDeobf(String deobfName) { | ||
| 103 | assert (isSimpleClassName(deobfName)); | ||
| 104 | return m_innerClassesByDeobf.get(deobfName); | ||
| 105 | } | ||
| 106 | |||
| 107 | public ClassMapping getInnerClassByDeobfThenObf(String name) { | ||
| 108 | ClassMapping classMapping = getInnerClassByDeobf(name); | ||
| 109 | if (classMapping == null) { | ||
| 110 | classMapping = getInnerClassByObf(name); | ||
| 111 | } | ||
| 112 | return classMapping; | ||
| 113 | } | ||
| 114 | |||
| 115 | public String getObfInnerClassName(String deobfName) { | ||
| 116 | assert (isSimpleClassName(deobfName)); | ||
| 117 | ClassMapping classMapping = m_innerClassesByDeobf.get(deobfName); | ||
| 118 | if (classMapping != null) { | ||
| 119 | return classMapping.getObfName(); | ||
| 120 | } | ||
| 121 | return null; | ||
| 122 | } | ||
| 123 | |||
| 124 | public String getDeobfInnerClassName(String obfName) { | ||
| 125 | assert (isSimpleClassName(obfName)); | ||
| 126 | ClassMapping classMapping = m_innerClassesByObf.get(obfName); | ||
| 127 | if (classMapping != null) { | ||
| 128 | return classMapping.getDeobfName(); | ||
| 129 | } | ||
| 130 | return null; | ||
| 131 | } | ||
| 132 | |||
| 133 | public void setInnerClassName(String obfName, String deobfName) { | ||
| 134 | assert (isSimpleClassName(obfName)); | ||
| 135 | ClassMapping classMapping = getOrCreateInnerClass(obfName); | ||
| 136 | if (classMapping.getDeobfName() != null) { | ||
| 137 | boolean wasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null; | ||
| 138 | assert (wasRemoved); | ||
| 139 | } | ||
| 140 | classMapping.setDeobfName(deobfName); | ||
| 141 | if (deobfName != null) { | ||
| 142 | assert (isSimpleClassName(deobfName)); | ||
| 143 | boolean wasAdded = m_innerClassesByDeobf.put(deobfName, classMapping) == null; | ||
| 144 | assert (wasAdded); | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | //// FIELDS //////// | ||
| 149 | |||
| 150 | public Iterable<FieldMapping> fields() { | ||
| 151 | assert (m_fieldsByObf.size() == m_fieldsByDeobf.size()); | ||
| 152 | return m_fieldsByObf.values(); | ||
| 153 | } | ||
| 154 | |||
| 155 | public boolean containsObfField(String obfName) { | ||
| 156 | return m_fieldsByObf.containsKey(obfName); | ||
| 157 | } | ||
| 158 | |||
| 159 | public boolean containsDeobfField(String deobfName) { | ||
| 160 | return m_fieldsByDeobf.containsKey(deobfName); | ||
| 161 | } | ||
| 162 | |||
| 163 | public void addFieldMapping(FieldMapping fieldMapping) { | ||
| 164 | if (m_fieldsByObf.containsKey(fieldMapping.getObfName())) { | ||
| 165 | throw new Error("Already have mapping for " + m_obfName + "." + fieldMapping.getObfName()); | ||
| 166 | } | ||
| 167 | if (m_fieldsByDeobf.containsKey(fieldMapping.getDeobfName())) { | ||
| 168 | throw new Error("Already have mapping for " + m_deobfName + "." + fieldMapping.getDeobfName()); | ||
| 169 | } | ||
| 170 | boolean obfWasAdded = m_fieldsByObf.put(fieldMapping.getObfName(), fieldMapping) == null; | ||
| 171 | assert (obfWasAdded); | ||
| 172 | boolean deobfWasAdded = m_fieldsByDeobf.put(fieldMapping.getDeobfName(), fieldMapping) == null; | ||
| 173 | assert (deobfWasAdded); | ||
| 174 | assert (m_fieldsByObf.size() == m_fieldsByDeobf.size()); | ||
| 175 | } | ||
| 176 | |||
| 177 | public void removeFieldMapping(FieldMapping fieldMapping) { | ||
| 178 | boolean obfWasRemoved = m_fieldsByObf.remove(fieldMapping.getObfName()) != null; | ||
| 179 | assert (obfWasRemoved); | ||
| 180 | if (fieldMapping.getDeobfName() != null) { | ||
| 181 | boolean deobfWasRemoved = m_fieldsByDeobf.remove(fieldMapping.getDeobfName()) != null; | ||
| 182 | assert (deobfWasRemoved); | ||
| 183 | } | ||
| 184 | } | ||
| 185 | |||
| 186 | public FieldMapping getFieldByObf(String obfName) { | ||
| 187 | return m_fieldsByObf.get(obfName); | ||
| 188 | } | ||
| 189 | |||
| 190 | public FieldMapping getFieldByDeobf(String deobfName) { | ||
| 191 | return m_fieldsByDeobf.get(deobfName); | ||
| 192 | } | ||
| 193 | |||
| 194 | public String getObfFieldName(String deobfName) { | ||
| 195 | FieldMapping fieldMapping = m_fieldsByDeobf.get(deobfName); | ||
| 196 | if (fieldMapping != null) { | ||
| 197 | return fieldMapping.getObfName(); | ||
| 198 | } | ||
| 199 | return null; | ||
| 200 | } | ||
| 201 | |||
| 202 | public String getDeobfFieldName(String obfName) { | ||
| 203 | FieldMapping fieldMapping = m_fieldsByObf.get(obfName); | ||
| 204 | if (fieldMapping != null) { | ||
| 205 | return fieldMapping.getDeobfName(); | ||
| 206 | } | ||
| 207 | return null; | ||
| 208 | } | ||
| 209 | |||
| 210 | public void setFieldName(String obfName, String deobfName) { | ||
| 211 | FieldMapping fieldMapping = m_fieldsByObf.get(obfName); | ||
| 212 | if (fieldMapping == null) { | ||
| 213 | fieldMapping = new FieldMapping(obfName, deobfName); | ||
| 214 | boolean obfWasAdded = m_fieldsByObf.put(obfName, fieldMapping) == null; | ||
| 215 | assert (obfWasAdded); | ||
| 216 | } else { | ||
| 217 | boolean wasRemoved = m_fieldsByDeobf.remove(fieldMapping.getDeobfName()) != null; | ||
| 218 | assert (wasRemoved); | ||
| 219 | } | ||
| 220 | fieldMapping.setDeobfName(deobfName); | ||
| 221 | if (deobfName != null) { | ||
| 222 | boolean wasAdded = m_fieldsByDeobf.put(deobfName, fieldMapping) == null; | ||
| 223 | assert (wasAdded); | ||
| 224 | } | ||
| 225 | } | ||
| 226 | |||
| 227 | //// METHODS //////// | ||
| 228 | |||
| 229 | public Iterable<MethodMapping> methods() { | ||
| 230 | assert (m_methodsByObf.size() >= m_methodsByDeobf.size()); | ||
| 231 | return m_methodsByObf.values(); | ||
| 232 | } | ||
| 233 | |||
| 234 | public boolean containsObfMethod(String obfName, Signature obfSignature) { | ||
| 235 | return m_methodsByObf.containsKey(getMethodKey(obfName, obfSignature)); | ||
| 236 | } | ||
| 237 | |||
| 238 | public boolean containsDeobfMethod(String deobfName, Signature deobfSignature) { | ||
| 239 | return m_methodsByDeobf.containsKey(getMethodKey(deobfName, deobfSignature)); | ||
| 240 | } | ||
| 241 | |||
| 242 | public void addMethodMapping(MethodMapping methodMapping) { | ||
| 243 | String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); | ||
| 244 | if (m_methodsByObf.containsKey(obfKey)) { | ||
| 245 | throw new Error("Already have mapping for " + m_obfName + "." + obfKey); | ||
| 246 | } | ||
| 247 | boolean wasAdded = m_methodsByObf.put(obfKey, methodMapping) == null; | ||
| 248 | assert (wasAdded); | ||
| 249 | if (methodMapping.getDeobfName() != null) { | ||
| 250 | String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature()); | ||
| 251 | if (m_methodsByDeobf.containsKey(deobfKey)) { | ||
| 252 | throw new Error("Already have mapping for " + m_deobfName + "." + deobfKey); | ||
| 253 | } | ||
| 254 | boolean deobfWasAdded = m_methodsByDeobf.put(deobfKey, methodMapping) == null; | ||
| 255 | assert (deobfWasAdded); | ||
| 256 | } | ||
| 257 | assert (m_methodsByObf.size() >= m_methodsByDeobf.size()); | ||
| 258 | } | ||
| 259 | |||
| 260 | public void removeMethodMapping(MethodMapping methodMapping) { | ||
| 261 | boolean obfWasRemoved = m_methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature())) != null; | ||
| 262 | assert (obfWasRemoved); | ||
| 263 | if (methodMapping.getDeobfName() != null) { | ||
| 264 | boolean deobfWasRemoved = m_methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null; | ||
| 265 | assert (deobfWasRemoved); | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | public MethodMapping getMethodByObf(String obfName, Signature signature) { | ||
| 270 | return m_methodsByObf.get(getMethodKey(obfName, signature)); | ||
| 271 | } | ||
| 272 | |||
| 273 | public MethodMapping getMethodByDeobf(String deobfName, Signature signature) { | ||
| 274 | return m_methodsByDeobf.get(getMethodKey(deobfName, signature)); | ||
| 275 | } | ||
| 276 | |||
| 277 | private String getMethodKey(String name, Signature signature) { | ||
| 278 | if (name == null) { | ||
| 279 | throw new IllegalArgumentException("name cannot be null!"); | ||
| 280 | } | ||
| 281 | if (signature == null) { | ||
| 282 | throw new IllegalArgumentException("signature cannot be null!"); | ||
| 283 | } | ||
| 284 | return name + signature; | ||
| 285 | } | ||
| 286 | |||
| 287 | public void setMethodName(String obfName, Signature obfSignature, String deobfName) { | ||
| 288 | MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfName, obfSignature)); | ||
| 289 | if (methodMapping == null) { | ||
| 290 | methodMapping = createMethodMapping(obfName, obfSignature); | ||
| 291 | } else if (methodMapping.getDeobfName() != null) { | ||
| 292 | boolean wasRemoved = m_methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null; | ||
| 293 | assert (wasRemoved); | ||
| 294 | } | ||
| 295 | methodMapping.setDeobfName(deobfName); | ||
| 296 | if (deobfName != null) { | ||
| 297 | boolean wasAdded = m_methodsByDeobf.put(getMethodKey(deobfName, obfSignature), methodMapping) == null; | ||
| 298 | assert (wasAdded); | ||
| 299 | } | ||
| 300 | } | ||
| 301 | |||
| 302 | //// ARGUMENTS //////// | ||
| 303 | |||
| 304 | public void setArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex, String argumentName) { | ||
| 305 | MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)); | ||
| 306 | if (methodMapping == null) { | ||
| 307 | methodMapping = createMethodMapping(obfMethodName, obfMethodSignature); | ||
| 308 | } | ||
| 309 | methodMapping.setArgumentName(argumentIndex, argumentName); | ||
| 310 | } | ||
| 311 | |||
| 312 | public void removeArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex) { | ||
| 313 | m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)).removeArgumentName(argumentIndex); | ||
| 314 | } | ||
| 315 | |||
| 316 | private MethodMapping createMethodMapping(String obfName, Signature obfSignature) { | ||
| 317 | MethodMapping methodMapping = new MethodMapping(obfName, obfSignature); | ||
| 318 | boolean wasAdded = m_methodsByObf.put(getMethodKey(obfName, obfSignature), methodMapping) == null; | ||
| 319 | assert (wasAdded); | ||
| 320 | return methodMapping; | ||
| 321 | } | ||
| 322 | |||
| 323 | @Override | ||
| 324 | public String toString() { | ||
| 325 | StringBuilder buf = new StringBuilder(); | ||
| 326 | buf.append(m_obfName); | ||
| 327 | buf.append(" <-> "); | ||
| 328 | buf.append(m_deobfName); | ||
| 329 | buf.append("\n"); | ||
| 330 | buf.append("Fields:\n"); | ||
| 331 | for (FieldMapping fieldMapping : fields()) { | ||
| 332 | buf.append("\t"); | ||
| 333 | buf.append(fieldMapping.getObfName()); | ||
| 334 | buf.append(" <-> "); | ||
| 335 | buf.append(fieldMapping.getDeobfName()); | ||
| 336 | buf.append("\n"); | ||
| 337 | } | ||
| 338 | buf.append("Methods:\n"); | ||
| 339 | for (MethodMapping methodMapping : m_methodsByObf.values()) { | ||
| 340 | buf.append(methodMapping.toString()); | ||
| 341 | buf.append("\n"); | ||
| 342 | } | ||
| 343 | buf.append("Inner Classes:\n"); | ||
| 344 | for (ClassMapping classMapping : m_innerClassesByObf.values()) { | ||
| 345 | buf.append("\t"); | ||
| 346 | buf.append(classMapping.getObfName()); | ||
| 347 | buf.append(" <-> "); | ||
| 348 | buf.append(classMapping.getDeobfName()); | ||
| 349 | buf.append("\n"); | ||
| 350 | } | ||
| 351 | return buf.toString(); | ||
| 352 | } | ||
| 353 | |||
| 354 | @Override | ||
| 355 | public int compareTo(ClassMapping other) { | ||
| 356 | // sort by a, b, c, ... aa, ab, etc | ||
| 357 | if (m_obfName.length() != other.m_obfName.length()) { | ||
| 358 | return m_obfName.length() - other.m_obfName.length(); | ||
| 359 | } | ||
| 360 | return m_obfName.compareTo(other.m_obfName); | ||
| 361 | } | ||
| 362 | |||
| 363 | public boolean renameObfClass(String oldObfClassName, String newObfClassName) { | ||
| 364 | |||
| 365 | // rename inner classes | ||
| 366 | for (ClassMapping innerClassMapping : new ArrayList<ClassMapping>(m_innerClassesByObf.values())) { | ||
| 367 | if (innerClassMapping.renameObfClass(oldObfClassName, newObfClassName)) { | ||
| 368 | boolean wasRemoved = m_innerClassesByObf.remove(oldObfClassName) != null; | ||
| 369 | assert (wasRemoved); | ||
| 370 | boolean wasAdded = m_innerClassesByObf.put(newObfClassName, innerClassMapping) == null; | ||
| 371 | assert (wasAdded); | ||
| 372 | } | ||
| 373 | } | ||
| 374 | |||
| 375 | // rename method signatures | ||
| 376 | for (MethodMapping methodMapping : new ArrayList<MethodMapping>(m_methodsByObf.values())) { | ||
| 377 | String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); | ||
| 378 | if (methodMapping.renameObfClass(oldObfClassName, newObfClassName)) { | ||
| 379 | boolean wasRemoved = m_methodsByObf.remove(oldMethodKey) != null; | ||
| 380 | assert (wasRemoved); | ||
| 381 | boolean wasAdded = m_methodsByObf.put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()), methodMapping) == null; | ||
| 382 | assert (wasAdded); | ||
| 383 | } | ||
| 384 | } | ||
| 385 | |||
| 386 | if (m_obfName.equals(oldObfClassName)) { | ||
| 387 | // rename this class | ||
| 388 | m_obfName = newObfClassName; | ||
| 389 | return true; | ||
| 390 | } | ||
| 391 | return false; | ||
| 392 | } | ||
| 393 | |||
| 394 | public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) { | ||
| 395 | MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfBehaviorEntry.getName(), obfBehaviorEntry.getSignature())); | ||
| 396 | if (methodMapping != null) { | ||
| 397 | return methodMapping.containsArgument(name); | ||
| 398 | } | ||
| 399 | return false; | ||
| 400 | } | ||
| 401 | |||
| 402 | public static boolean isSimpleClassName(String name) { | ||
| 403 | return name.indexOf('/') < 0 && name.indexOf('$') < 0; | ||
| 404 | } | ||
| 405 | } | ||
diff --git a/src/cuchaz/enigma/mapping/ClassNameReplacer.java b/src/cuchaz/enigma/mapping/ClassNameReplacer.java new file mode 100644 index 0000000..bf984fd --- /dev/null +++ b/src/cuchaz/enigma/mapping/ClassNameReplacer.java | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | package cuchaz.enigma.mapping; | ||
| 2 | |||
| 3 | public interface ClassNameReplacer { | ||
| 4 | String replace(String className); | ||
| 5 | } | ||
diff --git a/src/cuchaz/enigma/mapping/ConstructorEntry.java b/src/cuchaz/enigma/mapping/ConstructorEntry.java new file mode 100644 index 0000000..5f3760f --- /dev/null +++ b/src/cuchaz/enigma/mapping/ConstructorEntry.java | |||
| @@ -0,0 +1,116 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..39e1507 --- /dev/null +++ b/src/cuchaz/enigma/mapping/Entry.java | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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/EntryPair.java b/src/cuchaz/enigma/mapping/EntryPair.java new file mode 100644 index 0000000..60411c4 --- /dev/null +++ b/src/cuchaz/enigma/mapping/EntryPair.java | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..6cc9eb7 --- /dev/null +++ b/src/cuchaz/enigma/mapping/FieldEntry.java | |||
| @@ -0,0 +1,88 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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 | |||
| 24 | // NOTE: this argument order is important for the MethodReader/MethodWriter | ||
| 25 | public FieldEntry(ClassEntry classEntry, String name) { | ||
| 26 | if (classEntry == null) { | ||
| 27 | throw new IllegalArgumentException("Class cannot be null!"); | ||
| 28 | } | ||
| 29 | if (name == null) { | ||
| 30 | throw new IllegalArgumentException("Field name cannot be null!"); | ||
| 31 | } | ||
| 32 | |||
| 33 | m_classEntry = classEntry; | ||
| 34 | m_name = name; | ||
| 35 | } | ||
| 36 | |||
| 37 | public FieldEntry(FieldEntry other) { | ||
| 38 | m_classEntry = new ClassEntry(other.m_classEntry); | ||
| 39 | m_name = other.m_name; | ||
| 40 | } | ||
| 41 | |||
| 42 | public FieldEntry(FieldEntry other, String newClassName) { | ||
| 43 | m_classEntry = new ClassEntry(newClassName); | ||
| 44 | m_name = other.m_name; | ||
| 45 | } | ||
| 46 | |||
| 47 | @Override | ||
| 48 | public ClassEntry getClassEntry() { | ||
| 49 | return m_classEntry; | ||
| 50 | } | ||
| 51 | |||
| 52 | @Override | ||
| 53 | public String getName() { | ||
| 54 | return m_name; | ||
| 55 | } | ||
| 56 | |||
| 57 | @Override | ||
| 58 | public String getClassName() { | ||
| 59 | return m_classEntry.getName(); | ||
| 60 | } | ||
| 61 | |||
| 62 | @Override | ||
| 63 | public FieldEntry cloneToNewClass(ClassEntry classEntry) { | ||
| 64 | return new FieldEntry(this, classEntry.getName()); | ||
| 65 | } | ||
| 66 | |||
| 67 | @Override | ||
| 68 | public int hashCode() { | ||
| 69 | return Util.combineHashesOrdered(m_classEntry, m_name); | ||
| 70 | } | ||
| 71 | |||
| 72 | @Override | ||
| 73 | public boolean equals(Object other) { | ||
| 74 | if (other instanceof FieldEntry) { | ||
| 75 | return equals((FieldEntry)other); | ||
| 76 | } | ||
| 77 | return false; | ||
| 78 | } | ||
| 79 | |||
| 80 | public boolean equals(FieldEntry other) { | ||
| 81 | return m_classEntry.equals(other.m_classEntry) && m_name.equals(other.m_name); | ||
| 82 | } | ||
| 83 | |||
| 84 | @Override | ||
| 85 | public String toString() { | ||
| 86 | return m_classEntry.getName() + "." + m_name; | ||
| 87 | } | ||
| 88 | } | ||
diff --git a/src/cuchaz/enigma/mapping/FieldMapping.java b/src/cuchaz/enigma/mapping/FieldMapping.java new file mode 100644 index 0000000..5f5c270 --- /dev/null +++ b/src/cuchaz/enigma/mapping/FieldMapping.java | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | |||
| 15 | public class FieldMapping implements Serializable, Comparable<FieldMapping> { | ||
| 16 | |||
| 17 | private static final long serialVersionUID = 8610742471440861315L; | ||
| 18 | |||
| 19 | private String m_obfName; | ||
| 20 | private String m_deobfName; | ||
| 21 | |||
| 22 | public FieldMapping(String obfName, String deobfName) { | ||
| 23 | m_obfName = obfName; | ||
| 24 | m_deobfName = NameValidator.validateFieldName(deobfName); | ||
| 25 | } | ||
| 26 | |||
| 27 | public String getObfName() { | ||
| 28 | return m_obfName; | ||
| 29 | } | ||
| 30 | |||
| 31 | public String getDeobfName() { | ||
| 32 | return m_deobfName; | ||
| 33 | } | ||
| 34 | |||
| 35 | public void setDeobfName(String val) { | ||
| 36 | m_deobfName = NameValidator.validateFieldName(val); | ||
| 37 | } | ||
| 38 | |||
| 39 | @Override | ||
| 40 | public int compareTo(FieldMapping other) { | ||
| 41 | return m_obfName.compareTo(other.m_obfName); | ||
| 42 | } | ||
| 43 | } | ||
diff --git a/src/cuchaz/enigma/mapping/IllegalNameException.java b/src/cuchaz/enigma/mapping/IllegalNameException.java new file mode 100644 index 0000000..aacaf3b --- /dev/null +++ b/src/cuchaz/enigma/mapping/IllegalNameException.java | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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/JavassistUtil.java b/src/cuchaz/enigma/mapping/JavassistUtil.java new file mode 100644 index 0000000..0c446c4 --- /dev/null +++ b/src/cuchaz/enigma/mapping/JavassistUtil.java | |||
| @@ -0,0 +1,83 @@ | |||
| 1 | package cuchaz.enigma.mapping; | ||
| 2 | |||
| 3 | import javassist.CtBehavior; | ||
| 4 | import javassist.CtClass; | ||
| 5 | import javassist.CtConstructor; | ||
| 6 | import javassist.CtField; | ||
| 7 | import javassist.CtMethod; | ||
| 8 | import javassist.bytecode.Descriptor; | ||
| 9 | import javassist.expr.ConstructorCall; | ||
| 10 | import javassist.expr.FieldAccess; | ||
| 11 | import javassist.expr.MethodCall; | ||
| 12 | import javassist.expr.NewExpr; | ||
| 13 | |||
| 14 | public class JavassistUtil { | ||
| 15 | |||
| 16 | public static ClassEntry getClassEntry(CtClass c) { | ||
| 17 | return new ClassEntry(Descriptor.toJvmName(c.getName())); | ||
| 18 | } | ||
| 19 | |||
| 20 | public static ClassEntry getSuperclassEntry(CtClass c) { | ||
| 21 | return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass())); | ||
| 22 | } | ||
| 23 | |||
| 24 | public static MethodEntry getMethodEntry(CtMethod method) { | ||
| 25 | return new MethodEntry( | ||
| 26 | getClassEntry(method.getDeclaringClass()), | ||
| 27 | method.getName(), | ||
| 28 | new Signature(method.getMethodInfo().getDescriptor()) | ||
| 29 | ); | ||
| 30 | } | ||
| 31 | |||
| 32 | public static MethodEntry getMethodEntry(MethodCall call) { | ||
| 33 | return new MethodEntry( | ||
| 34 | new ClassEntry(Descriptor.toJvmName(call.getClassName())), | ||
| 35 | call.getMethodName(), | ||
| 36 | new Signature(call.getSignature()) | ||
| 37 | ); | ||
| 38 | } | ||
| 39 | |||
| 40 | public static ConstructorEntry getConstructorEntry(CtConstructor constructor) { | ||
| 41 | return new ConstructorEntry( | ||
| 42 | getClassEntry(constructor.getDeclaringClass()), | ||
| 43 | new Signature(constructor.getMethodInfo().getDescriptor()) | ||
| 44 | ); | ||
| 45 | } | ||
| 46 | |||
| 47 | public static ConstructorEntry getConstructorEntry(ConstructorCall call) { | ||
| 48 | return new ConstructorEntry( | ||
| 49 | new ClassEntry(Descriptor.toJvmName(call.getClassName())), | ||
| 50 | new Signature(call.getSignature()) | ||
| 51 | ); | ||
| 52 | } | ||
| 53 | |||
| 54 | public static ConstructorEntry getConstructorEntry(NewExpr call) { | ||
| 55 | return new ConstructorEntry( | ||
| 56 | new ClassEntry(Descriptor.toJvmName(call.getClassName())), | ||
| 57 | new Signature(call.getSignature()) | ||
| 58 | ); | ||
| 59 | } | ||
| 60 | |||
| 61 | public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) { | ||
| 62 | if (behavior instanceof CtMethod) { | ||
| 63 | return getMethodEntry((CtMethod)behavior); | ||
| 64 | } else if (behavior instanceof CtConstructor) { | ||
| 65 | return getConstructorEntry((CtConstructor)behavior); | ||
| 66 | } | ||
| 67 | throw new Error("behavior is neither Method nor Constructor!"); | ||
| 68 | } | ||
| 69 | |||
| 70 | public static FieldEntry getFieldEntry(CtField field) { | ||
| 71 | return new FieldEntry( | ||
| 72 | getClassEntry(field.getDeclaringClass()), | ||
| 73 | field.getName() | ||
| 74 | ); | ||
| 75 | } | ||
| 76 | |||
| 77 | public static FieldEntry getFieldEntry(FieldAccess call) { | ||
| 78 | return new FieldEntry( | ||
| 79 | new ClassEntry(Descriptor.toJvmName(call.getClassName())), | ||
| 80 | call.getFieldName() | ||
| 81 | ); | ||
| 82 | } | ||
| 83 | } | ||
diff --git a/src/cuchaz/enigma/mapping/MappingParseException.java b/src/cuchaz/enigma/mapping/MappingParseException.java new file mode 100644 index 0000000..1974c22 --- /dev/null +++ b/src/cuchaz/enigma/mapping/MappingParseException.java | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..57d8001 --- /dev/null +++ b/src/cuchaz/enigma/mapping/Mappings.java | |||
| @@ -0,0 +1,188 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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.Map; | ||
| 17 | import java.util.Set; | ||
| 18 | |||
| 19 | import com.google.common.collect.Maps; | ||
| 20 | import com.google.common.collect.Sets; | ||
| 21 | |||
| 22 | import cuchaz.enigma.analysis.TranslationIndex; | ||
| 23 | |||
| 24 | public class Mappings implements Serializable { | ||
| 25 | |||
| 26 | private static final long serialVersionUID = 4649790259460259026L; | ||
| 27 | |||
| 28 | protected Map<String,ClassMapping> m_classesByObf; | ||
| 29 | protected Map<String,ClassMapping> m_classesByDeobf; | ||
| 30 | |||
| 31 | public Mappings() { | ||
| 32 | m_classesByObf = Maps.newHashMap(); | ||
| 33 | m_classesByDeobf = Maps.newHashMap(); | ||
| 34 | } | ||
| 35 | |||
| 36 | public Mappings(Iterable<ClassMapping> classes) { | ||
| 37 | this(); | ||
| 38 | |||
| 39 | for (ClassMapping classMapping : classes) { | ||
| 40 | m_classesByObf.put(classMapping.getObfName(), classMapping); | ||
| 41 | if (classMapping.getDeobfName() != null) { | ||
| 42 | m_classesByDeobf.put(classMapping.getDeobfName(), classMapping); | ||
| 43 | } | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | public Collection<ClassMapping> classes() { | ||
| 48 | assert (m_classesByObf.size() >= m_classesByDeobf.size()); | ||
| 49 | return m_classesByObf.values(); | ||
| 50 | } | ||
| 51 | |||
| 52 | public void addClassMapping(ClassMapping classMapping) { | ||
| 53 | if (m_classesByObf.containsKey(classMapping.getObfName())) { | ||
| 54 | throw new Error("Already have mapping for " + classMapping.getObfName()); | ||
| 55 | } | ||
| 56 | boolean obfWasAdded = m_classesByObf.put(classMapping.getObfName(), classMapping) == null; | ||
| 57 | assert (obfWasAdded); | ||
| 58 | if (classMapping.getDeobfName() != null) { | ||
| 59 | if (m_classesByDeobf.containsKey(classMapping.getDeobfName())) { | ||
| 60 | throw new Error("Already have mapping for " + classMapping.getDeobfName()); | ||
| 61 | } | ||
| 62 | boolean deobfWasAdded = m_classesByDeobf.put(classMapping.getDeobfName(), classMapping) == null; | ||
| 63 | assert (deobfWasAdded); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | public void removeClassMapping(ClassMapping classMapping) { | ||
| 68 | boolean obfWasRemoved = m_classesByObf.remove(classMapping.getObfName()) != null; | ||
| 69 | assert (obfWasRemoved); | ||
| 70 | if (classMapping.getDeobfName() != null) { | ||
| 71 | boolean deobfWasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null; | ||
| 72 | assert (deobfWasRemoved); | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | public ClassMapping getClassByObf(ClassEntry entry) { | ||
| 77 | return getClassByObf(entry.getName()); | ||
| 78 | } | ||
| 79 | |||
| 80 | public ClassMapping getClassByObf(String obfName) { | ||
| 81 | return m_classesByObf.get(obfName); | ||
| 82 | } | ||
| 83 | |||
| 84 | public ClassMapping getClassByDeobf(ClassEntry entry) { | ||
| 85 | return getClassByDeobf(entry.getName()); | ||
| 86 | } | ||
| 87 | |||
| 88 | public ClassMapping getClassByDeobf(String deobfName) { | ||
| 89 | return m_classesByDeobf.get(deobfName); | ||
| 90 | } | ||
| 91 | |||
| 92 | public Translator getTranslator(TranslationDirection direction, TranslationIndex index) { | ||
| 93 | switch (direction) { | ||
| 94 | case Deobfuscating: | ||
| 95 | |||
| 96 | return new Translator(direction, m_classesByObf, index); | ||
| 97 | |||
| 98 | case Obfuscating: | ||
| 99 | |||
| 100 | // fill in the missing deobf class entries with obf entries | ||
| 101 | Map<String,ClassMapping> classes = Maps.newHashMap(); | ||
| 102 | for (ClassMapping classMapping : classes()) { | ||
| 103 | if (classMapping.getDeobfName() != null) { | ||
| 104 | classes.put(classMapping.getDeobfName(), classMapping); | ||
| 105 | } else { | ||
| 106 | classes.put(classMapping.getObfName(), classMapping); | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | // translate the translation index | ||
| 111 | // NOTE: this isn't actually recursive | ||
| 112 | TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.Deobfuscating, index)); | ||
| 113 | |||
| 114 | return new Translator(direction, classes, deobfIndex); | ||
| 115 | |||
| 116 | default: | ||
| 117 | throw new Error("Invalid translation direction!"); | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | @Override | ||
| 122 | public String toString() { | ||
| 123 | StringBuilder buf = new StringBuilder(); | ||
| 124 | for (ClassMapping classMapping : m_classesByObf.values()) { | ||
| 125 | buf.append(classMapping.toString()); | ||
| 126 | buf.append("\n"); | ||
| 127 | } | ||
| 128 | return buf.toString(); | ||
| 129 | } | ||
| 130 | |||
| 131 | public void renameObfClass(String oldObfName, String newObfName) { | ||
| 132 | for (ClassMapping classMapping : new ArrayList<ClassMapping>(classes())) { | ||
| 133 | if (classMapping.renameObfClass(oldObfName, newObfName)) { | ||
| 134 | boolean wasRemoved = m_classesByObf.remove(oldObfName) != null; | ||
| 135 | assert (wasRemoved); | ||
| 136 | boolean wasAdded = m_classesByObf.put(newObfName, classMapping) == null; | ||
| 137 | assert (wasAdded); | ||
| 138 | } | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | public Set<String> getAllObfClassNames() { | ||
| 143 | final Set<String> classNames = Sets.newHashSet(); | ||
| 144 | for (ClassMapping classMapping : classes()) { | ||
| 145 | |||
| 146 | // add the class name | ||
| 147 | classNames.add(classMapping.getObfName()); | ||
| 148 | |||
| 149 | // add classes from method signatures | ||
| 150 | for (MethodMapping methodMapping : classMapping.methods()) { | ||
| 151 | for (Type type : methodMapping.getObfSignature().types()) { | ||
| 152 | if (type.hasClass()) { | ||
| 153 | classNames.add(type.getClassEntry().getClassName()); | ||
| 154 | } | ||
| 155 | } | ||
| 156 | } | ||
| 157 | } | ||
| 158 | return classNames; | ||
| 159 | } | ||
| 160 | |||
| 161 | public boolean containsDeobfClass(String deobfName) { | ||
| 162 | return m_classesByDeobf.containsKey(deobfName); | ||
| 163 | } | ||
| 164 | |||
| 165 | public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName) { | ||
| 166 | ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName()); | ||
| 167 | if (classMapping != null) { | ||
| 168 | return classMapping.containsDeobfField(deobfName); | ||
| 169 | } | ||
| 170 | return false; | ||
| 171 | } | ||
| 172 | |||
| 173 | public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, Signature deobfSignature) { | ||
| 174 | ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName()); | ||
| 175 | if (classMapping != null) { | ||
| 176 | return classMapping.containsDeobfMethod(deobfName, deobfSignature); | ||
| 177 | } | ||
| 178 | return false; | ||
| 179 | } | ||
| 180 | |||
| 181 | public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) { | ||
| 182 | ClassMapping classMapping = m_classesByObf.get(obfBehaviorEntry.getClassName()); | ||
| 183 | if (classMapping != null) { | ||
| 184 | return classMapping.containsArgument(obfBehaviorEntry, name); | ||
| 185 | } | ||
| 186 | return false; | ||
| 187 | } | ||
| 188 | } | ||
diff --git a/src/cuchaz/enigma/mapping/MappingsReader.java b/src/cuchaz/enigma/mapping/MappingsReader.java new file mode 100644 index 0000000..adf460e --- /dev/null +++ b/src/cuchaz/enigma/mapping/MappingsReader.java | |||
| @@ -0,0 +1,175 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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 | import cuchaz.enigma.Constants; | ||
| 21 | |||
| 22 | public class MappingsReader { | ||
| 23 | |||
| 24 | public Mappings read(Reader in) throws IOException, MappingParseException { | ||
| 25 | return read(new BufferedReader(in)); | ||
| 26 | } | ||
| 27 | |||
| 28 | public Mappings read(BufferedReader in) throws IOException, MappingParseException { | ||
| 29 | Mappings mappings = new Mappings(); | ||
| 30 | Deque<Object> mappingStack = Queues.newArrayDeque(); | ||
| 31 | |||
| 32 | int lineNumber = 0; | ||
| 33 | String line = null; | ||
| 34 | while ( (line = in.readLine()) != null) { | ||
| 35 | lineNumber++; | ||
| 36 | |||
| 37 | // strip comments | ||
| 38 | int commentPos = line.indexOf('#'); | ||
| 39 | if (commentPos >= 0) { | ||
| 40 | line = line.substring(0, commentPos); | ||
| 41 | } | ||
| 42 | |||
| 43 | // skip blank lines | ||
| 44 | if (line.trim().length() <= 0) { | ||
| 45 | continue; | ||
| 46 | } | ||
| 47 | |||
| 48 | // get the indent of this line | ||
| 49 | int indent = 0; | ||
| 50 | for (int i = 0; i < line.length(); i++) { | ||
| 51 | if (line.charAt(i) != '\t') { | ||
| 52 | break; | ||
| 53 | } | ||
| 54 | indent++; | ||
| 55 | } | ||
| 56 | |||
| 57 | // handle stack pops | ||
| 58 | while (indent < mappingStack.size()) { | ||
| 59 | mappingStack.pop(); | ||
| 60 | } | ||
| 61 | |||
| 62 | String[] parts = line.trim().split("\\s"); | ||
| 63 | try { | ||
| 64 | // read the first token | ||
| 65 | String token = parts[0]; | ||
| 66 | |||
| 67 | if (token.equalsIgnoreCase("CLASS")) { | ||
| 68 | ClassMapping classMapping; | ||
| 69 | if (indent == 0) { | ||
| 70 | // outer class | ||
| 71 | classMapping = readClass(parts, false); | ||
| 72 | mappings.addClassMapping(classMapping); | ||
| 73 | } else if (indent == 1) { | ||
| 74 | // inner class | ||
| 75 | if (! (mappingStack.getFirst() instanceof ClassMapping)) { | ||
| 76 | throw new MappingParseException(lineNumber, "Unexpected CLASS entry here!"); | ||
| 77 | } | ||
| 78 | |||
| 79 | classMapping = readClass(parts, true); | ||
| 80 | ((ClassMapping)mappingStack.getFirst()).addInnerClassMapping(classMapping); | ||
| 81 | } else { | ||
| 82 | throw new MappingParseException(lineNumber, "Unexpected CLASS entry nesting!"); | ||
| 83 | } | ||
| 84 | mappingStack.push(classMapping); | ||
| 85 | } else if (token.equalsIgnoreCase("FIELD")) { | ||
| 86 | if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof ClassMapping)) { | ||
| 87 | throw new MappingParseException(lineNumber, "Unexpected FIELD entry here!"); | ||
| 88 | } | ||
| 89 | ((ClassMapping)mappingStack.getFirst()).addFieldMapping(readField(parts)); | ||
| 90 | } else if (token.equalsIgnoreCase("METHOD")) { | ||
| 91 | if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof ClassMapping)) { | ||
| 92 | throw new MappingParseException(lineNumber, "Unexpected METHOD entry here!"); | ||
| 93 | } | ||
| 94 | MethodMapping methodMapping = readMethod(parts); | ||
| 95 | ((ClassMapping)mappingStack.getFirst()).addMethodMapping(methodMapping); | ||
| 96 | mappingStack.push(methodMapping); | ||
| 97 | } else if (token.equalsIgnoreCase("ARG")) { | ||
| 98 | if (mappingStack.isEmpty() || ! (mappingStack.getFirst() instanceof MethodMapping)) { | ||
| 99 | throw new MappingParseException(lineNumber, "Unexpected ARG entry here!"); | ||
| 100 | } | ||
| 101 | ((MethodMapping)mappingStack.getFirst()).addArgumentMapping(readArgument(parts)); | ||
| 102 | } | ||
| 103 | } catch (ArrayIndexOutOfBoundsException | NumberFormatException ex) { | ||
| 104 | throw new MappingParseException(lineNumber, "Malformed line!"); | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | return mappings; | ||
| 109 | } | ||
| 110 | |||
| 111 | private ArgumentMapping readArgument(String[] parts) { | ||
| 112 | return new ArgumentMapping(Integer.parseInt(parts[1]), parts[2]); | ||
| 113 | } | ||
| 114 | |||
| 115 | private ClassMapping readClass(String[] parts, boolean makeSimple) { | ||
| 116 | if (parts.length == 2) { | ||
| 117 | String obfName = processName(parts[1], makeSimple); | ||
| 118 | return new ClassMapping(obfName); | ||
| 119 | } else { | ||
| 120 | String obfName = processName(parts[1], makeSimple); | ||
| 121 | String deobfName = processName(parts[2], makeSimple); | ||
| 122 | return new ClassMapping(obfName, deobfName); | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | private String processName(String name, boolean makeSimple) { | ||
| 127 | if (makeSimple) { | ||
| 128 | return new ClassEntry(name).getSimpleName(); | ||
| 129 | } else { | ||
| 130 | return moveClassOutOfDefaultPackage(name, Constants.NonePackage); | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | private String moveClassOutOfDefaultPackage(String className, String newPackageName) { | ||
| 135 | ClassEntry classEntry = new ClassEntry(className); | ||
| 136 | if (classEntry.isInDefaultPackage()) { | ||
| 137 | return newPackageName + "/" + classEntry.getName(); | ||
| 138 | } | ||
| 139 | return className; | ||
| 140 | } | ||
| 141 | |||
| 142 | private FieldMapping readField(String[] parts) { | ||
| 143 | return new FieldMapping(parts[1], parts[2]); | ||
| 144 | } | ||
| 145 | |||
| 146 | private MethodMapping readMethod(String[] parts) { | ||
| 147 | if (parts.length == 3) { | ||
| 148 | String obfName = parts[1]; | ||
| 149 | Signature obfSignature = moveSignatureOutOfDefaultPackage(new Signature(parts[2]), Constants.NonePackage); | ||
| 150 | return new MethodMapping(obfName, obfSignature); | ||
| 151 | } else { | ||
| 152 | String obfName = parts[1]; | ||
| 153 | String deobfName = parts[2]; | ||
| 154 | Signature obfSignature = moveSignatureOutOfDefaultPackage(new Signature(parts[3]), Constants.NonePackage); | ||
| 155 | if (obfName.equals(deobfName)) { | ||
| 156 | return new MethodMapping(obfName, obfSignature); | ||
| 157 | } else { | ||
| 158 | return new MethodMapping(obfName, obfSignature, deobfName); | ||
| 159 | } | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 163 | private Signature moveSignatureOutOfDefaultPackage(Signature signature, final String newPackageName) { | ||
| 164 | return new Signature(signature, new ClassNameReplacer() { | ||
| 165 | @Override | ||
| 166 | public String replace(String className) { | ||
| 167 | ClassEntry classEntry = new ClassEntry(className); | ||
| 168 | if (classEntry.isInDefaultPackage()) { | ||
| 169 | return newPackageName + "/" + className; | ||
| 170 | } | ||
| 171 | return null; | ||
| 172 | } | ||
| 173 | }); | ||
| 174 | } | ||
| 175 | } | ||
diff --git a/src/cuchaz/enigma/mapping/MappingsRenamer.java b/src/cuchaz/enigma/mapping/MappingsRenamer.java new file mode 100644 index 0000000..0a41c2b --- /dev/null +++ b/src/cuchaz/enigma/mapping/MappingsRenamer.java | |||
| @@ -0,0 +1,237 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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.Set; | ||
| 17 | import java.util.zip.GZIPOutputStream; | ||
| 18 | |||
| 19 | import cuchaz.enigma.Constants; | ||
| 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 | deobfName = NameValidator.validateClassName(deobfName, !obf.isInnerClass()); | ||
| 34 | ClassEntry targetEntry = new ClassEntry(deobfName); | ||
| 35 | if (m_mappings.containsDeobfClass(deobfName) || m_index.containsObfClass(targetEntry)) { | ||
| 36 | throw new IllegalNameException(deobfName, "There is already a class with that name"); | ||
| 37 | } | ||
| 38 | |||
| 39 | ClassMapping classMapping = getOrCreateClassMapping(obf); | ||
| 40 | |||
| 41 | if (obf.isInnerClass()) { | ||
| 42 | classMapping.setInnerClassName(obf.getInnerClassName(), deobfName); | ||
| 43 | } else { | ||
| 44 | if (classMapping.getDeobfName() != null) { | ||
| 45 | boolean wasRemoved = m_mappings.m_classesByDeobf.remove(classMapping.getDeobfName()) != null; | ||
| 46 | assert (wasRemoved); | ||
| 47 | } | ||
| 48 | classMapping.setDeobfName(deobfName); | ||
| 49 | boolean wasAdded = m_mappings.m_classesByDeobf.put(deobfName, classMapping) == null; | ||
| 50 | assert (wasAdded); | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | public void removeClassMapping(ClassEntry obf) { | ||
| 55 | ClassMapping classMapping = getClassMapping(obf); | ||
| 56 | if (obf.isInnerClass()) { | ||
| 57 | classMapping.setInnerClassName(obf.getName(), null); | ||
| 58 | } else { | ||
| 59 | boolean wasRemoved = m_mappings.m_classesByDeobf.remove(classMapping.getDeobfName()) != null; | ||
| 60 | assert (wasRemoved); | ||
| 61 | classMapping.setDeobfName(null); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | public void markClassAsDeobfuscated(ClassEntry obf) { | ||
| 66 | ClassMapping classMapping = getOrCreateClassMapping(obf); | ||
| 67 | if (obf.isInnerClass()) { | ||
| 68 | String innerClassName = Constants.NonePackage + "/" + obf.getInnerClassName(); | ||
| 69 | classMapping.setInnerClassName(innerClassName, innerClassName); | ||
| 70 | } else { | ||
| 71 | classMapping.setDeobfName(obf.getName()); | ||
| 72 | boolean wasAdded = m_mappings.m_classesByDeobf.put(obf.getName(), classMapping) == null; | ||
| 73 | assert (wasAdded); | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | public void setFieldName(FieldEntry obf, String deobfName) { | ||
| 78 | deobfName = NameValidator.validateFieldName(deobfName); | ||
| 79 | FieldEntry targetEntry = new FieldEntry(obf.getClassEntry(), deobfName); | ||
| 80 | if (m_mappings.containsDeobfField(obf.getClassEntry(), deobfName) || m_index.containsObfField(targetEntry)) { | ||
| 81 | throw new IllegalNameException(deobfName, "There is already a field with that name"); | ||
| 82 | } | ||
| 83 | |||
| 84 | ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); | ||
| 85 | classMapping.setFieldName(obf.getName(), deobfName); | ||
| 86 | } | ||
| 87 | |||
| 88 | public void removeFieldMapping(FieldEntry obf) { | ||
| 89 | ClassMapping classMapping = getClassMappingOrInnerClassMapping(obf.getClassEntry()); | ||
| 90 | classMapping.setFieldName(obf.getName(), null); | ||
| 91 | } | ||
| 92 | |||
| 93 | public void markFieldAsDeobfuscated(FieldEntry obf) { | ||
| 94 | ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); | ||
| 95 | classMapping.setFieldName(obf.getName(), obf.getName()); | ||
| 96 | } | ||
| 97 | |||
| 98 | public void setMethodTreeName(MethodEntry obf, String deobfName) { | ||
| 99 | Set<MethodEntry> implementations = m_index.getRelatedMethodImplementations(obf); | ||
| 100 | |||
| 101 | deobfName = NameValidator.validateMethodName(deobfName); | ||
| 102 | for (MethodEntry entry : implementations) { | ||
| 103 | Signature deobfSignature = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateSignature(obf.getSignature()); | ||
| 104 | MethodEntry targetEntry = new MethodEntry(entry.getClassEntry(), deobfName, deobfSignature); | ||
| 105 | if (m_mappings.containsDeobfMethod(entry.getClassEntry(), deobfName, entry.getSignature()) || m_index.containsObfBehavior(targetEntry)) { | ||
| 106 | String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(entry.getClassName()); | ||
| 107 | throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | for (MethodEntry entry : implementations) { | ||
| 112 | setMethodName(entry, deobfName); | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | public void setMethodName(MethodEntry obf, String deobfName) { | ||
| 117 | deobfName = NameValidator.validateMethodName(deobfName); | ||
| 118 | MethodEntry targetEntry = new MethodEntry(obf.getClassEntry(), deobfName, obf.getSignature()); | ||
| 119 | if (m_mappings.containsDeobfMethod(obf.getClassEntry(), deobfName, obf.getSignature()) || m_index.containsObfBehavior(targetEntry)) { | ||
| 120 | String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(obf.getClassName()); | ||
| 121 | throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); | ||
| 122 | } | ||
| 123 | |||
| 124 | ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); | ||
| 125 | classMapping.setMethodName(obf.getName(), obf.getSignature(), deobfName); | ||
| 126 | } | ||
| 127 | |||
| 128 | public void removeMethodTreeMapping(MethodEntry obf) { | ||
| 129 | for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) { | ||
| 130 | removeMethodMapping(implementation); | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | public void removeMethodMapping(MethodEntry obf) { | ||
| 135 | ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); | ||
| 136 | classMapping.setMethodName(obf.getName(), obf.getSignature(), null); | ||
| 137 | } | ||
| 138 | |||
| 139 | public void markMethodTreeAsDeobfuscated(MethodEntry obf) { | ||
| 140 | for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) { | ||
| 141 | markMethodAsDeobfuscated(implementation); | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | public void markMethodAsDeobfuscated(MethodEntry obf) { | ||
| 146 | ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); | ||
| 147 | classMapping.setMethodName(obf.getName(), obf.getSignature(), obf.getName()); | ||
| 148 | } | ||
| 149 | |||
| 150 | public void setArgumentName(ArgumentEntry obf, String deobfName) { | ||
| 151 | deobfName = NameValidator.validateArgumentName(deobfName); | ||
| 152 | // NOTE: don't need to check arguments for name collisions with names determined by Procyon | ||
| 153 | if (m_mappings.containsArgument(obf.getBehaviorEntry(), deobfName)) { | ||
| 154 | throw new IllegalNameException(deobfName, "There is already an argument with that name"); | ||
| 155 | } | ||
| 156 | |||
| 157 | ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); | ||
| 158 | classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), deobfName); | ||
| 159 | } | ||
| 160 | |||
| 161 | public void removeArgumentMapping(ArgumentEntry obf) { | ||
| 162 | ClassMapping classMapping = getClassMappingOrInnerClassMapping(obf.getClassEntry()); | ||
| 163 | classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex()); | ||
| 164 | } | ||
| 165 | |||
| 166 | public void markArgumentAsDeobfuscated(ArgumentEntry obf) { | ||
| 167 | ClassMapping classMapping = getOrCreateClassMappingOrInnerClassMapping(obf.getClassEntry()); | ||
| 168 | classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), obf.getName()); | ||
| 169 | } | ||
| 170 | |||
| 171 | public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) { | ||
| 172 | classMapping.removeFieldMapping(fieldMapping); | ||
| 173 | ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass); | ||
| 174 | if (!targetClassMapping.containsObfField(fieldMapping.getObfName())) { | ||
| 175 | if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName())) { | ||
| 176 | targetClassMapping.addFieldMapping(fieldMapping); | ||
| 177 | return true; | ||
| 178 | } else { | ||
| 179 | System.err.println("WARNING: deobf field was already there: " + obfClass + "." + fieldMapping.getDeobfName()); | ||
| 180 | } | ||
| 181 | } | ||
| 182 | return false; | ||
| 183 | } | ||
| 184 | |||
| 185 | public boolean moveMethodToObfClass(ClassMapping classMapping, MethodMapping methodMapping, ClassEntry obfClass) { | ||
| 186 | classMapping.removeMethodMapping(methodMapping); | ||
| 187 | ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass); | ||
| 188 | if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfSignature())) { | ||
| 189 | if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfSignature())) { | ||
| 190 | targetClassMapping.addMethodMapping(methodMapping); | ||
| 191 | return true; | ||
| 192 | } else { | ||
| 193 | System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfSignature()); | ||
| 194 | } | ||
| 195 | } | ||
| 196 | return false; | ||
| 197 | } | ||
| 198 | |||
| 199 | public void write(OutputStream out) throws IOException { | ||
| 200 | // TEMP: just use the object output for now. We can find a more efficient storage format later | ||
| 201 | GZIPOutputStream gzipout = new GZIPOutputStream(out); | ||
| 202 | ObjectOutputStream oout = new ObjectOutputStream(gzipout); | ||
| 203 | oout.writeObject(this); | ||
| 204 | gzipout.finish(); | ||
| 205 | } | ||
| 206 | |||
| 207 | private ClassMapping getClassMapping(ClassEntry obfClassEntry) { | ||
| 208 | return m_mappings.m_classesByObf.get(obfClassEntry.getOuterClassName()); | ||
| 209 | } | ||
| 210 | |||
| 211 | private ClassMapping getOrCreateClassMapping(ClassEntry obfClassEntry) { | ||
| 212 | String obfClassName = obfClassEntry.getOuterClassName(); | ||
| 213 | ClassMapping classMapping = m_mappings.m_classesByObf.get(obfClassName); | ||
| 214 | if (classMapping == null) { | ||
| 215 | classMapping = new ClassMapping(obfClassName); | ||
| 216 | boolean obfWasAdded = m_mappings.m_classesByObf.put(classMapping.getObfName(), classMapping) == null; | ||
| 217 | assert (obfWasAdded); | ||
| 218 | } | ||
| 219 | return classMapping; | ||
| 220 | } | ||
| 221 | |||
| 222 | private ClassMapping getClassMappingOrInnerClassMapping(ClassEntry obfClassEntry) { | ||
| 223 | ClassMapping classMapping = getClassMapping(obfClassEntry); | ||
| 224 | if (obfClassEntry.isInDefaultPackage()) { | ||
| 225 | classMapping = classMapping.getInnerClassByObf(obfClassEntry.getInnerClassName()); | ||
| 226 | } | ||
| 227 | return classMapping; | ||
| 228 | } | ||
| 229 | |||
| 230 | private ClassMapping getOrCreateClassMappingOrInnerClassMapping(ClassEntry obfClassEntry) { | ||
| 231 | ClassMapping classMapping = getOrCreateClassMapping(obfClassEntry); | ||
| 232 | if (obfClassEntry.isInnerClass()) { | ||
| 233 | classMapping = classMapping.getOrCreateInnerClass(obfClassEntry.getInnerClassName()); | ||
| 234 | } | ||
| 235 | return classMapping; | ||
| 236 | } | ||
| 237 | } | ||
diff --git a/src/cuchaz/enigma/mapping/MappingsWriter.java b/src/cuchaz/enigma/mapping/MappingsWriter.java new file mode 100644 index 0000000..5ac409f --- /dev/null +++ b/src/cuchaz/enigma/mapping/MappingsWriter.java | |||
| @@ -0,0 +1,88 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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.getObfName()); | ||
| 35 | } else { | ||
| 36 | out.format("%sCLASS %s %s\n", getIndent(depth), classMapping.getObfName(), classMapping.getDeobfName()); | ||
| 37 | } | ||
| 38 | |||
| 39 | for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) { | ||
| 40 | write(out, innerClassMapping, depth + 1); | ||
| 41 | } | ||
| 42 | |||
| 43 | for (FieldMapping fieldMapping : sorted(classMapping.fields())) { | ||
| 44 | write(out, fieldMapping, depth + 1); | ||
| 45 | } | ||
| 46 | |||
| 47 | for (MethodMapping methodMapping : sorted(classMapping.methods())) { | ||
| 48 | write(out, methodMapping, depth + 1); | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | private void write(PrintWriter out, FieldMapping fieldMapping, int depth) throws IOException { | ||
| 53 | out.format("%sFIELD %s %s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName()); | ||
| 54 | } | ||
| 55 | |||
| 56 | private void write(PrintWriter out, MethodMapping methodMapping, int depth) throws IOException { | ||
| 57 | if (methodMapping.getDeobfName() == null) { | ||
| 58 | out.format("%sMETHOD %s %s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfSignature()); | ||
| 59 | } else { | ||
| 60 | out.format("%sMETHOD %s %s %s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfSignature()); | ||
| 61 | } | ||
| 62 | |||
| 63 | for (ArgumentMapping argumentMapping : sorted(methodMapping.arguments())) { | ||
| 64 | write(out, argumentMapping, depth + 1); | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | private void write(PrintWriter out, ArgumentMapping argumentMapping, int depth) throws IOException { | ||
| 69 | out.format("%sARG %d %s\n", getIndent(depth), argumentMapping.getIndex(), argumentMapping.getName()); | ||
| 70 | } | ||
| 71 | |||
| 72 | private <T extends Comparable<T>> List<T> sorted(Iterable<T> classes) { | ||
| 73 | List<T> out = new ArrayList<T>(); | ||
| 74 | for (T t : classes) { | ||
| 75 | out.add(t); | ||
| 76 | } | ||
| 77 | Collections.sort(out); | ||
| 78 | return out; | ||
| 79 | } | ||
| 80 | |||
| 81 | private String getIndent(int depth) { | ||
| 82 | StringBuilder buf = new StringBuilder(); | ||
| 83 | for (int i = 0; i < depth; i++) { | ||
| 84 | buf.append("\t"); | ||
| 85 | } | ||
| 86 | return buf.toString(); | ||
| 87 | } | ||
| 88 | } | ||
diff --git a/src/cuchaz/enigma/mapping/MethodEntry.java b/src/cuchaz/enigma/mapping/MethodEntry.java new file mode 100644 index 0000000..057e02b --- /dev/null +++ b/src/cuchaz/enigma/mapping/MethodEntry.java | |||
| @@ -0,0 +1,104 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..1704428 --- /dev/null +++ b/src/cuchaz/enigma/mapping/MethodMapping.java | |||
| @@ -0,0 +1,161 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.io.Serializable; | ||
| 14 | import java.util.Map; | ||
| 15 | import java.util.TreeMap; | ||
| 16 | |||
| 17 | public class MethodMapping implements Serializable, Comparable<MethodMapping> { | ||
| 18 | |||
| 19 | private static final long serialVersionUID = -4409570216084263978L; | ||
| 20 | |||
| 21 | private String m_obfName; | ||
| 22 | private String m_deobfName; | ||
| 23 | private Signature m_obfSignature; | ||
| 24 | private Map<Integer,ArgumentMapping> m_arguments; | ||
| 25 | |||
| 26 | public MethodMapping(String obfName, Signature obfSignature) { | ||
| 27 | this(obfName, obfSignature, null); | ||
| 28 | } | ||
| 29 | |||
| 30 | public MethodMapping(String obfName, Signature obfSignature, String deobfName) { | ||
| 31 | if (obfName == null) { | ||
| 32 | throw new IllegalArgumentException("obf name cannot be null!"); | ||
| 33 | } | ||
| 34 | if (obfSignature == null) { | ||
| 35 | throw new IllegalArgumentException("obf signature cannot be null!"); | ||
| 36 | } | ||
| 37 | m_obfName = obfName; | ||
| 38 | m_deobfName = NameValidator.validateMethodName(deobfName); | ||
| 39 | m_obfSignature = obfSignature; | ||
| 40 | m_arguments = new TreeMap<Integer,ArgumentMapping>(); | ||
| 41 | } | ||
| 42 | |||
| 43 | public String getObfName() { | ||
| 44 | return m_obfName; | ||
| 45 | } | ||
| 46 | |||
| 47 | public String getDeobfName() { | ||
| 48 | return m_deobfName; | ||
| 49 | } | ||
| 50 | |||
| 51 | public void setDeobfName(String val) { | ||
| 52 | m_deobfName = NameValidator.validateMethodName(val); | ||
| 53 | } | ||
| 54 | |||
| 55 | public Signature getObfSignature() { | ||
| 56 | return m_obfSignature; | ||
| 57 | } | ||
| 58 | |||
| 59 | public Iterable<ArgumentMapping> arguments() { | ||
| 60 | return m_arguments.values(); | ||
| 61 | } | ||
| 62 | |||
| 63 | public boolean isConstructor() { | ||
| 64 | return m_obfName.startsWith("<"); | ||
| 65 | } | ||
| 66 | |||
| 67 | public void addArgumentMapping(ArgumentMapping argumentMapping) { | ||
| 68 | boolean wasAdded = m_arguments.put(argumentMapping.getIndex(), argumentMapping) == null; | ||
| 69 | assert (wasAdded); | ||
| 70 | } | ||
| 71 | |||
| 72 | public String getObfArgumentName(int index) { | ||
| 73 | ArgumentMapping argumentMapping = m_arguments.get(index); | ||
| 74 | if (argumentMapping != null) { | ||
| 75 | return argumentMapping.getName(); | ||
| 76 | } | ||
| 77 | |||
| 78 | return null; | ||
| 79 | } | ||
| 80 | |||
| 81 | public String getDeobfArgumentName(int index) { | ||
| 82 | ArgumentMapping argumentMapping = m_arguments.get(index); | ||
| 83 | if (argumentMapping != null) { | ||
| 84 | return argumentMapping.getName(); | ||
| 85 | } | ||
| 86 | |||
| 87 | return null; | ||
| 88 | } | ||
| 89 | |||
| 90 | public void setArgumentName(int index, String name) { | ||
| 91 | ArgumentMapping argumentMapping = m_arguments.get(index); | ||
| 92 | if (argumentMapping == null) { | ||
| 93 | argumentMapping = new ArgumentMapping(index, name); | ||
| 94 | boolean wasAdded = m_arguments.put(index, argumentMapping) == null; | ||
| 95 | assert (wasAdded); | ||
| 96 | } else { | ||
| 97 | argumentMapping.setName(name); | ||
| 98 | } | ||
| 99 | } | ||
| 100 | |||
| 101 | public void removeArgumentName(int index) { | ||
| 102 | boolean wasRemoved = m_arguments.remove(index) != null; | ||
| 103 | assert (wasRemoved); | ||
| 104 | } | ||
| 105 | |||
| 106 | @Override | ||
| 107 | public String toString() { | ||
| 108 | StringBuilder buf = new StringBuilder(); | ||
| 109 | buf.append("\t"); | ||
| 110 | buf.append(m_obfName); | ||
| 111 | buf.append(" <-> "); | ||
| 112 | buf.append(m_deobfName); | ||
| 113 | buf.append("\n"); | ||
| 114 | buf.append("\t"); | ||
| 115 | buf.append(m_obfSignature); | ||
| 116 | buf.append("\n"); | ||
| 117 | buf.append("\tArguments:\n"); | ||
| 118 | for (ArgumentMapping argumentMapping : m_arguments.values()) { | ||
| 119 | buf.append("\t\t"); | ||
| 120 | buf.append(argumentMapping.getIndex()); | ||
| 121 | buf.append(" -> "); | ||
| 122 | buf.append(argumentMapping.getName()); | ||
| 123 | buf.append("\n"); | ||
| 124 | } | ||
| 125 | return buf.toString(); | ||
| 126 | } | ||
| 127 | |||
| 128 | @Override | ||
| 129 | public int compareTo(MethodMapping other) { | ||
| 130 | return (m_obfName + m_obfSignature).compareTo(other.m_obfName + other.m_obfSignature); | ||
| 131 | } | ||
| 132 | |||
| 133 | public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) { | ||
| 134 | |||
| 135 | // rename obf classes in the signature | ||
| 136 | Signature newSignature = new Signature(m_obfSignature, new ClassNameReplacer() { | ||
| 137 | @Override | ||
| 138 | public String replace(String className) { | ||
| 139 | if (className.equals(oldObfClassName)) { | ||
| 140 | return newObfClassName; | ||
| 141 | } | ||
| 142 | return null; | ||
| 143 | } | ||
| 144 | }); | ||
| 145 | |||
| 146 | if (!newSignature.equals(m_obfSignature)) { | ||
| 147 | m_obfSignature = newSignature; | ||
| 148 | return true; | ||
| 149 | } | ||
| 150 | return false; | ||
| 151 | } | ||
| 152 | |||
| 153 | public boolean containsArgument(String name) { | ||
| 154 | for (ArgumentMapping argumentMapping : m_arguments.values()) { | ||
| 155 | if (argumentMapping.getName().equals(name)) { | ||
| 156 | return true; | ||
| 157 | } | ||
| 158 | } | ||
| 159 | return false; | ||
| 160 | } | ||
| 161 | } | ||
diff --git a/src/cuchaz/enigma/mapping/NameValidator.java b/src/cuchaz/enigma/mapping/NameValidator.java new file mode 100644 index 0000000..35a17f9 --- /dev/null +++ b/src/cuchaz/enigma/mapping/NameValidator.java | |||
| @@ -0,0 +1,80 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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/Signature.java b/src/cuchaz/enigma/mapping/Signature.java new file mode 100644 index 0000000..ff7f807 --- /dev/null +++ b/src/cuchaz/enigma/mapping/Signature.java | |||
| @@ -0,0 +1,109 @@ | |||
| 1 | package cuchaz.enigma.mapping; | ||
| 2 | |||
| 3 | import java.util.List; | ||
| 4 | |||
| 5 | import com.beust.jcommander.internal.Lists; | ||
| 6 | |||
| 7 | import cuchaz.enigma.Util; | ||
| 8 | |||
| 9 | public class Signature { | ||
| 10 | |||
| 11 | private List<Type> m_argumentTypes; | ||
| 12 | private Type m_returnType; | ||
| 13 | |||
| 14 | public Signature(String signature) { | ||
| 15 | try { | ||
| 16 | m_argumentTypes = Lists.newArrayList(); | ||
| 17 | int i=0; | ||
| 18 | while (i<signature.length()) { | ||
| 19 | char c = signature.charAt(i); | ||
| 20 | if (c == '(') { | ||
| 21 | assert(m_argumentTypes.isEmpty()); | ||
| 22 | assert(m_returnType == null); | ||
| 23 | i++; | ||
| 24 | } else if (c == ')') { | ||
| 25 | i++; | ||
| 26 | break; | ||
| 27 | } else { | ||
| 28 | String type = Type.parseFirst(signature.substring(i)); | ||
| 29 | m_argumentTypes.add(new Type(type)); | ||
| 30 | i += type.length(); | ||
| 31 | } | ||
| 32 | } | ||
| 33 | m_returnType = new Type(Type.parseFirst(signature.substring(i))); | ||
| 34 | } catch (Exception ex) { | ||
| 35 | throw new IllegalArgumentException("Unable to parse signature: " + signature, ex); | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | public Signature(Signature other, ClassNameReplacer replacer) { | ||
| 40 | m_argumentTypes = Lists.newArrayList(other.m_argumentTypes); | ||
| 41 | for (int i=0; i<m_argumentTypes.size(); i++) { | ||
| 42 | m_argumentTypes.set(i, new Type(m_argumentTypes.get(i), replacer)); | ||
| 43 | } | ||
| 44 | m_returnType = new Type(other.m_returnType, replacer); | ||
| 45 | } | ||
| 46 | |||
| 47 | public List<Type> getArgumentTypes() { | ||
| 48 | return m_argumentTypes; | ||
| 49 | } | ||
| 50 | |||
| 51 | public Type getReturnType() { | ||
| 52 | return m_returnType; | ||
| 53 | } | ||
| 54 | |||
| 55 | @Override | ||
| 56 | public String toString() { | ||
| 57 | StringBuilder buf = new StringBuilder(); | ||
| 58 | buf.append("("); | ||
| 59 | for (Type type : m_argumentTypes) { | ||
| 60 | buf.append(type.toString()); | ||
| 61 | } | ||
| 62 | buf.append(")"); | ||
| 63 | buf.append(m_returnType.toString()); | ||
| 64 | return buf.toString(); | ||
| 65 | } | ||
| 66 | |||
| 67 | public Iterable<Type> types() { | ||
| 68 | List<Type> types = Lists.newArrayList(); | ||
| 69 | types.addAll(m_argumentTypes); | ||
| 70 | types.add(m_returnType); | ||
| 71 | return types; | ||
| 72 | } | ||
| 73 | |||
| 74 | public Iterable<ClassEntry> classes() { | ||
| 75 | List<ClassEntry> out = Lists.newArrayList(); | ||
| 76 | for (Type type : types()) { | ||
| 77 | if (type.isClass()) { | ||
| 78 | out.add(type.getClassEntry()); | ||
| 79 | } | ||
| 80 | } | ||
| 81 | return out; | ||
| 82 | } | ||
| 83 | |||
| 84 | @Override | ||
| 85 | public boolean equals(Object other) { | ||
| 86 | if (other instanceof Signature) { | ||
| 87 | return equals((Signature)other); | ||
| 88 | } | ||
| 89 | return false; | ||
| 90 | } | ||
| 91 | |||
| 92 | public boolean equals(Signature other) { | ||
| 93 | return m_argumentTypes.equals(other.m_argumentTypes) && m_returnType.equals(other.m_returnType); | ||
| 94 | } | ||
| 95 | |||
| 96 | @Override | ||
| 97 | public int hashCode() { | ||
| 98 | return Util.combineHashesOrdered(m_argumentTypes.hashCode(), m_returnType.hashCode()); | ||
| 99 | } | ||
| 100 | |||
| 101 | public boolean hasClass(ClassEntry classEntry) { | ||
| 102 | for (Type type : types()) { | ||
| 103 | if (type.hasClass() && type.getClassEntry().equals(classEntry)) { | ||
| 104 | return true; | ||
| 105 | } | ||
| 106 | } | ||
| 107 | return false; | ||
| 108 | } | ||
| 109 | } | ||
diff --git a/src/cuchaz/enigma/mapping/SignatureUpdater.java b/src/cuchaz/enigma/mapping/SignatureUpdater.java new file mode 100644 index 0000000..3477cd5 --- /dev/null +++ b/src/cuchaz/enigma/mapping/SignatureUpdater.java | |||
| @@ -0,0 +1,94 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..d1b14cd --- /dev/null +++ b/src/cuchaz/enigma/mapping/TranslationDirection.java | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..5eba18c --- /dev/null +++ b/src/cuchaz/enigma/mapping/Translator.java | |||
| @@ -0,0 +1,239 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma.mapping; | ||
| 12 | |||
| 13 | import java.util.Map; | ||
| 14 | |||
| 15 | import com.google.common.collect.Maps; | ||
| 16 | |||
| 17 | import cuchaz.enigma.analysis.TranslationIndex; | ||
| 18 | |||
| 19 | public class Translator { | ||
| 20 | |||
| 21 | private TranslationDirection m_direction; | ||
| 22 | private Map<String,ClassMapping> m_classes; | ||
| 23 | private TranslationIndex m_index; | ||
| 24 | |||
| 25 | public Translator() { | ||
| 26 | m_direction = null; | ||
| 27 | m_classes = Maps.newHashMap(); | ||
| 28 | } | ||
| 29 | |||
| 30 | public Translator(TranslationDirection direction, Map<String,ClassMapping> classes, TranslationIndex index) { | ||
| 31 | m_direction = direction; | ||
| 32 | m_classes = classes; | ||
| 33 | m_index = index; | ||
| 34 | } | ||
| 35 | |||
| 36 | @SuppressWarnings("unchecked") | ||
| 37 | public <T extends Entry> T translateEntry(T entry) { | ||
| 38 | if (entry instanceof ClassEntry) { | ||
| 39 | return (T)translateEntry((ClassEntry)entry); | ||
| 40 | } else if (entry instanceof FieldEntry) { | ||
| 41 | return (T)translateEntry((FieldEntry)entry); | ||
| 42 | } else if (entry instanceof MethodEntry) { | ||
| 43 | return (T)translateEntry((MethodEntry)entry); | ||
| 44 | } else if (entry instanceof ConstructorEntry) { | ||
| 45 | return (T)translateEntry((ConstructorEntry)entry); | ||
| 46 | } else if (entry instanceof ArgumentEntry) { | ||
| 47 | return (T)translateEntry((ArgumentEntry)entry); | ||
| 48 | } else { | ||
| 49 | throw new Error("Unknown entry type: " + entry.getClass().getName()); | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | public String translateClass(String className) { | ||
| 54 | return translate(new ClassEntry(className)); | ||
| 55 | } | ||
| 56 | |||
| 57 | public String translate(ClassEntry in) { | ||
| 58 | ClassMapping classMapping = m_classes.get(in.getOuterClassName()); | ||
| 59 | if (classMapping != null) { | ||
| 60 | if (in.isInnerClass()) { | ||
| 61 | // translate the inner class | ||
| 62 | String translatedInnerClassName = m_direction.choose( | ||
| 63 | classMapping.getDeobfInnerClassName(in.getInnerClassName()), | ||
| 64 | classMapping.getObfInnerClassName(in.getInnerClassName()) | ||
| 65 | ); | ||
| 66 | if (translatedInnerClassName != null) { | ||
| 67 | // try to translate the outer name | ||
| 68 | String translatedOuterClassName = m_direction.choose(classMapping.getDeobfName(), classMapping.getObfName()); | ||
| 69 | if (translatedOuterClassName != null) { | ||
| 70 | return translatedOuterClassName + "$" + translatedInnerClassName; | ||
| 71 | } else { | ||
| 72 | return in.getOuterClassName() + "$" + translatedInnerClassName; | ||
| 73 | } | ||
| 74 | } | ||
| 75 | } else { | ||
| 76 | // just return outer | ||
| 77 | return m_direction.choose(classMapping.getDeobfName(), classMapping.getObfName()); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | return null; | ||
| 81 | } | ||
| 82 | |||
| 83 | public ClassEntry translateEntry(ClassEntry in) { | ||
| 84 | |||
| 85 | // can we translate the inner class? | ||
| 86 | String name = translate(in); | ||
| 87 | if (name != null) { | ||
| 88 | return new ClassEntry(name); | ||
| 89 | } | ||
| 90 | |||
| 91 | if (in.isInnerClass()) { | ||
| 92 | |||
| 93 | // guess not. just translate the outer class name then | ||
| 94 | String outerClassName = translate(in.getOuterClassEntry()); | ||
| 95 | if (outerClassName != null) { | ||
| 96 | return new ClassEntry(outerClassName + "$" + in.getInnerClassName()); | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | return in; | ||
| 101 | } | ||
| 102 | |||
| 103 | public String translate(FieldEntry in) { | ||
| 104 | |||
| 105 | // resolve the class entry | ||
| 106 | ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in); | ||
| 107 | if (resolvedClassEntry != null) { | ||
| 108 | |||
| 109 | // look for the class | ||
| 110 | ClassMapping classMapping = findClassMapping(resolvedClassEntry); | ||
| 111 | if (classMapping != null) { | ||
| 112 | |||
| 113 | // look for the field | ||
| 114 | String translatedName = m_direction.choose( | ||
| 115 | classMapping.getDeobfFieldName(in.getName()), | ||
| 116 | classMapping.getObfFieldName(in.getName()) | ||
| 117 | ); | ||
| 118 | if (translatedName != null) { | ||
| 119 | return translatedName; | ||
| 120 | } | ||
| 121 | } | ||
| 122 | } | ||
| 123 | return null; | ||
| 124 | } | ||
| 125 | |||
| 126 | public FieldEntry translateEntry(FieldEntry in) { | ||
| 127 | String name = translate(in); | ||
| 128 | if (name == null) { | ||
| 129 | name = in.getName(); | ||
| 130 | } | ||
| 131 | return new FieldEntry(translateEntry(in.getClassEntry()), name); | ||
| 132 | } | ||
| 133 | |||
| 134 | public String translate(MethodEntry in) { | ||
| 135 | |||
| 136 | // resolve the class entry | ||
| 137 | ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in); | ||
| 138 | if (resolvedClassEntry != null) { | ||
| 139 | |||
| 140 | // look for class | ||
| 141 | ClassMapping classMapping = findClassMapping(resolvedClassEntry); | ||
| 142 | if (classMapping != null) { | ||
| 143 | |||
| 144 | // look for the method | ||
| 145 | MethodMapping methodMapping = m_direction.choose( | ||
| 146 | classMapping.getMethodByObf(in.getName(), in.getSignature()), | ||
| 147 | classMapping.getMethodByDeobf(in.getName(), translateSignature(in.getSignature())) | ||
| 148 | ); | ||
| 149 | if (methodMapping != null) { | ||
| 150 | return m_direction.choose(methodMapping.getDeobfName(), methodMapping.getObfName()); | ||
| 151 | } | ||
| 152 | } | ||
| 153 | } | ||
| 154 | return null; | ||
| 155 | } | ||
| 156 | |||
| 157 | public MethodEntry translateEntry(MethodEntry in) { | ||
| 158 | String name = translate(in); | ||
| 159 | if (name == null) { | ||
| 160 | name = in.getName(); | ||
| 161 | } | ||
| 162 | return new MethodEntry(translateEntry(in.getClassEntry()), name, translateSignature(in.getSignature())); | ||
| 163 | } | ||
| 164 | |||
| 165 | public ConstructorEntry translateEntry(ConstructorEntry in) { | ||
| 166 | if (in.isStatic()) { | ||
| 167 | return new ConstructorEntry(translateEntry(in.getClassEntry())); | ||
| 168 | } else { | ||
| 169 | return new ConstructorEntry(translateEntry(in.getClassEntry()), translateSignature(in.getSignature())); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | public BehaviorEntry translateEntry(BehaviorEntry in) { | ||
| 174 | if (in instanceof MethodEntry) { | ||
| 175 | return translateEntry((MethodEntry)in); | ||
| 176 | } else if (in instanceof ConstructorEntry) { | ||
| 177 | return translateEntry((ConstructorEntry)in); | ||
| 178 | } | ||
| 179 | throw new Error("Wrong entry type!"); | ||
| 180 | } | ||
| 181 | |||
| 182 | public String translate(ArgumentEntry in) { | ||
| 183 | |||
| 184 | // look for the class | ||
| 185 | ClassMapping classMapping = findClassMapping(in.getClassEntry()); | ||
| 186 | if (classMapping != null) { | ||
| 187 | |||
| 188 | // look for the method | ||
| 189 | MethodMapping methodMapping = m_direction.choose( | ||
| 190 | classMapping.getMethodByObf(in.getMethodName(), in.getMethodSignature()), | ||
| 191 | classMapping.getMethodByDeobf(in.getMethodName(), translateSignature(in.getMethodSignature())) | ||
| 192 | ); | ||
| 193 | if (methodMapping != null) { | ||
| 194 | return m_direction.choose( | ||
| 195 | methodMapping.getDeobfArgumentName(in.getIndex()), | ||
| 196 | methodMapping.getObfArgumentName(in.getIndex()) | ||
| 197 | ); | ||
| 198 | } | ||
| 199 | } | ||
| 200 | return null; | ||
| 201 | } | ||
| 202 | |||
| 203 | public ArgumentEntry translateEntry(ArgumentEntry in) { | ||
| 204 | String name = translate(in); | ||
| 205 | if (name == null) { | ||
| 206 | name = in.getName(); | ||
| 207 | } | ||
| 208 | return new ArgumentEntry(translateEntry(in.getBehaviorEntry()), in.getIndex(), name); | ||
| 209 | } | ||
| 210 | |||
| 211 | public Type translateType(Type type) { | ||
| 212 | return new Type(type, new ClassNameReplacer() { | ||
| 213 | @Override | ||
| 214 | public String replace(String className) { | ||
| 215 | return translateClass(className); | ||
| 216 | } | ||
| 217 | }); | ||
| 218 | } | ||
| 219 | |||
| 220 | public Signature translateSignature(Signature signature) { | ||
| 221 | return new Signature(signature, new ClassNameReplacer() { | ||
| 222 | @Override | ||
| 223 | public String replace(String className) { | ||
| 224 | return translateClass(className); | ||
| 225 | } | ||
| 226 | }); | ||
| 227 | } | ||
| 228 | |||
| 229 | private ClassMapping findClassMapping(ClassEntry classEntry) { | ||
| 230 | ClassMapping classMapping = m_classes.get(classEntry.getOuterClassName()); | ||
| 231 | if (classMapping != null && classEntry.isInnerClass()) { | ||
| 232 | classMapping = m_direction.choose( | ||
| 233 | classMapping.getInnerClassByObf(classEntry.getInnerClassName()), | ||
| 234 | classMapping.getInnerClassByDeobfThenObf(classEntry.getInnerClassName()) | ||
| 235 | ); | ||
| 236 | } | ||
| 237 | return classMapping; | ||
| 238 | } | ||
| 239 | } | ||
diff --git a/src/cuchaz/enigma/mapping/Type.java b/src/cuchaz/enigma/mapping/Type.java new file mode 100644 index 0000000..9f5d52f --- /dev/null +++ b/src/cuchaz/enigma/mapping/Type.java | |||
| @@ -0,0 +1,218 @@ | |||
| 1 | package cuchaz.enigma.mapping; | ||
| 2 | |||
| 3 | import java.util.Map; | ||
| 4 | |||
| 5 | import com.google.common.collect.Maps; | ||
| 6 | |||
| 7 | public class Type { | ||
| 8 | |||
| 9 | public enum Primitive { | ||
| 10 | Byte('B'), | ||
| 11 | Character('C'), | ||
| 12 | Short('S'), | ||
| 13 | Integer('I'), | ||
| 14 | Long('J'), | ||
| 15 | Float('F'), | ||
| 16 | Double('D'), | ||
| 17 | Boolean('Z'); | ||
| 18 | |||
| 19 | private static final Map<Character,Primitive> m_lookup; | ||
| 20 | |||
| 21 | static { | ||
| 22 | m_lookup = Maps.newTreeMap(); | ||
| 23 | for (Primitive val : values()) { | ||
| 24 | m_lookup.put(val.getCode(), val); | ||
| 25 | } | ||
| 26 | } | ||
| 27 | |||
| 28 | public static Primitive get(char code) { | ||
| 29 | return m_lookup.get(code); | ||
| 30 | } | ||
| 31 | |||
| 32 | private char m_code; | ||
| 33 | |||
| 34 | private Primitive(char code) { | ||
| 35 | m_code = code; | ||
| 36 | } | ||
| 37 | |||
| 38 | public char getCode() { | ||
| 39 | return m_code; | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | public static String parseFirst(String in) { | ||
| 44 | |||
| 45 | if (in == null || in.length() <= 0) { | ||
| 46 | throw new IllegalArgumentException("No type to parse, input is empty!"); | ||
| 47 | } | ||
| 48 | |||
| 49 | // read one type from the input | ||
| 50 | |||
| 51 | char c = in.charAt(0); | ||
| 52 | |||
| 53 | // first check for void | ||
| 54 | if (c == 'V') { | ||
| 55 | return "V"; | ||
| 56 | } | ||
| 57 | |||
| 58 | // then check for primitives | ||
| 59 | Primitive primitive = Primitive.get(c); | ||
| 60 | if (primitive != null) { | ||
| 61 | return in.substring(0, 1); | ||
| 62 | } | ||
| 63 | |||
| 64 | // then check for classes | ||
| 65 | if (c == 'L') { | ||
| 66 | return readClass(in); | ||
| 67 | } | ||
| 68 | |||
| 69 | // then check for arrays | ||
| 70 | int dim = countArrayDimension(in); | ||
| 71 | if (dim > 0) { | ||
| 72 | String arrayType = Type.parseFirst(in.substring(dim)); | ||
| 73 | return in.substring(0, dim + arrayType.length()); | ||
| 74 | } | ||
| 75 | |||
| 76 | throw new IllegalArgumentException("don't know how to parse: " + in); | ||
| 77 | } | ||
| 78 | |||
| 79 | private String m_name; | ||
| 80 | |||
| 81 | public Type(String name) { | ||
| 82 | m_name = name; | ||
| 83 | } | ||
| 84 | |||
| 85 | public Type(ClassEntry classEntry) { | ||
| 86 | m_name = "L" + classEntry.getClassName() + ";"; | ||
| 87 | } | ||
| 88 | |||
| 89 | public Type(Type type, ClassNameReplacer replacer) { | ||
| 90 | m_name = type.m_name; | ||
| 91 | if (type.isClass()) { | ||
| 92 | String replacedName = replacer.replace(type.getClassEntry().getClassName()); | ||
| 93 | if (replacedName != null) { | ||
| 94 | m_name = "L" + replacedName + ";"; | ||
| 95 | } | ||
| 96 | } else if (type.isArray() && type.hasClass()) { | ||
| 97 | String replacedName = replacer.replace(type.getClassEntry().getClassName()); | ||
| 98 | if (replacedName != null) { | ||
| 99 | m_name = Type.getArrayPrefix(type.getArrayDimension()) + "L" + replacedName + ";"; | ||
| 100 | } | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | @Override | ||
| 105 | public String toString() { | ||
| 106 | return m_name; | ||
| 107 | } | ||
| 108 | |||
| 109 | public boolean isVoid() { | ||
| 110 | return m_name.length() == 1 && m_name.charAt(0) == 'V'; | ||
| 111 | } | ||
| 112 | |||
| 113 | public boolean isPrimitive() { | ||
| 114 | return m_name.length() == 1 && Primitive.get(m_name.charAt(0)) != null; | ||
| 115 | } | ||
| 116 | |||
| 117 | public Primitive getPrimitive() { | ||
| 118 | if (!isPrimitive()) { | ||
| 119 | throw new IllegalStateException("not a primitive"); | ||
| 120 | } | ||
| 121 | return Primitive.get(m_name.charAt(0)); | ||
| 122 | } | ||
| 123 | |||
| 124 | public boolean isClass() { | ||
| 125 | return m_name.charAt(0) == 'L' && m_name.charAt(m_name.length() - 1) == ';'; | ||
| 126 | } | ||
| 127 | |||
| 128 | public ClassEntry getClassEntry() { | ||
| 129 | if (isClass()) { | ||
| 130 | String name = m_name.substring(1, m_name.length() - 1); | ||
| 131 | |||
| 132 | int pos = name.indexOf('<'); | ||
| 133 | if (pos >= 0) { | ||
| 134 | // remove the parameters from the class name | ||
| 135 | name = name.substring(0, pos); | ||
| 136 | } | ||
| 137 | |||
| 138 | return new ClassEntry(name); | ||
| 139 | |||
| 140 | } else if (isArray() && getArrayType().isClass()) { | ||
| 141 | return getArrayType().getClassEntry(); | ||
| 142 | } else { | ||
| 143 | throw new IllegalStateException("type doesn't have a class"); | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | public boolean isArray() { | ||
| 148 | return m_name.charAt(0) == '['; | ||
| 149 | } | ||
| 150 | |||
| 151 | public int getArrayDimension() { | ||
| 152 | if (!isArray()) { | ||
| 153 | throw new IllegalStateException("not an array"); | ||
| 154 | } | ||
| 155 | return countArrayDimension(m_name); | ||
| 156 | } | ||
| 157 | |||
| 158 | public Type getArrayType() { | ||
| 159 | if (!isArray()) { | ||
| 160 | throw new IllegalStateException("not an array"); | ||
| 161 | } | ||
| 162 | return new Type(m_name.substring(getArrayDimension(), m_name.length())); | ||
| 163 | } | ||
| 164 | |||
| 165 | private static String getArrayPrefix(int dimension) { | ||
| 166 | StringBuilder buf = new StringBuilder(); | ||
| 167 | for (int i=0; i<dimension; i++) { | ||
| 168 | buf.append("["); | ||
| 169 | } | ||
| 170 | return buf.toString(); | ||
| 171 | } | ||
| 172 | |||
| 173 | public boolean hasClass() { | ||
| 174 | return isClass() || (isArray() && getArrayType().hasClass()); | ||
| 175 | } | ||
| 176 | |||
| 177 | @Override | ||
| 178 | public boolean equals(Object other) { | ||
| 179 | if (other instanceof Type) { | ||
| 180 | return equals((Type)other); | ||
| 181 | } | ||
| 182 | return false; | ||
| 183 | } | ||
| 184 | |||
| 185 | public boolean equals(Type other) { | ||
| 186 | return m_name.equals(other.m_name); | ||
| 187 | } | ||
| 188 | |||
| 189 | public int hashCode() { | ||
| 190 | return m_name.hashCode(); | ||
| 191 | } | ||
| 192 | |||
| 193 | private static int countArrayDimension(String in) { | ||
| 194 | int i=0; | ||
| 195 | for(; i < in.length() && in.charAt(i) == '['; i++); | ||
| 196 | return i; | ||
| 197 | } | ||
| 198 | |||
| 199 | private static String readClass(String in) { | ||
| 200 | // read all the characters in the buffer until we hit a ';' | ||
| 201 | // remember to treat parameters correctly | ||
| 202 | StringBuilder buf = new StringBuilder(); | ||
| 203 | int depth = 0; | ||
| 204 | for (int i=0; i<in.length(); i++) { | ||
| 205 | char c = in.charAt(i); | ||
| 206 | buf.append(c); | ||
| 207 | |||
| 208 | if (c == '<') { | ||
| 209 | depth++; | ||
| 210 | } else if (c == '>') { | ||
| 211 | depth--; | ||
| 212 | } else if (depth == 0 && c == ';') { | ||
| 213 | return buf.toString(); | ||
| 214 | } | ||
| 215 | } | ||
| 216 | return null; | ||
| 217 | } | ||
| 218 | } | ||
diff --git a/test/cuchaz/enigma/EntryFactory.java b/test/cuchaz/enigma/EntryFactory.java new file mode 100644 index 0000000..fa90779 --- /dev/null +++ b/test/cuchaz/enigma/EntryFactory.java | |||
| @@ -0,0 +1,67 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin.\ | ||
| 3 | * | ||
| 4 | * All rights reserved. This program and the accompanying materials | ||
| 5 | * are made available under the terms of the GNU Public License v3.0 | ||
| 6 | * which accompanies this distribution, and is available at | ||
| 7 | * http://www.gnu.org/licenses/gpl.html | ||
| 8 | * | ||
| 9 | * Contributors: | ||
| 10 | * Jeff Martin - initial API and implementation | ||
| 11 | ******************************************************************************/ | ||
| 12 | package cuchaz.enigma; | ||
| 13 | |||
| 14 | import cuchaz.enigma.analysis.EntryReference; | ||
| 15 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 16 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 17 | import cuchaz.enigma.mapping.ConstructorEntry; | ||
| 18 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 19 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 20 | import cuchaz.enigma.mapping.Signature; | ||
| 21 | |||
| 22 | public class EntryFactory { | ||
| 23 | |||
| 24 | public static ClassEntry newClass(String name) { | ||
| 25 | return new ClassEntry(name); | ||
| 26 | } | ||
| 27 | |||
| 28 | public static FieldEntry newField(String className, String fieldName) { | ||
| 29 | return newField(newClass(className), fieldName); | ||
| 30 | } | ||
| 31 | |||
| 32 | public static FieldEntry newField(ClassEntry classEntry, String fieldName) { | ||
| 33 | return new FieldEntry(classEntry, fieldName); | ||
| 34 | } | ||
| 35 | |||
| 36 | public static MethodEntry newMethod(String className, String methodName, String methodSignature) { | ||
| 37 | return newMethod(newClass(className), methodName, methodSignature); | ||
| 38 | } | ||
| 39 | |||
| 40 | public static MethodEntry newMethod(ClassEntry classEntry, String methodName, String methodSignature) { | ||
| 41 | return new MethodEntry(classEntry, methodName, new Signature(methodSignature)); | ||
| 42 | } | ||
| 43 | |||
| 44 | public static ConstructorEntry newConstructor(String className, String signature) { | ||
| 45 | return newConstructor(newClass(className), signature); | ||
| 46 | } | ||
| 47 | |||
| 48 | public static ConstructorEntry newConstructor(ClassEntry classEntry, String signature) { | ||
| 49 | return new ConstructorEntry(classEntry, new Signature(signature)); | ||
| 50 | } | ||
| 51 | |||
| 52 | public static EntryReference<FieldEntry,BehaviorEntry> newFieldReferenceByMethod(FieldEntry fieldEntry, String callerClassName, String callerName, String callerSignature) { | ||
| 53 | return new EntryReference<FieldEntry,BehaviorEntry>(fieldEntry, "", newMethod(callerClassName, callerName, callerSignature)); | ||
| 54 | } | ||
| 55 | |||
| 56 | public static EntryReference<FieldEntry,BehaviorEntry> newFieldReferenceByConstructor(FieldEntry fieldEntry, String callerClassName, String callerSignature) { | ||
| 57 | return new EntryReference<FieldEntry,BehaviorEntry>(fieldEntry, "", newConstructor(callerClassName, callerSignature)); | ||
| 58 | } | ||
| 59 | |||
| 60 | public static EntryReference<BehaviorEntry,BehaviorEntry> newBehaviorReferenceByMethod(BehaviorEntry behaviorEntry, String callerClassName, String callerName, String callerSignature) { | ||
| 61 | return new EntryReference<BehaviorEntry,BehaviorEntry>(behaviorEntry, "", newMethod(callerClassName, callerName, callerSignature)); | ||
| 62 | } | ||
| 63 | |||
| 64 | public static EntryReference<BehaviorEntry,BehaviorEntry> newBehaviorReferenceByConstructor(BehaviorEntry behaviorEntry, String callerClassName, String callerSignature) { | ||
| 65 | return new EntryReference<BehaviorEntry,BehaviorEntry>(behaviorEntry, "", newConstructor(callerClassName, callerSignature)); | ||
| 66 | } | ||
| 67 | } | ||
diff --git a/test/cuchaz/enigma/TestDeobfuscator.java b/test/cuchaz/enigma/TestDeobfuscator.java new file mode 100644 index 0000000..26d492d --- /dev/null +++ b/test/cuchaz/enigma/TestDeobfuscator.java | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin.\ | ||
| 3 | * | ||
| 4 | * All rights reserved. This program and the accompanying materials | ||
| 5 | * are made available under the terms of the GNU Public License v3.0 | ||
| 6 | * which accompanies this distribution, and is available at | ||
| 7 | * http://www.gnu.org/licenses/gpl.html | ||
| 8 | * | ||
| 9 | * Contributors: | ||
| 10 | * Jeff Martin - initial API and implementation | ||
| 11 | ******************************************************************************/ | ||
| 12 | package cuchaz.enigma; | ||
| 13 | |||
| 14 | import static org.junit.Assert.*; | ||
| 15 | |||
| 16 | import java.io.IOException; | ||
| 17 | import java.util.List; | ||
| 18 | import java.util.jar.JarFile; | ||
| 19 | |||
| 20 | import org.junit.Test; | ||
| 21 | |||
| 22 | import com.google.common.collect.Lists; | ||
| 23 | |||
| 24 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 25 | |||
| 26 | public class TestDeobfuscator { | ||
| 27 | |||
| 28 | private Deobfuscator getDeobfuscator() | ||
| 29 | throws IOException { | ||
| 30 | return new Deobfuscator(new JarFile("build/testLoneClass.obf.jar")); | ||
| 31 | } | ||
| 32 | |||
| 33 | @Test | ||
| 34 | public void loadJar() | ||
| 35 | throws Exception { | ||
| 36 | getDeobfuscator(); | ||
| 37 | } | ||
| 38 | |||
| 39 | @Test | ||
| 40 | public void getClasses() | ||
| 41 | throws Exception { | ||
| 42 | Deobfuscator deobfuscator = getDeobfuscator(); | ||
| 43 | List<ClassEntry> obfClasses = Lists.newArrayList(); | ||
| 44 | List<ClassEntry> deobfClasses = Lists.newArrayList(); | ||
| 45 | deobfuscator.getSeparatedClasses(obfClasses, deobfClasses); | ||
| 46 | assertEquals(1, obfClasses.size()); | ||
| 47 | assertEquals("none/a", obfClasses.get(0).getName()); | ||
| 48 | assertEquals(1, deobfClasses.size()); | ||
| 49 | assertEquals("cuchaz/enigma/inputs/Keep", deobfClasses.get(0).getName()); | ||
| 50 | } | ||
| 51 | |||
| 52 | @Test | ||
| 53 | public void decompileClass() | ||
| 54 | throws Exception { | ||
| 55 | Deobfuscator deobfuscator = getDeobfuscator(); | ||
| 56 | deobfuscator.getSource(deobfuscator.getSourceTree("none/a")); | ||
| 57 | } | ||
| 58 | } | ||
diff --git a/test/cuchaz/enigma/TestInnerClasses.java b/test/cuchaz/enigma/TestInnerClasses.java new file mode 100644 index 0000000..2e16a33 --- /dev/null +++ b/test/cuchaz/enigma/TestInnerClasses.java | |||
| @@ -0,0 +1,90 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * | ||
| 4 | * All rights reserved. This program and the accompanying materials | ||
| 5 | * are made available under the terms of the GNU Public License v3.0 | ||
| 6 | * which accompanies this distribution, and is available at | ||
| 7 | * http://www.gnu.org/licenses/gpl.html | ||
| 8 | * | ||
| 9 | * Contributors: | ||
| 10 | * Jeff Martin - initial API and implementation | ||
| 11 | ******************************************************************************/ | ||
| 12 | package cuchaz.enigma; | ||
| 13 | |||
| 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.analysis.JarIndex; | ||
| 22 | |||
| 23 | public class TestInnerClasses { | ||
| 24 | |||
| 25 | private JarIndex m_index; | ||
| 26 | private Deobfuscator m_deobfuscator; | ||
| 27 | |||
| 28 | private static final String AnonymousOuter = "none/a"; | ||
| 29 | private static final String AnonymousInner = "b"; | ||
| 30 | private static final String SimpleOuter = "none/g"; | ||
| 31 | private static final String SimpleInner = "h"; | ||
| 32 | private static final String ConstructorArgsOuter = "none/e"; | ||
| 33 | private static final String ConstructorArgsInner = "f"; | ||
| 34 | private static final String AnonymousWithScopeArgsOuter = "none/c"; | ||
| 35 | private static final String AnonymousWithScopeArgsInner = "d"; | ||
| 36 | private static final String AnonymousWithOuterAccessOuter = "none/i"; | ||
| 37 | private static final String AnonymousWithOuterAccessInner = "j"; | ||
| 38 | |||
| 39 | public TestInnerClasses() | ||
| 40 | throws Exception { | ||
| 41 | m_index = new JarIndex(); | ||
| 42 | JarFile jar = new JarFile("build/testInnerClasses.obf.jar"); | ||
| 43 | m_index.indexJar(jar, true); | ||
| 44 | m_deobfuscator = new Deobfuscator(jar); | ||
| 45 | } | ||
| 46 | |||
| 47 | @Test | ||
| 48 | public void simple() { | ||
| 49 | assertThat(m_index.getOuterClass(SimpleInner), is(SimpleOuter)); | ||
| 50 | assertThat(m_index.getInnerClasses(SimpleOuter), containsInAnyOrder(SimpleInner)); | ||
| 51 | assertThat(m_index.isAnonymousClass(SimpleInner), is(false)); | ||
| 52 | decompile(SimpleOuter); | ||
| 53 | } | ||
| 54 | |||
| 55 | @Test | ||
| 56 | public void anonymous() { | ||
| 57 | assertThat(m_index.getOuterClass(AnonymousInner), is(AnonymousOuter)); | ||
| 58 | assertThat(m_index.getInnerClasses(AnonymousOuter), containsInAnyOrder(AnonymousInner)); | ||
| 59 | assertThat(m_index.isAnonymousClass(AnonymousInner), is(true)); | ||
| 60 | decompile(AnonymousOuter); | ||
| 61 | } | ||
| 62 | |||
| 63 | @Test | ||
| 64 | public void constructorArgs() { | ||
| 65 | assertThat(m_index.getOuterClass(ConstructorArgsInner), is(ConstructorArgsOuter)); | ||
| 66 | assertThat(m_index.getInnerClasses(ConstructorArgsOuter), containsInAnyOrder(ConstructorArgsInner)); | ||
| 67 | assertThat(m_index.isAnonymousClass(ConstructorArgsInner), is(false)); | ||
| 68 | decompile(ConstructorArgsOuter); | ||
| 69 | } | ||
| 70 | |||
| 71 | @Test | ||
| 72 | public void anonymousWithScopeArgs() { | ||
| 73 | assertThat(m_index.getOuterClass(AnonymousWithScopeArgsInner), is(AnonymousWithScopeArgsOuter)); | ||
| 74 | assertThat(m_index.getInnerClasses(AnonymousWithScopeArgsOuter), containsInAnyOrder(AnonymousWithScopeArgsInner)); | ||
| 75 | assertThat(m_index.isAnonymousClass(AnonymousWithScopeArgsInner), is(true)); | ||
| 76 | decompile(AnonymousWithScopeArgsOuter); | ||
| 77 | } | ||
| 78 | |||
| 79 | @Test | ||
| 80 | public void anonymousWithOuterAccess() { | ||
| 81 | assertThat(m_index.getOuterClass(AnonymousWithOuterAccessInner), is(AnonymousWithOuterAccessOuter)); | ||
| 82 | assertThat(m_index.getInnerClasses(AnonymousWithOuterAccessOuter), containsInAnyOrder(AnonymousWithOuterAccessInner)); | ||
| 83 | assertThat(m_index.isAnonymousClass(AnonymousWithOuterAccessInner), is(true)); | ||
| 84 | decompile(AnonymousWithOuterAccessOuter); | ||
| 85 | } | ||
| 86 | |||
| 87 | private void decompile(String name) { | ||
| 88 | m_deobfuscator.getSourceTree(name); | ||
| 89 | } | ||
| 90 | } | ||
diff --git a/test/cuchaz/enigma/TestJarIndexConstructorReferences.java b/test/cuchaz/enigma/TestJarIndexConstructorReferences.java new file mode 100644 index 0000000..22812fe --- /dev/null +++ b/test/cuchaz/enigma/TestJarIndexConstructorReferences.java | |||
| @@ -0,0 +1,124 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import static cuchaz.enigma.EntryFactory.*; | ||
| 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/testConstructors.obf.jar"); | ||
| 41 | m_index = new JarIndex(); | ||
| 42 | m_index.indexJar(new JarFile(jarFile), false); | ||
| 43 | } | ||
| 44 | |||
| 45 | @Test | ||
| 46 | public void obfEntries() { | ||
| 47 | assertThat(m_index.getObfClassEntries(), containsInAnyOrder(newClass("cuchaz/enigma/inputs/Keep"), m_baseClass, m_subClass, m_subsubClass, m_defaultClass, m_callerClass)); | ||
| 48 | } | ||
| 49 | |||
| 50 | @Test | ||
| 51 | @SuppressWarnings("unchecked") | ||
| 52 | public void baseDefault() { | ||
| 53 | BehaviorEntry source = newConstructor(m_baseClass, "()V"); | ||
| 54 | Collection<EntryReference<BehaviorEntry,BehaviorEntry>> references = m_index.getBehaviorReferences(source); | ||
| 55 | assertThat(references, containsInAnyOrder( | ||
| 56 | newBehaviorReferenceByMethod(source, m_callerClass.getName(), "a", "()V"), | ||
| 57 | newBehaviorReferenceByConstructor(source, m_subClass.getName(), "()V"), | ||
| 58 | newBehaviorReferenceByConstructor(source, m_subClass.getName(), "(III)V") | ||
| 59 | )); | ||
| 60 | } | ||
| 61 | |||
| 62 | @Test | ||
| 63 | @SuppressWarnings("unchecked") | ||
| 64 | public void baseInt() { | ||
| 65 | BehaviorEntry source = newConstructor(m_baseClass, "(I)V"); | ||
| 66 | assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder( | ||
| 67 | newBehaviorReferenceByMethod(source, m_callerClass.getName(), "b", "()V") | ||
| 68 | )); | ||
| 69 | } | ||
| 70 | |||
| 71 | @Test | ||
| 72 | @SuppressWarnings("unchecked") | ||
| 73 | public void subDefault() { | ||
| 74 | BehaviorEntry source = newConstructor(m_subClass, "()V"); | ||
| 75 | assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder( | ||
| 76 | newBehaviorReferenceByMethod(source, m_callerClass.getName(), "c", "()V"), | ||
| 77 | newBehaviorReferenceByConstructor(source, m_subClass.getName(), "(I)V") | ||
| 78 | )); | ||
| 79 | } | ||
| 80 | |||
| 81 | @Test | ||
| 82 | @SuppressWarnings("unchecked") | ||
| 83 | public void subInt() { | ||
| 84 | BehaviorEntry source = newConstructor(m_subClass, "(I)V"); | ||
| 85 | assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder( | ||
| 86 | newBehaviorReferenceByMethod(source, m_callerClass.getName(), "d", "()V"), | ||
| 87 | newBehaviorReferenceByConstructor(source, m_subClass.getName(), "(II)V"), | ||
| 88 | newBehaviorReferenceByConstructor(source, m_subsubClass.getName(), "(I)V") | ||
| 89 | )); | ||
| 90 | } | ||
| 91 | |||
| 92 | @Test | ||
| 93 | @SuppressWarnings("unchecked") | ||
| 94 | public void subIntInt() { | ||
| 95 | BehaviorEntry source = newConstructor(m_subClass, "(II)V"); | ||
| 96 | assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder( | ||
| 97 | newBehaviorReferenceByMethod(source, m_callerClass.getName(), "e", "()V") | ||
| 98 | )); | ||
| 99 | } | ||
| 100 | |||
| 101 | @Test | ||
| 102 | public void subIntIntInt() { | ||
| 103 | BehaviorEntry source = newConstructor(m_subClass, "(III)V"); | ||
| 104 | assertThat(m_index.getBehaviorReferences(source), is(empty())); | ||
| 105 | } | ||
| 106 | |||
| 107 | @Test | ||
| 108 | @SuppressWarnings("unchecked") | ||
| 109 | public void subsubInt() { | ||
| 110 | BehaviorEntry source = newConstructor(m_subsubClass, "(I)V"); | ||
| 111 | assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder( | ||
| 112 | newBehaviorReferenceByMethod(source, m_callerClass.getName(), "f", "()V") | ||
| 113 | )); | ||
| 114 | } | ||
| 115 | |||
| 116 | @Test | ||
| 117 | @SuppressWarnings("unchecked") | ||
| 118 | public void defaultConstructable() { | ||
| 119 | BehaviorEntry source = newConstructor(m_defaultClass, "()V"); | ||
| 120 | assertThat(m_index.getBehaviorReferences(source), containsInAnyOrder( | ||
| 121 | newBehaviorReferenceByMethod(source, m_callerClass.getName(), "g", "()V") | ||
| 122 | )); | ||
| 123 | } | ||
| 124 | } | ||
diff --git a/test/cuchaz/enigma/TestJarIndexInheritanceTree.java b/test/cuchaz/enigma/TestJarIndexInheritanceTree.java new file mode 100644 index 0000000..1d6e5a5 --- /dev/null +++ b/test/cuchaz/enigma/TestJarIndexInheritanceTree.java | |||
| @@ -0,0 +1,228 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import static cuchaz.enigma.EntryFactory.*; | ||
| 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 = new FieldEntry(m_baseClass, "a"); | ||
| 41 | private FieldEntry m_numThingsField = new FieldEntry(m_subClassB, "a"); | ||
| 42 | |||
| 43 | public TestJarIndexInheritanceTree() | ||
| 44 | throws Exception { | ||
| 45 | m_index = new JarIndex(); | ||
| 46 | m_index.indexJar(new JarFile("build/testInheritanceTree.obf.jar"), false); | ||
| 47 | } | ||
| 48 | |||
| 49 | @Test | ||
| 50 | public void obfEntries() { | ||
| 51 | assertThat(m_index.getObfClassEntries(), containsInAnyOrder( | ||
| 52 | newClass("cuchaz/enigma/inputs/Keep"), | ||
| 53 | m_baseClass, | ||
| 54 | m_subClassA, | ||
| 55 | m_subClassAA, | ||
| 56 | m_subClassB | ||
| 57 | )); | ||
| 58 | } | ||
| 59 | |||
| 60 | @Test | ||
| 61 | public void translationIndex() { | ||
| 62 | |||
| 63 | TranslationIndex index = m_index.getTranslationIndex(); | ||
| 64 | |||
| 65 | // base class | ||
| 66 | assertThat(index.getSuperclass(m_baseClass), is(nullValue())); | ||
| 67 | assertThat(index.getAncestry(m_baseClass), is(empty())); | ||
| 68 | assertThat(index.getSubclass(m_baseClass), containsInAnyOrder( | ||
| 69 | m_subClassA, | ||
| 70 | m_subClassB | ||
| 71 | )); | ||
| 72 | |||
| 73 | // subclass a | ||
| 74 | assertThat(index.getSuperclass(m_subClassA), is(m_baseClass)); | ||
| 75 | assertThat(index.getAncestry(m_subClassA), contains(m_baseClass)); | ||
| 76 | assertThat(index.getSubclass(m_subClassA), contains(m_subClassAA)); | ||
| 77 | |||
| 78 | // subclass aa | ||
| 79 | assertThat(index.getSuperclass(m_subClassAA), is(m_subClassA)); | ||
| 80 | assertThat(index.getAncestry(m_subClassAA), contains(m_subClassA, m_baseClass)); | ||
| 81 | assertThat(index.getSubclass(m_subClassAA), is(empty())); | ||
| 82 | |||
| 83 | // subclass b | ||
| 84 | assertThat(index.getSuperclass(m_subClassB), is(m_baseClass)); | ||
| 85 | assertThat(index.getAncestry(m_subClassB), contains(m_baseClass)); | ||
| 86 | assertThat(index.getSubclass(m_subClassB), is(empty())); | ||
| 87 | } | ||
| 88 | |||
| 89 | @Test | ||
| 90 | public void access() { | ||
| 91 | assertThat(m_index.getAccess(m_nameField), is(Access.Private)); | ||
| 92 | assertThat(m_index.getAccess(m_numThingsField), is(Access.Private)); | ||
| 93 | } | ||
| 94 | |||
| 95 | @Test | ||
| 96 | public void relatedMethodImplementations() { | ||
| 97 | |||
| 98 | Set<MethodEntry> entries; | ||
| 99 | |||
| 100 | // getName() | ||
| 101 | entries = m_index.getRelatedMethodImplementations(newMethod(m_baseClass, "a", "()Ljava/lang/String;")); | ||
| 102 | assertThat(entries, containsInAnyOrder( | ||
| 103 | newMethod(m_baseClass, "a", "()Ljava/lang/String;"), | ||
| 104 | newMethod(m_subClassAA, "a", "()Ljava/lang/String;") | ||
| 105 | )); | ||
| 106 | entries = m_index.getRelatedMethodImplementations(newMethod(m_subClassAA, "a", "()Ljava/lang/String;")); | ||
| 107 | assertThat(entries, containsInAnyOrder( | ||
| 108 | newMethod(m_baseClass, "a", "()Ljava/lang/String;"), | ||
| 109 | newMethod(m_subClassAA, "a", "()Ljava/lang/String;") | ||
| 110 | )); | ||
| 111 | |||
| 112 | // doBaseThings() | ||
| 113 | entries = m_index.getRelatedMethodImplementations(newMethod(m_baseClass, "a", "()V")); | ||
| 114 | assertThat(entries, containsInAnyOrder( | ||
| 115 | newMethod(m_baseClass, "a", "()V"), | ||
| 116 | newMethod(m_subClassAA, "a", "()V"), | ||
| 117 | newMethod(m_subClassB, "a", "()V") | ||
| 118 | )); | ||
| 119 | entries = m_index.getRelatedMethodImplementations(newMethod(m_subClassAA, "a", "()V")); | ||
| 120 | assertThat(entries, containsInAnyOrder( | ||
| 121 | newMethod(m_baseClass, "a", "()V"), | ||
| 122 | newMethod(m_subClassAA, "a", "()V"), | ||
| 123 | newMethod(m_subClassB, "a", "()V") | ||
| 124 | )); | ||
| 125 | entries = m_index.getRelatedMethodImplementations(newMethod(m_subClassB, "a", "()V")); | ||
| 126 | assertThat(entries, containsInAnyOrder( | ||
| 127 | newMethod(m_baseClass, "a", "()V"), | ||
| 128 | newMethod(m_subClassAA, "a", "()V"), | ||
| 129 | newMethod(m_subClassB, "a", "()V") | ||
| 130 | )); | ||
| 131 | |||
| 132 | // doBThings | ||
| 133 | entries = m_index.getRelatedMethodImplementations(newMethod(m_subClassB, "b", "()V")); | ||
| 134 | assertThat(entries, containsInAnyOrder(newMethod(m_subClassB, "b", "()V"))); | ||
| 135 | } | ||
| 136 | |||
| 137 | @Test | ||
| 138 | @SuppressWarnings("unchecked") | ||
| 139 | public void fieldReferences() { | ||
| 140 | Collection<EntryReference<FieldEntry,BehaviorEntry>> references; | ||
| 141 | |||
| 142 | // name | ||
| 143 | references = m_index.getFieldReferences(m_nameField); | ||
| 144 | assertThat(references, containsInAnyOrder( | ||
| 145 | newFieldReferenceByConstructor(m_nameField, m_baseClass.getName(), "(Ljava/lang/String;)V"), | ||
| 146 | newFieldReferenceByMethod(m_nameField, m_baseClass.getName(), "a", "()Ljava/lang/String;") | ||
| 147 | )); | ||
| 148 | |||
| 149 | // numThings | ||
| 150 | references = m_index.getFieldReferences(m_numThingsField); | ||
| 151 | assertThat(references, containsInAnyOrder( | ||
| 152 | newFieldReferenceByConstructor(m_numThingsField, m_subClassB.getName(), "()V"), | ||
| 153 | newFieldReferenceByMethod(m_numThingsField, m_subClassB.getName(), "b", "()V") | ||
| 154 | )); | ||
| 155 | } | ||
| 156 | |||
| 157 | @Test | ||
| 158 | @SuppressWarnings("unchecked") | ||
| 159 | public void behaviorReferences() { | ||
| 160 | |||
| 161 | BehaviorEntry source; | ||
| 162 | Collection<EntryReference<BehaviorEntry,BehaviorEntry>> references; | ||
| 163 | |||
| 164 | // baseClass constructor | ||
| 165 | source = newConstructor(m_baseClass, "(Ljava/lang/String;)V"); | ||
| 166 | references = m_index.getBehaviorReferences(source); | ||
| 167 | assertThat(references, containsInAnyOrder( | ||
| 168 | newBehaviorReferenceByConstructor(source, m_subClassA.getName(), "(Ljava/lang/String;)V"), | ||
| 169 | newBehaviorReferenceByConstructor(source, m_subClassB.getName(), "()V") | ||
| 170 | )); | ||
| 171 | |||
| 172 | // subClassA constructor | ||
| 173 | source = newConstructor(m_subClassA, "(Ljava/lang/String;)V"); | ||
| 174 | references = m_index.getBehaviorReferences(source); | ||
| 175 | assertThat(references, containsInAnyOrder( | ||
| 176 | newBehaviorReferenceByConstructor(source, m_subClassAA.getName(), "()V") | ||
| 177 | )); | ||
| 178 | |||
| 179 | // baseClass.getName() | ||
| 180 | source = newMethod(m_baseClass, "a", "()Ljava/lang/String;"); | ||
| 181 | references = m_index.getBehaviorReferences(source); | ||
| 182 | assertThat(references, containsInAnyOrder( | ||
| 183 | newBehaviorReferenceByMethod(source, m_subClassAA.getName(), "a", "()Ljava/lang/String;"), | ||
| 184 | newBehaviorReferenceByMethod(source, m_subClassB.getName(), "a", "()V") | ||
| 185 | )); | ||
| 186 | |||
| 187 | // subclassAA.getName() | ||
| 188 | source = newMethod(m_subClassAA, "a", "()Ljava/lang/String;"); | ||
| 189 | references = m_index.getBehaviorReferences(source); | ||
| 190 | assertThat(references, containsInAnyOrder( | ||
| 191 | newBehaviorReferenceByMethod(source, m_subClassAA.getName(), "a", "()V") | ||
| 192 | )); | ||
| 193 | } | ||
| 194 | |||
| 195 | @Test | ||
| 196 | public void containsEntries() { | ||
| 197 | |||
| 198 | // classes | ||
| 199 | assertThat(m_index.containsObfClass(m_baseClass), is(true)); | ||
| 200 | assertThat(m_index.containsObfClass(m_subClassA), is(true)); | ||
| 201 | assertThat(m_index.containsObfClass(m_subClassAA), is(true)); | ||
| 202 | assertThat(m_index.containsObfClass(m_subClassB), is(true)); | ||
| 203 | |||
| 204 | // fields | ||
| 205 | assertThat(m_index.containsObfField(m_nameField), is(true)); | ||
| 206 | assertThat(m_index.containsObfField(m_numThingsField), is(true)); | ||
| 207 | |||
| 208 | // methods | ||
| 209 | // getName() | ||
| 210 | assertThat(m_index.containsObfBehavior(newMethod(m_baseClass, "a", "()Ljava/lang/String;")), is(true)); | ||
| 211 | assertThat(m_index.containsObfBehavior(newMethod(m_subClassA, "a", "()Ljava/lang/String;")), is(false)); | ||
| 212 | assertThat(m_index.containsObfBehavior(newMethod(m_subClassAA, "a", "()Ljava/lang/String;")), is(true)); | ||
| 213 | assertThat(m_index.containsObfBehavior(newMethod(m_subClassB, "a", "()Ljava/lang/String;")), is(false)); | ||
| 214 | |||
| 215 | // doBaseThings() | ||
| 216 | assertThat(m_index.containsObfBehavior(newMethod(m_baseClass, "a", "()V")), is(true)); | ||
| 217 | assertThat(m_index.containsObfBehavior(newMethod(m_subClassA, "a", "()V")), is(false)); | ||
| 218 | assertThat(m_index.containsObfBehavior(newMethod(m_subClassAA, "a", "()V")), is(true)); | ||
| 219 | assertThat(m_index.containsObfBehavior(newMethod(m_subClassB, "a", "()V")), is(true)); | ||
| 220 | |||
| 221 | // doBThings() | ||
| 222 | assertThat(m_index.containsObfBehavior(newMethod(m_baseClass, "b", "()V")), is(false)); | ||
| 223 | assertThat(m_index.containsObfBehavior(newMethod(m_subClassA, "b", "()V")), is(false)); | ||
| 224 | assertThat(m_index.containsObfBehavior(newMethod(m_subClassAA, "b", "()V")), is(false)); | ||
| 225 | assertThat(m_index.containsObfBehavior(newMethod(m_subClassB, "b", "()V")), is(true)); | ||
| 226 | |||
| 227 | } | ||
| 228 | } | ||
diff --git a/test/cuchaz/enigma/TestJarIndexLoneClass.java b/test/cuchaz/enigma/TestJarIndexLoneClass.java new file mode 100644 index 0000000..c6a9e55 --- /dev/null +++ b/test/cuchaz/enigma/TestJarIndexLoneClass.java | |||
| @@ -0,0 +1,165 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * | ||
| 4 | * All rights reserved. This program and the accompanying materials | ||
| 5 | * are made available under the terms of the GNU Public License v3.0 | ||
| 6 | * which accompanies this distribution, and is available at | ||
| 7 | * http://www.gnu.org/licenses/gpl.html | ||
| 8 | * | ||
| 9 | * Contributors: | ||
| 10 | * Jeff Martin - initial API and implementation | ||
| 11 | ******************************************************************************/ | ||
| 12 | package cuchaz.enigma; | ||
| 13 | |||
| 14 | import static cuchaz.enigma.EntryFactory.*; | ||
| 15 | import static org.hamcrest.MatcherAssert.*; | ||
| 16 | import static org.hamcrest.Matchers.*; | ||
| 17 | |||
| 18 | import java.util.Collection; | ||
| 19 | import java.util.Set; | ||
| 20 | import java.util.jar.JarFile; | ||
| 21 | |||
| 22 | import org.junit.Test; | ||
| 23 | |||
| 24 | import cuchaz.enigma.analysis.Access; | ||
| 25 | import cuchaz.enigma.analysis.ClassImplementationsTreeNode; | ||
| 26 | import cuchaz.enigma.analysis.ClassInheritanceTreeNode; | ||
| 27 | import cuchaz.enigma.analysis.EntryReference; | ||
| 28 | import cuchaz.enigma.analysis.JarIndex; | ||
| 29 | import cuchaz.enigma.analysis.MethodImplementationsTreeNode; | ||
| 30 | import cuchaz.enigma.analysis.MethodInheritanceTreeNode; | ||
| 31 | import cuchaz.enigma.mapping.BehaviorEntry; | ||
| 32 | import cuchaz.enigma.mapping.ClassEntry; | ||
| 33 | import cuchaz.enigma.mapping.FieldEntry; | ||
| 34 | import cuchaz.enigma.mapping.MethodEntry; | ||
| 35 | import cuchaz.enigma.mapping.Translator; | ||
| 36 | |||
| 37 | public class TestJarIndexLoneClass { | ||
| 38 | |||
| 39 | private JarIndex m_index; | ||
| 40 | |||
| 41 | public TestJarIndexLoneClass() | ||
| 42 | throws Exception { | ||
| 43 | m_index = new JarIndex(); | ||
| 44 | m_index.indexJar(new JarFile("build/testLoneClass.obf.jar"), false); | ||
| 45 | } | ||
| 46 | |||
| 47 | @Test | ||
| 48 | public void obfEntries() { | ||
| 49 | assertThat(m_index.getObfClassEntries(), containsInAnyOrder( | ||
| 50 | newClass("cuchaz/enigma/inputs/Keep"), | ||
| 51 | newClass("none/a") | ||
| 52 | )); | ||
| 53 | } | ||
| 54 | |||
| 55 | @Test | ||
| 56 | public void translationIndex() { | ||
| 57 | assertThat(m_index.getTranslationIndex().getSuperclass(new ClassEntry("none/a")), is(nullValue())); | ||
| 58 | assertThat(m_index.getTranslationIndex().getSuperclass(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(nullValue())); | ||
| 59 | assertThat(m_index.getTranslationIndex().getAncestry(new ClassEntry("none/a")), is(empty())); | ||
| 60 | assertThat(m_index.getTranslationIndex().getAncestry(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(empty())); | ||
| 61 | assertThat(m_index.getTranslationIndex().getSubclass(new ClassEntry("none/a")), is(empty())); | ||
| 62 | assertThat(m_index.getTranslationIndex().getSubclass(new ClassEntry("cuchaz/enigma/inputs/Keep")), is(empty())); | ||
| 63 | } | ||
| 64 | |||
| 65 | @Test | ||
| 66 | public void access() { | ||
| 67 | assertThat(m_index.getAccess(newField("none/a", "a")), is(Access.Private)); | ||
| 68 | assertThat(m_index.getAccess(newMethod("none/a", "a", "()Ljava/lang/String;")), is(Access.Public)); | ||
| 69 | assertThat(m_index.getAccess(newField("none/a", "b")), is(nullValue())); | ||
| 70 | } | ||
| 71 | |||
| 72 | @Test | ||
| 73 | public void classInheritance() { | ||
| 74 | ClassInheritanceTreeNode node = m_index.getClassInheritance(new Translator(), newClass("none/a")); | ||
| 75 | assertThat(node, is(not(nullValue()))); | ||
| 76 | assertThat(node.getObfClassName(), is("none/a")); | ||
| 77 | assertThat(node.getChildCount(), is(0)); | ||
| 78 | } | ||
| 79 | |||
| 80 | @Test | ||
| 81 | public void methodInheritance() { | ||
| 82 | MethodEntry source = newMethod("none/a", "a", "()Ljava/lang/String;"); | ||
| 83 | MethodInheritanceTreeNode node = m_index.getMethodInheritance(new Translator(), source); | ||
| 84 | assertThat(node, is(not(nullValue()))); | ||
| 85 | assertThat(node.getMethodEntry(), is(source)); | ||
| 86 | assertThat(node.getChildCount(), is(0)); | ||
| 87 | } | ||
| 88 | |||
| 89 | @Test | ||
| 90 | public void classImplementations() { | ||
| 91 | ClassImplementationsTreeNode node = m_index.getClassImplementations(new Translator(), newClass("none/a")); | ||
| 92 | assertThat(node, is(nullValue())); | ||
| 93 | } | ||
| 94 | |||
| 95 | @Test | ||
| 96 | public void methodImplementations() { | ||
| 97 | MethodEntry source = newMethod("none/a", "a", "()Ljava/lang/String;"); | ||
| 98 | MethodImplementationsTreeNode node = m_index.getMethodImplementations(new Translator(), source); | ||
| 99 | assertThat(node, is(nullValue())); | ||
| 100 | } | ||
| 101 | |||
| 102 | @Test | ||
| 103 | public void relatedMethodImplementations() { | ||
| 104 | Set<MethodEntry> entries = m_index.getRelatedMethodImplementations(newMethod("none/a", "a", "()Ljava/lang/String;")); | ||
| 105 | assertThat(entries, containsInAnyOrder( | ||
| 106 | newMethod("none/a", "a", "()Ljava/lang/String;") | ||
| 107 | )); | ||
| 108 | } | ||
| 109 | |||
| 110 | @Test | ||
| 111 | @SuppressWarnings("unchecked") | ||
| 112 | public void fieldReferences() { | ||
| 113 | FieldEntry source = newField("none/a", "a"); | ||
| 114 | Collection<EntryReference<FieldEntry,BehaviorEntry>> references = m_index.getFieldReferences(source); | ||
| 115 | assertThat(references, containsInAnyOrder( | ||
| 116 | newFieldReferenceByConstructor(source, "none/a", "(Ljava/lang/String;)V"), | ||
| 117 | newFieldReferenceByMethod(source, "none/a", "a", "()Ljava/lang/String;") | ||
| 118 | )); | ||
| 119 | } | ||
| 120 | |||
| 121 | @Test | ||
| 122 | public void behaviorReferences() { | ||
| 123 | assertThat(m_index.getBehaviorReferences(newMethod("none/a", "a", "()Ljava/lang/String;")), is(empty())); | ||
| 124 | } | ||
| 125 | |||
| 126 | @Test | ||
| 127 | public void innerClasses() { | ||
| 128 | assertThat(m_index.getInnerClasses("none/a"), is(empty())); | ||
| 129 | } | ||
| 130 | |||
| 131 | @Test | ||
| 132 | public void outerClass() { | ||
| 133 | assertThat(m_index.getOuterClass("a"), is(nullValue())); | ||
| 134 | } | ||
| 135 | |||
| 136 | @Test | ||
| 137 | public void isAnonymousClass() { | ||
| 138 | assertThat(m_index.isAnonymousClass("none/a"), is(false)); | ||
| 139 | } | ||
| 140 | |||
| 141 | @Test | ||
| 142 | public void interfaces() { | ||
| 143 | assertThat(m_index.getInterfaces("none/a"), is(empty())); | ||
| 144 | } | ||
| 145 | |||
| 146 | @Test | ||
| 147 | public void implementingClasses() { | ||
| 148 | assertThat(m_index.getImplementingClasses("none/a"), is(empty())); | ||
| 149 | } | ||
| 150 | |||
| 151 | @Test | ||
| 152 | public void isInterface() { | ||
| 153 | assertThat(m_index.isInterface("none/a"), is(false)); | ||
| 154 | } | ||
| 155 | |||
| 156 | @Test | ||
| 157 | public void contains() { | ||
| 158 | assertThat(m_index.containsObfClass(newClass("none/a")), is(true)); | ||
| 159 | assertThat(m_index.containsObfClass(newClass("none/b")), is(false)); | ||
| 160 | assertThat(m_index.containsObfField(newField("none/a", "a")), is(true)); | ||
| 161 | assertThat(m_index.containsObfField(newField("none/a", "b")), is(false)); | ||
| 162 | assertThat(m_index.containsObfBehavior(newMethod("none/a", "a", "()Ljava/lang/String;")), is(true)); | ||
| 163 | assertThat(m_index.containsObfBehavior(newMethod("none/a", "b", "()Ljava/lang/String;")), is(false)); | ||
| 164 | } | ||
| 165 | } | ||
diff --git a/test/cuchaz/enigma/TestSignature.java b/test/cuchaz/enigma/TestSignature.java new file mode 100644 index 0000000..183a4fd --- /dev/null +++ b/test/cuchaz/enigma/TestSignature.java | |||
| @@ -0,0 +1,266 @@ | |||
| 1 | package cuchaz.enigma; | ||
| 2 | |||
| 3 | import static org.hamcrest.MatcherAssert.*; | ||
| 4 | import static org.hamcrest.Matchers.*; | ||
| 5 | |||
| 6 | import org.junit.Test; | ||
| 7 | |||
| 8 | import cuchaz.enigma.mapping.ClassNameReplacer; | ||
| 9 | import cuchaz.enigma.mapping.Signature; | ||
| 10 | import cuchaz.enigma.mapping.Type; | ||
| 11 | |||
| 12 | |||
| 13 | public class TestSignature { | ||
| 14 | |||
| 15 | @Test | ||
| 16 | public void easiest() { | ||
| 17 | final Signature sig = new Signature("()V"); | ||
| 18 | assertThat(sig.getArgumentTypes(), is(empty())); | ||
| 19 | assertThat(sig.getReturnType(), is(new Type("V"))); | ||
| 20 | } | ||
| 21 | |||
| 22 | @Test | ||
| 23 | public void primitives() { | ||
| 24 | { | ||
| 25 | final Signature sig = new Signature("(I)V"); | ||
| 26 | assertThat(sig.getArgumentTypes(), contains( | ||
| 27 | new Type("I") | ||
| 28 | )); | ||
| 29 | assertThat(sig.getReturnType(), is(new Type("V"))); | ||
| 30 | } | ||
| 31 | { | ||
| 32 | final Signature sig = new Signature("(I)I"); | ||
| 33 | assertThat(sig.getArgumentTypes(), contains( | ||
| 34 | new Type("I") | ||
| 35 | )); | ||
| 36 | assertThat(sig.getReturnType(), is(new Type("I"))); | ||
| 37 | } | ||
| 38 | { | ||
| 39 | final Signature sig = new Signature("(IBCJ)Z"); | ||
| 40 | assertThat(sig.getArgumentTypes(), contains( | ||
| 41 | new Type("I"), | ||
| 42 | new Type("B"), | ||
| 43 | new Type("C"), | ||
| 44 | new Type("J") | ||
| 45 | )); | ||
| 46 | assertThat(sig.getReturnType(), is(new Type("Z"))); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | @Test | ||
| 51 | public void classes() { | ||
| 52 | { | ||
| 53 | final Signature sig = new Signature("([LFoo;)V"); | ||
| 54 | assertThat(sig.getArgumentTypes().size(), is(1)); | ||
| 55 | assertThat(sig.getArgumentTypes().get(0), is(new Type("[LFoo;"))); | ||
| 56 | assertThat(sig.getReturnType(), is(new Type("V"))); | ||
| 57 | } | ||
| 58 | { | ||
| 59 | final Signature sig = new Signature("(LFoo;)LBar;"); | ||
| 60 | assertThat(sig.getArgumentTypes(), contains( | ||
| 61 | new Type("LFoo;") | ||
| 62 | )); | ||
| 63 | assertThat(sig.getReturnType(), is(new Type("LBar;"))); | ||
| 64 | } | ||
| 65 | { | ||
| 66 | final Signature sig = new Signature("(LFoo;LMoo;LZoo;)LBar;"); | ||
| 67 | assertThat(sig.getArgumentTypes(), contains( | ||
| 68 | new Type("LFoo;"), | ||
| 69 | new Type("LMoo;"), | ||
| 70 | new Type("LZoo;") | ||
| 71 | )); | ||
| 72 | assertThat(sig.getReturnType(), is(new Type("LBar;"))); | ||
| 73 | } | ||
| 74 | { | ||
| 75 | final Signature sig = new Signature("(LFoo<LParm;>;LMoo<LParm;>;)LBar<LParm;>;"); | ||
| 76 | assertThat(sig.getArgumentTypes(), contains( | ||
| 77 | new Type("LFoo<LParm;>;"), | ||
| 78 | new Type("LMoo<LParm;>;") | ||
| 79 | )); | ||
| 80 | assertThat(sig.getReturnType(), is(new Type("LBar<LParm;>;"))); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | @Test | ||
| 85 | public void arrays() { | ||
| 86 | { | ||
| 87 | final Signature sig = new Signature("([I)V"); | ||
| 88 | assertThat(sig.getArgumentTypes(), contains( | ||
| 89 | new Type("[I") | ||
| 90 | )); | ||
| 91 | assertThat(sig.getReturnType(), is(new Type("V"))); | ||
| 92 | } | ||
| 93 | { | ||
| 94 | final Signature sig = new Signature("([I)[J"); | ||
| 95 | assertThat(sig.getArgumentTypes(), contains( | ||
| 96 | new Type("[I") | ||
| 97 | )); | ||
| 98 | assertThat(sig.getReturnType(), is(new Type("[J"))); | ||
| 99 | } | ||
| 100 | { | ||
| 101 | final Signature sig = new Signature("([I[Z[F)[D"); | ||
| 102 | assertThat(sig.getArgumentTypes(), contains( | ||
| 103 | new Type("[I"), | ||
| 104 | new Type("[Z"), | ||
| 105 | new Type("[F") | ||
| 106 | )); | ||
| 107 | assertThat(sig.getReturnType(), is(new Type("[D"))); | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | @Test | ||
| 112 | public void mixed() { | ||
| 113 | { | ||
| 114 | final Signature sig = new Signature("(I[JLFoo;)Z"); | ||
| 115 | assertThat(sig.getArgumentTypes(), contains( | ||
| 116 | new Type("I"), | ||
| 117 | new Type("[J"), | ||
| 118 | new Type("LFoo;") | ||
| 119 | )); | ||
| 120 | assertThat(sig.getReturnType(), is(new Type("Z"))); | ||
| 121 | } | ||
| 122 | { | ||
| 123 | final Signature sig = new Signature("(III)[LFoo;"); | ||
| 124 | assertThat(sig.getArgumentTypes(), contains( | ||
| 125 | new Type("I"), | ||
| 126 | new Type("I"), | ||
| 127 | new Type("I") | ||
| 128 | )); | ||
| 129 | assertThat(sig.getReturnType(), is(new Type("[LFoo;"))); | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | @Test | ||
| 134 | public void replaceClasses() { | ||
| 135 | { | ||
| 136 | final Signature oldSig = new Signature("()V"); | ||
| 137 | final Signature sig = new Signature(oldSig, new ClassNameReplacer() { | ||
| 138 | @Override | ||
| 139 | public String replace(String val) { | ||
| 140 | return null; | ||
| 141 | } | ||
| 142 | }); | ||
| 143 | assertThat(sig.getArgumentTypes(), is(empty())); | ||
| 144 | assertThat(sig.getReturnType(), is(new Type("V"))); | ||
| 145 | } | ||
| 146 | { | ||
| 147 | final Signature oldSig = new Signature("(IJLFoo;)V"); | ||
| 148 | final Signature sig = new Signature(oldSig, new ClassNameReplacer() { | ||
| 149 | @Override | ||
| 150 | public String replace(String val) { | ||
| 151 | return null; | ||
| 152 | } | ||
| 153 | }); | ||
| 154 | assertThat(sig.getArgumentTypes(), contains( | ||
| 155 | new Type("I"), | ||
| 156 | new Type("J"), | ||
| 157 | new Type("LFoo;") | ||
| 158 | )); | ||
| 159 | assertThat(sig.getReturnType(), is(new Type("V"))); | ||
| 160 | } | ||
| 161 | { | ||
| 162 | final Signature oldSig = new Signature("(LFoo;LBar;)LMoo;"); | ||
| 163 | final Signature sig = new Signature(oldSig, new ClassNameReplacer() { | ||
| 164 | @Override | ||
| 165 | public String replace(String val) { | ||
| 166 | if (val.equals("Foo")) { | ||
| 167 | return "Bar"; | ||
| 168 | } | ||
| 169 | return null; | ||
| 170 | } | ||
| 171 | }); | ||
| 172 | assertThat(sig.getArgumentTypes(), contains( | ||
| 173 | new Type("LBar;"), | ||
| 174 | new Type("LBar;") | ||
| 175 | )); | ||
| 176 | assertThat(sig.getReturnType(), is(new Type("LMoo;"))); | ||
| 177 | } | ||
| 178 | { | ||
| 179 | final Signature oldSig = new Signature("(LFoo;LBar;)LMoo;"); | ||
| 180 | final Signature sig = new Signature(oldSig, new ClassNameReplacer() { | ||
| 181 | @Override | ||
| 182 | public String replace(String val) { | ||
| 183 | if (val.equals("Moo")) { | ||
| 184 | return "Cow"; | ||
| 185 | } | ||
| 186 | return null; | ||
| 187 | } | ||
| 188 | }); | ||
| 189 | assertThat(sig.getArgumentTypes(), contains( | ||
| 190 | new Type("LFoo;"), | ||
| 191 | new Type("LBar;") | ||
| 192 | )); | ||
| 193 | assertThat(sig.getReturnType(), is(new Type("LCow;"))); | ||
| 194 | } | ||
| 195 | } | ||
| 196 | |||
| 197 | @Test | ||
| 198 | public void replaceArrayClasses() { | ||
| 199 | { | ||
| 200 | final Signature oldSig = new Signature("([LFoo;)[[[LBar;"); | ||
| 201 | final Signature sig = new Signature(oldSig, new ClassNameReplacer() { | ||
| 202 | @Override | ||
| 203 | public String replace(String val) { | ||
| 204 | if (val.equals("Foo")) { | ||
| 205 | return "Food"; | ||
| 206 | } else if (val.equals("Bar")) { | ||
| 207 | return "Beer"; | ||
| 208 | } | ||
| 209 | return null; | ||
| 210 | } | ||
| 211 | }); | ||
| 212 | assertThat(sig.getArgumentTypes(), contains( | ||
| 213 | new Type("[LFood;") | ||
| 214 | )); | ||
| 215 | assertThat(sig.getReturnType(), is(new Type("[[[LBeer;"))); | ||
| 216 | } | ||
| 217 | } | ||
| 218 | |||
| 219 | @Test | ||
| 220 | public void equals() { | ||
| 221 | |||
| 222 | // base | ||
| 223 | assertThat(new Signature("()V"), is(new Signature("()V"))); | ||
| 224 | |||
| 225 | // arguments | ||
| 226 | assertThat(new Signature("(I)V"), is(new Signature("(I)V"))); | ||
| 227 | assertThat(new Signature("(ZIZ)V"), is(new Signature("(ZIZ)V"))); | ||
| 228 | assertThat(new Signature("(LFoo;)V"), is(new Signature("(LFoo;)V"))); | ||
| 229 | assertThat(new Signature("(LFoo;LBar;)V"), is(new Signature("(LFoo;LBar;)V"))); | ||
| 230 | assertThat(new Signature("([I)V"), is(new Signature("([I)V"))); | ||
| 231 | assertThat(new Signature("([[D[[[J)V"), is(new Signature("([[D[[[J)V"))); | ||
| 232 | |||
| 233 | assertThat(new Signature("()V"), is(not(new Signature("(I)V")))); | ||
| 234 | assertThat(new Signature("(I)V"), is(not(new Signature("()V")))); | ||
| 235 | assertThat(new Signature("(IJ)V"), is(not(new Signature("(JI)V")))); | ||
| 236 | assertThat(new Signature("([[Z)V"), is(not(new Signature("([[LFoo;)V")))); | ||
| 237 | assertThat(new Signature("(LFoo;LBar;)V"), is(not(new Signature("(LFoo;LCow;)V")))); | ||
| 238 | assertThat(new Signature("([LFoo;LBar;)V"), is(not(new Signature("(LFoo;LCow;)V")))); | ||
| 239 | |||
| 240 | // return type | ||
| 241 | assertThat(new Signature("()I"), is(new Signature("()I"))); | ||
| 242 | assertThat(new Signature("()Z"), is(new Signature("()Z"))); | ||
| 243 | assertThat(new Signature("()[D"), is(new Signature("()[D"))); | ||
| 244 | assertThat(new Signature("()[[[Z"), is(new Signature("()[[[Z"))); | ||
| 245 | assertThat(new Signature("()LFoo;"), is(new Signature("()LFoo;"))); | ||
| 246 | assertThat(new Signature("()[LFoo;"), is(new Signature("()[LFoo;"))); | ||
| 247 | |||
| 248 | assertThat(new Signature("()I"), is(not(new Signature("()Z")))); | ||
| 249 | assertThat(new Signature("()Z"), is(not(new Signature("()I")))); | ||
| 250 | assertThat(new Signature("()[D"), is(not(new Signature("()[J")))); | ||
| 251 | assertThat(new Signature("()[[[Z"), is(not(new Signature("()[[Z")))); | ||
| 252 | assertThat(new Signature("()LFoo;"), is(not(new Signature("()LBar;")))); | ||
| 253 | assertThat(new Signature("()[LFoo;"), is(not(new Signature("()[LBar;")))); | ||
| 254 | } | ||
| 255 | |||
| 256 | @Test | ||
| 257 | public void testToString() { | ||
| 258 | assertThat(new Signature("()V").toString(), is("()V")); | ||
| 259 | assertThat(new Signature("(I)V").toString(), is("(I)V")); | ||
| 260 | assertThat(new Signature("(ZIZ)V").toString(), is("(ZIZ)V")); | ||
| 261 | assertThat(new Signature("(LFoo;)V").toString(), is("(LFoo;)V")); | ||
| 262 | assertThat(new Signature("(LFoo;LBar;)V").toString(), is("(LFoo;LBar;)V")); | ||
| 263 | assertThat(new Signature("([I)V").toString(), is("([I)V")); | ||
| 264 | assertThat(new Signature("([[D[[[J)V").toString(), is("([[D[[[J)V")); | ||
| 265 | } | ||
| 266 | } | ||
diff --git a/test/cuchaz/enigma/TestSourceIndex.java b/test/cuchaz/enigma/TestSourceIndex.java new file mode 100644 index 0000000..357acb6 --- /dev/null +++ b/test/cuchaz/enigma/TestSourceIndex.java | |||
| @@ -0,0 +1,50 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * | ||
| 4 | * All rights reserved. This program and the accompanying materials | ||
| 5 | * are made available under the terms of the GNU Public License v3.0 | ||
| 6 | * which accompanies this distribution, and is available at | ||
| 7 | * http://www.gnu.org/licenses/gpl.html | ||
| 8 | * | ||
| 9 | * Contributors: | ||
| 10 | * Jeff Martin - initial API and implementation | ||
| 11 | ******************************************************************************/ | ||
| 12 | package cuchaz.enigma; | ||
| 13 | |||
| 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 | |||
| 26 | // TEMP | ||
| 27 | //@Test | ||
| 28 | public void indexEverything() | ||
| 29 | throws Exception { | ||
| 30 | Deobfuscator deobfuscator = new Deobfuscator(new JarFile("input/1.8.jar")); | ||
| 31 | |||
| 32 | // get all classes that aren't inner classes | ||
| 33 | Set<ClassEntry> classEntries = Sets.newHashSet(); | ||
| 34 | for (ClassEntry obfClassEntry : deobfuscator.getJarIndex().getObfClassEntries()) { | ||
| 35 | if (!obfClassEntry.isInnerClass()) { | ||
| 36 | classEntries.add(obfClassEntry); | ||
| 37 | } | ||
| 38 | } | ||
| 39 | |||
| 40 | for (ClassEntry obfClassEntry : classEntries) { | ||
| 41 | try { | ||
| 42 | CompilationUnit tree = deobfuscator.getSourceTree(obfClassEntry.getName()); | ||
| 43 | String source = deobfuscator.getSource(tree); | ||
| 44 | deobfuscator.getSourceIndex(tree, source); | ||
| 45 | } catch (Throwable t) { | ||
| 46 | throw new Error("Unable to index " + obfClassEntry, t); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | } | ||
| 50 | } | ||
diff --git a/test/cuchaz/enigma/TestTokensConstructors.java b/test/cuchaz/enigma/TestTokensConstructors.java new file mode 100644 index 0000000..6758d2a --- /dev/null +++ b/test/cuchaz/enigma/TestTokensConstructors.java | |||
| @@ -0,0 +1,136 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | package cuchaz.enigma; | ||
| 12 | |||
| 13 | import static cuchaz.enigma.EntryFactory.*; | ||
| 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/testConstructors.obf.jar")); | ||
| 28 | } | ||
| 29 | |||
| 30 | @Test | ||
| 31 | public void baseDeclarations() { | ||
| 32 | assertThat(getDeclarationToken(newConstructor("none/a", "()V")), is("a")); | ||
| 33 | assertThat(getDeclarationToken(newConstructor("none/a", "(I)V")), is("a")); | ||
| 34 | } | ||
| 35 | |||
| 36 | @Test | ||
| 37 | public void subDeclarations() { | ||
| 38 | assertThat(getDeclarationToken(newConstructor("none/d", "()V")), is("d")); | ||
| 39 | assertThat(getDeclarationToken(newConstructor("none/d", "(I)V")), is("d")); | ||
| 40 | assertThat(getDeclarationToken(newConstructor("none/d", "(II)V")), is("d")); | ||
| 41 | assertThat(getDeclarationToken(newConstructor("none/d", "(III)V")), is("d")); | ||
| 42 | } | ||
| 43 | |||
| 44 | @Test | ||
| 45 | public void subsubDeclarations() { | ||
| 46 | assertThat(getDeclarationToken(newConstructor("none/e", "(I)V")), is("e")); | ||
| 47 | } | ||
| 48 | |||
| 49 | @Test | ||
| 50 | public void defaultDeclarations() { | ||
| 51 | assertThat(getDeclarationToken(newConstructor("none/c", "()V")), nullValue()); | ||
| 52 | } | ||
| 53 | |||
| 54 | @Test | ||
| 55 | public void baseDefaultReferences() { | ||
| 56 | BehaviorEntry source = newConstructor("none/a", "()V"); | ||
| 57 | assertThat( | ||
| 58 | getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "a", "()V")), | ||
| 59 | containsInAnyOrder("a") | ||
| 60 | ); | ||
| 61 | assertThat( | ||
| 62 | getReferenceTokens(newBehaviorReferenceByConstructor(source, "none/d", "()V")), | ||
| 63 | is(empty()) // implicit call, not decompiled to token | ||
| 64 | ); | ||
| 65 | assertThat( | ||
| 66 | getReferenceTokens(newBehaviorReferenceByConstructor(source, "none/d", "(III)V")), | ||
| 67 | is(empty()) // implicit call, not decompiled to token | ||
| 68 | ); | ||
| 69 | } | ||
| 70 | |||
| 71 | @Test | ||
| 72 | public void baseIntReferences() { | ||
| 73 | BehaviorEntry source = newConstructor("none/a", "(I)V"); | ||
| 74 | assertThat( | ||
| 75 | getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "b", "()V")), | ||
| 76 | containsInAnyOrder("a") | ||
| 77 | ); | ||
| 78 | } | ||
| 79 | |||
| 80 | @Test | ||
| 81 | public void subDefaultReferences() { | ||
| 82 | BehaviorEntry source = newConstructor("none/d", "()V"); | ||
| 83 | assertThat( | ||
| 84 | getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "c", "()V")), | ||
| 85 | containsInAnyOrder("d") | ||
| 86 | ); | ||
| 87 | assertThat( | ||
| 88 | getReferenceTokens(newBehaviorReferenceByConstructor(source, "none/d", "(I)V")), | ||
| 89 | containsInAnyOrder("this") | ||
| 90 | ); | ||
| 91 | } | ||
| 92 | |||
| 93 | @Test | ||
| 94 | public void subIntReferences() { | ||
| 95 | BehaviorEntry source = newConstructor("none/d", "(I)V"); | ||
| 96 | assertThat(getReferenceTokens( | ||
| 97 | newBehaviorReferenceByMethod(source, "none/b", "d", "()V")), | ||
| 98 | containsInAnyOrder("d") | ||
| 99 | ); | ||
| 100 | assertThat(getReferenceTokens( | ||
| 101 | newBehaviorReferenceByConstructor(source, "none/d", "(II)V")), | ||
| 102 | containsInAnyOrder("this") | ||
| 103 | ); | ||
| 104 | assertThat(getReferenceTokens( | ||
| 105 | newBehaviorReferenceByConstructor(source, "none/e", "(I)V")), | ||
| 106 | containsInAnyOrder("super") | ||
| 107 | ); | ||
| 108 | } | ||
| 109 | |||
| 110 | @Test | ||
| 111 | public void subIntIntReferences() { | ||
| 112 | BehaviorEntry source = newConstructor("none/d", "(II)V"); | ||
| 113 | assertThat( | ||
| 114 | getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "e", "()V")), | ||
| 115 | containsInAnyOrder("d") | ||
| 116 | ); | ||
| 117 | } | ||
| 118 | |||
| 119 | @Test | ||
| 120 | public void subsubIntReferences() { | ||
| 121 | BehaviorEntry source = newConstructor("none/e", "(I)V"); | ||
| 122 | assertThat( | ||
| 123 | getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "f", "()V")), | ||
| 124 | containsInAnyOrder("e") | ||
| 125 | ); | ||
| 126 | } | ||
| 127 | |||
| 128 | @Test | ||
| 129 | public void defaultConstructableReferences() { | ||
| 130 | BehaviorEntry source = newConstructor("none/c", "()V"); | ||
| 131 | assertThat( | ||
| 132 | getReferenceTokens(newBehaviorReferenceByMethod(source, "none/b", "g", "()V")), | ||
| 133 | containsInAnyOrder("c") | ||
| 134 | ); | ||
| 135 | } | ||
| 136 | } | ||
diff --git a/test/cuchaz/enigma/TestTranslator.java b/test/cuchaz/enigma/TestTranslator.java new file mode 100644 index 0000000..290f6f0 --- /dev/null +++ b/test/cuchaz/enigma/TestTranslator.java | |||
| @@ -0,0 +1,39 @@ | |||
| 1 | package cuchaz.enigma; | ||
| 2 | |||
| 3 | import static cuchaz.enigma.EntryFactory.*; | ||
| 4 | import static org.hamcrest.MatcherAssert.*; | ||
| 5 | import static org.hamcrest.Matchers.*; | ||
| 6 | |||
| 7 | import java.io.InputStream; | ||
| 8 | import java.io.InputStreamReader; | ||
| 9 | import java.util.jar.JarFile; | ||
| 10 | |||
| 11 | import org.junit.Test; | ||
| 12 | |||
| 13 | import cuchaz.enigma.mapping.Mappings; | ||
| 14 | import cuchaz.enigma.mapping.MappingsReader; | ||
| 15 | import cuchaz.enigma.mapping.TranslationDirection; | ||
| 16 | import cuchaz.enigma.mapping.Translator; | ||
| 17 | |||
| 18 | |||
| 19 | public class TestTranslator { | ||
| 20 | |||
| 21 | private Deobfuscator m_deobfuscator; | ||
| 22 | private Mappings m_mappings; | ||
| 23 | |||
| 24 | public TestTranslator() | ||
| 25 | throws Exception { | ||
| 26 | m_deobfuscator = new Deobfuscator(new JarFile("build/testTranslation.obf.jar")); | ||
| 27 | try (InputStream in = getClass().getResourceAsStream("/cuchaz/enigma/resources/translation.mappings")) { | ||
| 28 | m_mappings = new MappingsReader().read(new InputStreamReader(in)); | ||
| 29 | m_deobfuscator.setMappings(m_mappings); | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | @Test | ||
| 34 | public void deobfuscatingTranslations() | ||
| 35 | throws Exception { | ||
| 36 | Translator translator = m_deobfuscator.getTranslator(TranslationDirection.Deobfuscating); | ||
| 37 | assertThat(translator.translateEntry(newClass("none/a")), is(newClass("deobf/A"))); | ||
| 38 | } | ||
| 39 | } | ||
diff --git a/test/cuchaz/enigma/TestType.java b/test/cuchaz/enigma/TestType.java new file mode 100644 index 0000000..7c3cebe --- /dev/null +++ b/test/cuchaz/enigma/TestType.java | |||
| @@ -0,0 +1,229 @@ | |||
| 1 | package cuchaz.enigma; | ||
| 2 | |||
| 3 | import org.junit.Test; | ||
| 4 | |||
| 5 | import static org.hamcrest.MatcherAssert.*; | ||
| 6 | import static org.hamcrest.Matchers.*; | ||
| 7 | |||
| 8 | import cuchaz.enigma.mapping.Type; | ||
| 9 | |||
| 10 | import static cuchaz.enigma.EntryFactory.*; | ||
| 11 | |||
| 12 | |||
| 13 | public class TestType { | ||
| 14 | |||
| 15 | @Test | ||
| 16 | public void isVoid() { | ||
| 17 | assertThat(new Type("V").isVoid(), is(true)); | ||
| 18 | assertThat(new Type("Z").isVoid(), is(false)); | ||
| 19 | assertThat(new Type("B").isVoid(), is(false)); | ||
| 20 | assertThat(new Type("C").isVoid(), is(false)); | ||
| 21 | assertThat(new Type("I").isVoid(), is(false)); | ||
| 22 | assertThat(new Type("J").isVoid(), is(false)); | ||
| 23 | assertThat(new Type("F").isVoid(), is(false)); | ||
| 24 | assertThat(new Type("D").isVoid(), is(false)); | ||
| 25 | assertThat(new Type("LFoo;").isVoid(), is(false)); | ||
| 26 | assertThat(new Type("[I").isVoid(), is(false)); | ||
| 27 | } | ||
| 28 | |||
| 29 | @Test | ||
| 30 | public void isPrimitive() { | ||
| 31 | assertThat(new Type("V").isPrimitive(), is(false)); | ||
| 32 | assertThat(new Type("Z").isPrimitive(), is(true)); | ||
| 33 | assertThat(new Type("B").isPrimitive(), is(true)); | ||
| 34 | assertThat(new Type("C").isPrimitive(), is(true)); | ||
| 35 | assertThat(new Type("I").isPrimitive(), is(true)); | ||
| 36 | assertThat(new Type("J").isPrimitive(), is(true)); | ||
| 37 | assertThat(new Type("F").isPrimitive(), is(true)); | ||
| 38 | assertThat(new Type("D").isPrimitive(), is(true)); | ||
| 39 | assertThat(new Type("LFoo;").isPrimitive(), is(false)); | ||
| 40 | assertThat(new Type("[I").isPrimitive(), is(false)); | ||
| 41 | } | ||
| 42 | |||
| 43 | @Test | ||
| 44 | public void getPrimitive() { | ||
| 45 | assertThat(new Type("Z").getPrimitive(), is(Type.Primitive.Boolean)); | ||
| 46 | assertThat(new Type("B").getPrimitive(), is(Type.Primitive.Byte)); | ||
| 47 | assertThat(new Type("C").getPrimitive(), is(Type.Primitive.Character)); | ||
| 48 | assertThat(new Type("I").getPrimitive(), is(Type.Primitive.Integer)); | ||
| 49 | assertThat(new Type("J").getPrimitive(), is(Type.Primitive.Long)); | ||
| 50 | assertThat(new Type("F").getPrimitive(), is(Type.Primitive.Float)); | ||
| 51 | assertThat(new Type("D").getPrimitive(), is(Type.Primitive.Double)); | ||
| 52 | } | ||
| 53 | |||
| 54 | @Test | ||
| 55 | public void isClass() { | ||
| 56 | assertThat(new Type("V").isClass(), is(false)); | ||
| 57 | assertThat(new Type("Z").isClass(), is(false)); | ||
| 58 | assertThat(new Type("B").isClass(), is(false)); | ||
| 59 | assertThat(new Type("C").isClass(), is(false)); | ||
| 60 | assertThat(new Type("I").isClass(), is(false)); | ||
| 61 | assertThat(new Type("J").isClass(), is(false)); | ||
| 62 | assertThat(new Type("F").isClass(), is(false)); | ||
| 63 | assertThat(new Type("D").isClass(), is(false)); | ||
| 64 | assertThat(new Type("LFoo;").isClass(), is(true)); | ||
| 65 | assertThat(new Type("[I").isClass(), is(false)); | ||
| 66 | } | ||
| 67 | |||
| 68 | @Test | ||
| 69 | public void getClassEntry() { | ||
| 70 | assertThat(new Type("LFoo;").getClassEntry(), is(newClass("Foo"))); | ||
| 71 | assertThat(new Type("LFoo<Ljava/lang/String;>;").getClassEntry(), is(newClass("Foo"))); | ||
| 72 | } | ||
| 73 | |||
| 74 | @Test | ||
| 75 | public void getArrayClassEntry() { | ||
| 76 | assertThat(new Type("[LFoo;").getClassEntry(), is(newClass("Foo"))); | ||
| 77 | assertThat(new Type("[[[LFoo<Ljava/lang/String;>;").getClassEntry(), is(newClass("Foo"))); | ||
| 78 | } | ||
| 79 | |||
| 80 | @Test | ||
| 81 | public void isArray() { | ||
| 82 | assertThat(new Type("V").isArray(), is(false)); | ||
| 83 | assertThat(new Type("Z").isArray(), is(false)); | ||
| 84 | assertThat(new Type("B").isArray(), is(false)); | ||
| 85 | assertThat(new Type("C").isArray(), is(false)); | ||
| 86 | assertThat(new Type("I").isArray(), is(false)); | ||
| 87 | assertThat(new Type("J").isArray(), is(false)); | ||
| 88 | assertThat(new Type("F").isArray(), is(false)); | ||
| 89 | assertThat(new Type("D").isArray(), is(false)); | ||
| 90 | assertThat(new Type("LFoo;").isArray(), is(false)); | ||
| 91 | assertThat(new Type("[I").isArray(), is(true)); | ||
| 92 | } | ||
| 93 | |||
| 94 | @Test | ||
| 95 | public void getArrayDimension() { | ||
| 96 | assertThat(new Type("[I").getArrayDimension(), is(1)); | ||
| 97 | assertThat(new Type("[[I").getArrayDimension(), is(2)); | ||
| 98 | assertThat(new Type("[[[I").getArrayDimension(), is(3)); | ||
| 99 | } | ||
| 100 | |||
| 101 | @Test | ||
| 102 | public void getArrayType() { | ||
| 103 | assertThat(new Type("[I").getArrayType(), is(new Type("I"))); | ||
| 104 | assertThat(new Type("[[I").getArrayType(), is(new Type("I"))); | ||
| 105 | assertThat(new Type("[[[I").getArrayType(), is(new Type("I"))); | ||
| 106 | assertThat(new Type("[Ljava/lang/String;").getArrayType(), is(new Type("Ljava/lang/String;"))); | ||
| 107 | } | ||
| 108 | |||
| 109 | @Test | ||
| 110 | public void hasClass() { | ||
| 111 | assertThat(new Type("LFoo;").hasClass(), is(true)); | ||
| 112 | assertThat(new Type("LCow<LCheese;>;").hasClass(), is(true)); | ||
| 113 | assertThat(new Type("[LBar;").hasClass(), is(true)); | ||
| 114 | assertThat(new Type("[[[LCat;").hasClass(), is(true)); | ||
| 115 | |||
| 116 | assertThat(new Type("V").hasClass(), is(false)); | ||
| 117 | assertThat(new Type("[I").hasClass(), is(false)); | ||
| 118 | assertThat(new Type("[[[I").hasClass(), is(false)); | ||
| 119 | assertThat(new Type("Z").hasClass(), is(false)); | ||
| 120 | } | ||
| 121 | |||
| 122 | @Test | ||
| 123 | public void parseVoid() { | ||
| 124 | final String answer = "V"; | ||
| 125 | assertThat(Type.parseFirst("V"), is(answer)); | ||
| 126 | assertThat(Type.parseFirst("VVV"), is(answer)); | ||
| 127 | assertThat(Type.parseFirst("VIJ"), is(answer)); | ||
| 128 | assertThat(Type.parseFirst("V[I"), is(answer)); | ||
| 129 | assertThat(Type.parseFirst("VLFoo;"), is(answer)); | ||
| 130 | assertThat(Type.parseFirst("V[LFoo;"), is(answer)); | ||
| 131 | } | ||
| 132 | |||
| 133 | @Test | ||
| 134 | public void parsePrimitive() { | ||
| 135 | final String answer = "I"; | ||
| 136 | assertThat(Type.parseFirst("I"), is(answer)); | ||
| 137 | assertThat(Type.parseFirst("III"), is(answer)); | ||
| 138 | assertThat(Type.parseFirst("IJZ"), is(answer)); | ||
| 139 | assertThat(Type.parseFirst("I[I"), is(answer)); | ||
| 140 | assertThat(Type.parseFirst("ILFoo;"), is(answer)); | ||
| 141 | assertThat(Type.parseFirst("I[LFoo;"), is(answer)); | ||
| 142 | } | ||
| 143 | |||
| 144 | @Test | ||
| 145 | public void parseClass() { | ||
| 146 | { | ||
| 147 | final String answer = "LFoo;"; | ||
| 148 | assertThat(Type.parseFirst("LFoo;"), is(answer)); | ||
| 149 | assertThat(Type.parseFirst("LFoo;I"), is(answer)); | ||
| 150 | assertThat(Type.parseFirst("LFoo;JZ"), is(answer)); | ||
| 151 | assertThat(Type.parseFirst("LFoo;[I"), is(answer)); | ||
| 152 | assertThat(Type.parseFirst("LFoo;LFoo;"), is(answer)); | ||
| 153 | assertThat(Type.parseFirst("LFoo;[LFoo;"), is(answer)); | ||
| 154 | } | ||
| 155 | { | ||
| 156 | final String answer = "LFoo<LFoo;>;"; | ||
| 157 | assertThat(Type.parseFirst("LFoo<LFoo;>;"), is(answer)); | ||
| 158 | assertThat(Type.parseFirst("LFoo<LFoo;>;I"), is(answer)); | ||
| 159 | assertThat(Type.parseFirst("LFoo<LFoo;>;JZ"), is(answer)); | ||
| 160 | assertThat(Type.parseFirst("LFoo<LFoo;>;[I"), is(answer)); | ||
| 161 | assertThat(Type.parseFirst("LFoo<LFoo;>;LFoo;"), is(answer)); | ||
| 162 | assertThat(Type.parseFirst("LFoo<LFoo;>;[LFoo;"), is(answer)); | ||
| 163 | } | ||
| 164 | { | ||
| 165 | final String answer = "LFoo<LFoo;,LBar;>;"; | ||
| 166 | assertThat(Type.parseFirst("LFoo<LFoo;,LBar;>;"), is(answer)); | ||
| 167 | assertThat(Type.parseFirst("LFoo<LFoo;,LBar;>;I"), is(answer)); | ||
| 168 | assertThat(Type.parseFirst("LFoo<LFoo;,LBar;>;JZ"), is(answer)); | ||
| 169 | assertThat(Type.parseFirst("LFoo<LFoo;,LBar;>;[I"), is(answer)); | ||
| 170 | assertThat(Type.parseFirst("LFoo<LFoo;,LBar;>;LFoo;"), is(answer)); | ||
| 171 | assertThat(Type.parseFirst("LFoo<LFoo;,LBar;>;[LFoo;"), is(answer)); | ||
| 172 | } | ||
| 173 | } | ||
| 174 | |||
| 175 | @Test | ||
| 176 | public void parseArray() { | ||
| 177 | { | ||
| 178 | final String answer = "[I"; | ||
| 179 | assertThat(Type.parseFirst("[I"), is(answer)); | ||
| 180 | assertThat(Type.parseFirst("[III"), is(answer)); | ||
| 181 | assertThat(Type.parseFirst("[IJZ"), is(answer)); | ||
| 182 | assertThat(Type.parseFirst("[I[I"), is(answer)); | ||
| 183 | assertThat(Type.parseFirst("[ILFoo;"), is(answer)); | ||
| 184 | } | ||
| 185 | { | ||
| 186 | final String answer = "[[I"; | ||
| 187 | assertThat(Type.parseFirst("[[I"), is(answer)); | ||
| 188 | assertThat(Type.parseFirst("[[III"), is(answer)); | ||
| 189 | assertThat(Type.parseFirst("[[IJZ"), is(answer)); | ||
| 190 | assertThat(Type.parseFirst("[[I[I"), is(answer)); | ||
| 191 | assertThat(Type.parseFirst("[[ILFoo;"), is(answer)); | ||
| 192 | } | ||
| 193 | { | ||
| 194 | final String answer = "[LFoo;"; | ||
| 195 | assertThat(Type.parseFirst("[LFoo;"), is(answer)); | ||
| 196 | assertThat(Type.parseFirst("[LFoo;II"), is(answer)); | ||
| 197 | assertThat(Type.parseFirst("[LFoo;JZ"), is(answer)); | ||
| 198 | assertThat(Type.parseFirst("[LFoo;[I"), is(answer)); | ||
| 199 | assertThat(Type.parseFirst("[LFoo;LFoo;"), is(answer)); | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | @Test | ||
| 204 | public void equals() { | ||
| 205 | assertThat(new Type("V"), is(new Type("V"))); | ||
| 206 | assertThat(new Type("Z"), is(new Type("Z"))); | ||
| 207 | assertThat(new Type("B"), is(new Type("B"))); | ||
| 208 | assertThat(new Type("C"), is(new Type("C"))); | ||
| 209 | assertThat(new Type("I"), is(new Type("I"))); | ||
| 210 | assertThat(new Type("J"), is(new Type("J"))); | ||
| 211 | assertThat(new Type("F"), is(new Type("F"))); | ||
| 212 | assertThat(new Type("D"), is(new Type("D"))); | ||
| 213 | assertThat(new Type("LFoo;"), is(new Type("LFoo;"))); | ||
| 214 | assertThat(new Type("[I"), is(new Type("[I"))); | ||
| 215 | assertThat(new Type("[[[I"), is(new Type("[[[I"))); | ||
| 216 | assertThat(new Type("[LFoo;"), is(new Type("[LFoo;"))); | ||
| 217 | assertThat(new Type("LFoo<LBar;>;"), is(new Type("LFoo<LBar;>;"))); | ||
| 218 | |||
| 219 | assertThat(new Type("V"), is(not(new Type("I")))); | ||
| 220 | assertThat(new Type("I"), is(not(new Type("J")))); | ||
| 221 | assertThat(new Type("I"), is(not(new Type("LBar;")))); | ||
| 222 | assertThat(new Type("I"), is(not(new Type("[I")))); | ||
| 223 | assertThat(new Type("LFoo;"), is(not(new Type("LBar;")))); | ||
| 224 | assertThat(new Type("LFoo<LBar;>;"), is(not(new Type("LFoo<LCow;>;")))); | ||
| 225 | assertThat(new Type("[I"), is(not(new Type("[Z")))); | ||
| 226 | assertThat(new Type("[[[I"), is(not(new Type("[I")))); | ||
| 227 | assertThat(new Type("[LFoo;"), is(not(new Type("[LBar;")))); | ||
| 228 | } | ||
| 229 | } | ||
diff --git a/test/cuchaz/enigma/TokenChecker.java b/test/cuchaz/enigma/TokenChecker.java new file mode 100644 index 0000000..a72c2fc --- /dev/null +++ b/test/cuchaz/enigma/TokenChecker.java | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2014 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Public License v3.0 | ||
| 5 | * which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/gpl.html | ||
| 7 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 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..390e82f --- /dev/null +++ b/test/cuchaz/enigma/inputs/Keep.java | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | package cuchaz.enigma.inputs; | ||
| 2 | |||
| 3 | public class Keep { | ||
| 4 | public static void main(String[] args) { | ||
| 5 | System.out.println("Keep me!"); | ||
| 6 | } | ||
| 7 | } | ||
diff --git a/test/cuchaz/enigma/inputs/constructors/BaseClass.java b/test/cuchaz/enigma/inputs/constructors/BaseClass.java new file mode 100644 index 0000000..9345308 --- /dev/null +++ b/test/cuchaz/enigma/inputs/constructors/BaseClass.java | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | package cuchaz.enigma.inputs.constructors; | ||
| 2 | |||
| 3 | // none/a | ||
| 4 | public class BaseClass { | ||
| 5 | |||
| 6 | // <init>()V | ||
| 7 | public BaseClass() { | ||
| 8 | System.out.println("Default constructor"); | ||
| 9 | } | ||
| 10 | |||
| 11 | // <init>(I)V | ||
| 12 | public BaseClass(int i) { | ||
| 13 | System.out.println("Int constructor " + i); | ||
| 14 | } | ||
| 15 | } | ||
diff --git a/test/cuchaz/enigma/inputs/constructors/Caller.java b/test/cuchaz/enigma/inputs/constructors/Caller.java new file mode 100644 index 0000000..5727875 --- /dev/null +++ b/test/cuchaz/enigma/inputs/constructors/Caller.java | |||
| @@ -0,0 +1,47 @@ | |||
| 1 | package cuchaz.enigma.inputs.constructors; | ||
| 2 | |||
| 3 | // none/b | ||
| 4 | public class Caller { | ||
| 5 | |||
| 6 | // a()V | ||
| 7 | public void callBaseDefault() { | ||
| 8 | // none/a.<init>()V | ||
| 9 | System.out.println(new BaseClass()); | ||
| 10 | } | ||
| 11 | |||
| 12 | // b()V | ||
| 13 | public void callBaseInt() { | ||
| 14 | // none/a.<init>(I)V | ||
| 15 | System.out.println(new BaseClass(5)); | ||
| 16 | } | ||
| 17 | |||
| 18 | // c()V | ||
| 19 | public void callSubDefault() { | ||
| 20 | // none/d.<init>()V | ||
| 21 | System.out.println(new SubClass()); | ||
| 22 | } | ||
| 23 | |||
| 24 | // d()V | ||
| 25 | public void callSubInt() { | ||
| 26 | // none/d.<init>(I)V | ||
| 27 | System.out.println(new SubClass(6)); | ||
| 28 | } | ||
| 29 | |||
| 30 | // e()V | ||
| 31 | public void callSubIntInt() { | ||
| 32 | // none/d.<init>(II)V | ||
| 33 | System.out.println(new SubClass(4, 2)); | ||
| 34 | } | ||
| 35 | |||
| 36 | // f()V | ||
| 37 | public void callSubSubInt() { | ||
| 38 | // none/e.<init>(I)V | ||
| 39 | System.out.println(new SubSubClass(3)); | ||
| 40 | } | ||
| 41 | |||
| 42 | // g()V | ||
| 43 | public void callDefaultConstructable() { | ||
| 44 | // none/c.<init>()V | ||
| 45 | System.out.println(new DefaultConstructable()); | ||
| 46 | } | ||
| 47 | } | ||
diff --git a/test/cuchaz/enigma/inputs/constructors/DefaultConstructable.java b/test/cuchaz/enigma/inputs/constructors/DefaultConstructable.java new file mode 100644 index 0000000..26a3ddb --- /dev/null +++ b/test/cuchaz/enigma/inputs/constructors/DefaultConstructable.java | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | package cuchaz.enigma.inputs.constructors; | ||
| 2 | |||
| 3 | public class DefaultConstructable { | ||
| 4 | // only default constructor | ||
| 5 | } | ||
diff --git a/test/cuchaz/enigma/inputs/constructors/SubClass.java b/test/cuchaz/enigma/inputs/constructors/SubClass.java new file mode 100644 index 0000000..fecfa2b --- /dev/null +++ b/test/cuchaz/enigma/inputs/constructors/SubClass.java | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | package cuchaz.enigma.inputs.constructors; | ||
| 2 | |||
| 3 | // none/d extends none/a | ||
| 4 | public class SubClass extends BaseClass { | ||
| 5 | |||
| 6 | // <init>()V | ||
| 7 | public SubClass() { | ||
| 8 | // none/a.<init>()V | ||
| 9 | } | ||
| 10 | |||
| 11 | // <init>(I)V | ||
| 12 | public SubClass(int num) { | ||
| 13 | // <init>()V | ||
| 14 | this(); | ||
| 15 | System.out.println("SubClass " + num); | ||
| 16 | } | ||
| 17 | |||
| 18 | // <init>(II)V | ||
| 19 | public SubClass(int a, int b) { | ||
| 20 | // <init>(I)V | ||
| 21 | this(a + b); | ||
| 22 | } | ||
| 23 | |||
| 24 | // <init>(III)V | ||
| 25 | public SubClass(int a, int b, int c) { | ||
| 26 | // none/a.<init>()V | ||
| 27 | } | ||
| 28 | } | ||
diff --git a/test/cuchaz/enigma/inputs/constructors/SubSubClass.java b/test/cuchaz/enigma/inputs/constructors/SubSubClass.java new file mode 100644 index 0000000..ab84161 --- /dev/null +++ b/test/cuchaz/enigma/inputs/constructors/SubSubClass.java | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | package cuchaz.enigma.inputs.constructors; | ||
| 2 | |||
| 3 | // none/e extends none/d | ||
| 4 | public class SubSubClass extends SubClass { | ||
| 5 | |||
| 6 | // <init>(I)V | ||
| 7 | public SubSubClass(int i) { | ||
| 8 | // none/c.<init>(I)V | ||
| 9 | super(i); | ||
| 10 | } | ||
| 11 | } | ||
diff --git a/test/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java b/test/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java new file mode 100644 index 0000000..5b416c4 --- /dev/null +++ b/test/cuchaz/enigma/inputs/inheritanceTree/BaseClass.java | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | package cuchaz.enigma.inputs.inheritanceTree; | ||
| 2 | |||
| 3 | // none/a | ||
| 4 | public abstract class BaseClass { | ||
| 5 | |||
| 6 | // a | ||
| 7 | private String m_name; | ||
| 8 | |||
| 9 | // <init>(Ljava/lang/String;)V | ||
| 10 | protected BaseClass(String name) { | ||
| 11 | m_name = name; | ||
| 12 | } | ||
| 13 | |||
| 14 | // a()Ljava/lang/String; | ||
| 15 | public String getName() { | ||
| 16 | return m_name; | ||
| 17 | } | ||
| 18 | |||
| 19 | // a()V | ||
| 20 | public abstract void doBaseThings(); | ||
| 21 | } | ||
diff --git a/test/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java b/test/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java new file mode 100644 index 0000000..7a99d51 --- /dev/null +++ b/test/cuchaz/enigma/inputs/inheritanceTree/SubclassA.java | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | package cuchaz.enigma.inputs.inheritanceTree; | ||
| 2 | |||
| 3 | // none/b extends none/a | ||
| 4 | public abstract class SubclassA extends BaseClass { | ||
| 5 | |||
| 6 | // <init>(Ljava/lang/String;)V | ||
| 7 | protected SubclassA(String name) { | ||
| 8 | // call to none/a.<init>(Ljava/lang/String)V | ||
| 9 | super(name); | ||
| 10 | } | ||
| 11 | } | ||
diff --git a/test/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java b/test/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java new file mode 100644 index 0000000..c9485d3 --- /dev/null +++ b/test/cuchaz/enigma/inputs/inheritanceTree/SubclassB.java | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | package cuchaz.enigma.inputs.inheritanceTree; | ||
| 2 | |||
| 3 | // none/c extends none/a | ||
| 4 | public class SubclassB extends BaseClass { | ||
| 5 | |||
| 6 | // a | ||
| 7 | private int m_numThings; | ||
| 8 | |||
| 9 | // <init>()V | ||
| 10 | protected SubclassB() { | ||
| 11 | // none/a.<init>(Ljava/lang/String;)V | ||
| 12 | super("B"); | ||
| 13 | |||
| 14 | // access to a | ||
| 15 | m_numThings = 4; | ||
| 16 | } | ||
| 17 | |||
| 18 | @Override | ||
| 19 | // a()V | ||
| 20 | public void doBaseThings() { | ||
| 21 | // call to none/a.a()Ljava/lang/String; | ||
| 22 | System.out.println("Base things by B! " + getName()); | ||
| 23 | } | ||
| 24 | |||
| 25 | // b()V | ||
| 26 | public void doBThings() { | ||
| 27 | // access to a | ||
| 28 | System.out.println("" + m_numThings + " B things!"); | ||
| 29 | } | ||
| 30 | } | ||
diff --git a/test/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java b/test/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java new file mode 100644 index 0000000..afd03ac --- /dev/null +++ b/test/cuchaz/enigma/inputs/inheritanceTree/SubsubclassAA.java | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | package cuchaz.enigma.inputs.inheritanceTree; | ||
| 2 | |||
| 3 | // none/d extends none/b | ||
| 4 | public class SubsubclassAA extends SubclassA { | ||
| 5 | |||
| 6 | protected SubsubclassAA() { | ||
| 7 | // call to none/b.<init>(Ljava/lang/String;)V | ||
| 8 | super("AA"); | ||
| 9 | } | ||
| 10 | |||
| 11 | @Override | ||
| 12 | // a()Ljava/lang/String; | ||
| 13 | public String getName() { | ||
| 14 | // call to none/b.a()Ljava/lang/String; | ||
| 15 | return "subsub" + super.getName(); | ||
| 16 | } | ||
| 17 | |||
| 18 | @Override | ||
| 19 | // a()V | ||
| 20 | public void doBaseThings() { | ||
| 21 | // call to none/d.a()Ljava/lang/String; | ||
| 22 | System.out.println("Base things by " + getName()); | ||
| 23 | } | ||
| 24 | } | ||
diff --git a/test/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java b/test/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java new file mode 100644 index 0000000..f7118f6 --- /dev/null +++ b/test/cuchaz/enigma/inputs/innerClasses/A_Anonymous.java | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | package cuchaz.enigma.inputs.innerClasses; | ||
| 2 | |||
| 3 | public class A_Anonymous { | ||
| 4 | |||
| 5 | public void foo() { | ||
| 6 | Runnable runnable = new Runnable() { | ||
| 7 | @Override | ||
| 8 | public void run() { | ||
| 9 | // don't care | ||
| 10 | } | ||
| 11 | }; | ||
| 12 | runnable.run(); | ||
| 13 | } | ||
| 14 | } | ||
diff --git a/test/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java b/test/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java new file mode 100644 index 0000000..42fba9a --- /dev/null +++ b/test/cuchaz/enigma/inputs/innerClasses/B_AnonymousWithScopeArgs.java | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | package cuchaz.enigma.inputs.innerClasses; | ||
| 2 | |||
| 3 | public class B_AnonymousWithScopeArgs { | ||
| 4 | |||
| 5 | public static void foo(final D_Simple arg) { | ||
| 6 | System.out.println(new Object() { | ||
| 7 | @Override | ||
| 8 | public String toString() { | ||
| 9 | return arg.toString(); | ||
| 10 | } | ||
| 11 | }); | ||
| 12 | } | ||
| 13 | } | ||
diff --git a/test/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java b/test/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java new file mode 100644 index 0000000..8fa6c5b --- /dev/null +++ b/test/cuchaz/enigma/inputs/innerClasses/C_ConstructorArgs.java | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | package cuchaz.enigma.inputs.innerClasses; | ||
| 2 | |||
| 3 | @SuppressWarnings("unused") | ||
| 4 | public class C_ConstructorArgs { | ||
| 5 | |||
| 6 | class Inner { | ||
| 7 | |||
| 8 | private int a; | ||
| 9 | |||
| 10 | public Inner(int a) { | ||
| 11 | this.a = a; | ||
| 12 | } | ||
| 13 | } | ||
| 14 | |||
| 15 | Inner i; | ||
| 16 | |||
| 17 | public void foo() { | ||
| 18 | i = new Inner(5); | ||
| 19 | } | ||
| 20 | } | ||
diff --git a/test/cuchaz/enigma/inputs/innerClasses/D_Simple.java b/test/cuchaz/enigma/inputs/innerClasses/D_Simple.java new file mode 100644 index 0000000..c4fc0ef --- /dev/null +++ b/test/cuchaz/enigma/inputs/innerClasses/D_Simple.java | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | package cuchaz.enigma.inputs.innerClasses; | ||
| 2 | |||
| 3 | public class D_Simple { | ||
| 4 | |||
| 5 | class Inner { | ||
| 6 | // nothing to do | ||
| 7 | } | ||
| 8 | } | ||
diff --git a/test/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java b/test/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java new file mode 100644 index 0000000..e1de53c --- /dev/null +++ b/test/cuchaz/enigma/inputs/innerClasses/E_AnonymousWithOuterAccess.java | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | package cuchaz.enigma.inputs.innerClasses; | ||
| 2 | |||
| 3 | public class E_AnonymousWithOuterAccess { | ||
| 4 | |||
| 5 | // reproduction of error case documented at: | ||
| 6 | // https://bitbucket.org/cuchaz/enigma/issue/61/stackoverflowerror-when-deobfuscating | ||
| 7 | |||
| 8 | public Object makeInner() { | ||
| 9 | outerMethod(); | ||
| 10 | return new Object() { | ||
| 11 | @Override | ||
| 12 | public String toString() { | ||
| 13 | return outerMethod(); | ||
| 14 | } | ||
| 15 | }; | ||
| 16 | } | ||
| 17 | |||
| 18 | private String outerMethod() { | ||
| 19 | return "foo"; | ||
| 20 | } | ||
| 21 | } | ||
diff --git a/test/cuchaz/enigma/inputs/loneClass/LoneClass.java b/test/cuchaz/enigma/inputs/loneClass/LoneClass.java new file mode 100644 index 0000000..18c716e --- /dev/null +++ b/test/cuchaz/enigma/inputs/loneClass/LoneClass.java | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | package cuchaz.enigma.inputs.loneClass; | ||
| 2 | |||
| 3 | public class LoneClass { | ||
| 4 | |||
| 5 | private String m_name; | ||
| 6 | |||
| 7 | public LoneClass(String name) { | ||
| 8 | m_name = name; | ||
| 9 | } | ||
| 10 | |||
| 11 | public String getName() { | ||
| 12 | return m_name; | ||
| 13 | } | ||
| 14 | } | ||
diff --git a/test/cuchaz/enigma/inputs/translation/A.java b/test/cuchaz/enigma/inputs/translation/A.java new file mode 100644 index 0000000..b8aaf11 --- /dev/null +++ b/test/cuchaz/enigma/inputs/translation/A.java | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | package cuchaz.enigma.inputs.translation; | ||
| 2 | |||
| 3 | public class A { | ||
| 4 | |||
| 5 | public int one; | ||
| 6 | public float two; | ||
| 7 | public String three; | ||
| 8 | } | ||
diff --git a/test/cuchaz/enigma/resources/translation.mappings b/test/cuchaz/enigma/resources/translation.mappings new file mode 100644 index 0000000..70755bf --- /dev/null +++ b/test/cuchaz/enigma/resources/translation.mappings | |||
| @@ -0,0 +1,4 @@ | |||
| 1 | CLASS none/a deobf/A | ||
| 2 | FIELD a one | ||
| 3 | FIELD b two | ||
| 4 | FIELD c three \ No newline at end of file | ||